Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
108
omni_inbound/src/server.ts
Normal file
108
omni_inbound/src/server.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
||||
import { RECEIVER_FLOW_QUEUE_NAME, enqueueInboundEvent, isDuplicateJobError } from "./queue";
|
||||
import { parseTelegramBusinessUpdate } from "./telegram";
|
||||
|
||||
const MAX_BODY_SIZE_BYTES = Number(process.env.MAX_BODY_SIZE_BYTES || 1024 * 1024);
|
||||
|
||||
function writeJson(res: ServerResponse, statusCode: number, body: unknown) {
|
||||
const payload = JSON.stringify(body);
|
||||
res.statusCode = statusCode;
|
||||
res.setHeader("content-type", "application/json; charset=utf-8");
|
||||
res.end(payload);
|
||||
}
|
||||
|
||||
async function readJsonBody(req: IncomingMessage): Promise<unknown> {
|
||||
const chunks: Buffer[] = [];
|
||||
let total = 0;
|
||||
|
||||
for await (const chunk of req) {
|
||||
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||
total += buf.length;
|
||||
if (total > MAX_BODY_SIZE_BYTES) {
|
||||
throw new Error(`payload_too_large:${MAX_BODY_SIZE_BYTES}`);
|
||||
}
|
||||
chunks.push(buf);
|
||||
}
|
||||
|
||||
const raw = Buffer.concat(chunks).toString("utf8");
|
||||
if (!raw) return {};
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
function validateTelegramSecret(req: IncomingMessage): boolean {
|
||||
const expected = (process.env.TELEGRAM_WEBHOOK_SECRET || "").trim();
|
||||
if (!expected) return true;
|
||||
|
||||
const incoming = String(req.headers["x-telegram-bot-api-secret-token"] || "").trim();
|
||||
return incoming !== "" && incoming === expected;
|
||||
}
|
||||
|
||||
export function startServer() {
|
||||
const port = Number(process.env.PORT || 8080);
|
||||
|
||||
const server = createServer(async (req, res) => {
|
||||
if (!req.url || !req.method) {
|
||||
writeJson(res, 404, { ok: false, error: "not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url === "/health" && req.method === "GET") {
|
||||
writeJson(res, 200, {
|
||||
ok: true,
|
||||
service: "omni_inbound",
|
||||
queue: RECEIVER_FLOW_QUEUE_NAME,
|
||||
now: new Date().toISOString(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url === "/webhooks/telegram/business" && req.method === "POST") {
|
||||
if (!validateTelegramSecret(req)) {
|
||||
writeJson(res, 401, { ok: false, error: "invalid_webhook_secret" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await readJsonBody(req);
|
||||
const envelope = parseTelegramBusinessUpdate(body);
|
||||
|
||||
await enqueueInboundEvent(envelope);
|
||||
|
||||
writeJson(res, 200, {
|
||||
ok: true,
|
||||
queued: true,
|
||||
duplicate: false,
|
||||
providerEventId: envelope.providerEventId,
|
||||
idempotencyKey: envelope.idempotencyKey,
|
||||
});
|
||||
} catch (error) {
|
||||
if (isDuplicateJobError(error)) {
|
||||
writeJson(res, 200, {
|
||||
ok: true,
|
||||
queued: false,
|
||||
duplicate: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const statusCode = message.startsWith("payload_too_large:") ? 413 : 503;
|
||||
writeJson(res, statusCode, {
|
||||
ok: false,
|
||||
error: "receiver_enqueue_failed",
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
writeJson(res, 404, { ok: false, error: "not_found" });
|
||||
});
|
||||
|
||||
server.listen(port, "0.0.0.0", () => {
|
||||
console.log(`[omni_inbound] listening on :${port}`);
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
Reference in New Issue
Block a user