Squirble
Squirble2w ago

How to get `ctx` through indirection?

I'm writing a telegram bot that uses webhooks. I was able to set it up like this: convex/http.ts
import { httpRouter } from "convex/server";
import { webhook } from "./telegramWebhook";

const http = httpRouter();

http.route({
path: "/telegram/bot/webhook",
method: "POST",
handler: webhook,
});

export default http;
import { httpRouter } from "convex/server";
import { webhook } from "./telegramWebhook";

const http = httpRouter();

http.route({
path: "/telegram/bot/webhook",
method: "POST",
handler: webhook,
});

export default http;
convex/telegramWebhook.ts
import { httpAction } from "./_generated/server";
import { webhookCallback } from "grammy";
import bot from "./bot";

const handleUpdate = webhookCallback(bot, "std/http");

export const webhook = httpAction(async (ctx, req) => {
try {
const url = new URL(req.url);
if (url.searchParams.get("token") !== bot.token) {
return new Response("not allowed", { status: 405 });
}
return await handleUpdate(req);
} catch (err) {
console.error(err);
return new Response();
}
});

export default webhook;
import { httpAction } from "./_generated/server";
import { webhookCallback } from "grammy";
import bot from "./bot";

const handleUpdate = webhookCallback(bot, "std/http");

export const webhook = httpAction(async (ctx, req) => {
try {
const url = new URL(req.url);
if (url.searchParams.get("token") !== bot.token) {
return new Response("not allowed", { status: 405 });
}
return await handleUpdate(req);
} catch (err) {
console.error(err);
return new Response();
}
});

export default webhook;
Now, in the bot itself, how can I access the convex ctx? bot
import { Bot } from "grammy";
const bot = new Bot(process.env.TELEGRAM_BOT_SECRET!);

bot.command("start", (ctx) => ctx.reply("Welcome! Up and running."));

bot.command("getMessages", (ctx) => {
// TODO: How do I get the convex ctx here?
const poll = await messages.list(ctx); // not the same ctx!
ctx.reply(`You have ${messages.length} messages.`)
});

export default bot
import { Bot } from "grammy";
const bot = new Bot(process.env.TELEGRAM_BOT_SECRET!);

bot.command("start", (ctx) => ctx.reply("Welcome! Up and running."));

bot.command("getMessages", (ctx) => {
// TODO: How do I get the convex ctx here?
const poll = await messages.list(ctx); // not the same ctx!
ctx.reply(`You have ${messages.length} messages.`)
});

export default bot
Can I do some sort of global / thread-local trick to get access to the convex context at a distance? Or do I need to somehow pass it through indirection? Would it be safe to try to attach the convex context to my bot somehow in the webhook http handler? How unique is the convex context? Is it different in each request or is it really just the same thing? Is there a reason we can't just import it from somewhere?
6 Replies
Convex Bot
Convex Bot2w ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
erquhart
erquhart2w ago
The Convex ctx is only available in Convex functions in your convex directory. You can write http actions that your bot can use to interact with your Convex deployment. The HTTP Actions doc gives the fundamentals: https://docs.convex.dev/functions/http-actions This stack article gets more advanced, it focuses on using Hono specifically, but also gives a good picture of how to author an api with Convex: https://stack.convex.dev/hono-with-convex Finally, if you want, you can actually call any of your non-internal Convex functions directly - just be sure to use a shared secret or something to verify requests: https://docs.convex.dev/http-api/
Squirble
SquirbleOP2w ago
Here's what I ended up doing:
import { httpAction } from "../_generated/server";
import { webhookCallback } from "grammy";
import { createBot } from "./bot";

export const webhook = httpAction(async (ctx, req) => {
const bot = createBot(ctx);
const handleUpdate = webhookCallback(bot, "std/http");
return await handleUpdate(req);
});

export default webhook;
import { httpAction } from "../_generated/server";
import { webhookCallback } from "grammy";
import { createBot } from "./bot";

export const webhook = httpAction(async (ctx, req) => {
const bot = createBot(ctx);
const handleUpdate = webhookCallback(bot, "std/http");
return await handleUpdate(req);
});

export default webhook;
I created a factory function that creates the bot in each request so it can have the convex context. Is this a bad idea?
ampp
ampp2w ago
Its what i'm doing especially now that need to interface with many bots
erquhart
erquhart2w ago
I thought the bot had to be a long running service, did not know serverless was even an option. Great approach!
Squirble
SquirbleOP7d ago
Yeah it's webhooks.