Something went wrong. Try again.
a little carrier pigeon that ferries figma events to discord
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130/** * HTTP entrypoint for the worker. * * Exposes: * POST /figma - Figma webhook endpoint * GET /health - liveness probe * * All other routes return 404. */
import type { Env } from "./types.js";import type { FigmaWebhookEvent, LibraryPublishEvent } from "./figma.js";import { isExcluded } from "./figma.js";
export { Batcher } from "./batcher.js";
function ok(body = "ok"): Response { return new Response(body, { status: 200 });}
async function handleFigma( request: Request, env: Env, ctx: ExecutionContext,): Promise<Response> { let payload: FigmaWebhookEvent; try { payload = (await request.json()) as FigmaWebhookEvent; } catch { return new Response("invalid json", { status: 400 }); }
// Constant-time-ish passcode check. Figma echoes the value we registered. if ( typeof payload.passcode !== "string" || payload.passcode !== env.FIGMA_PASSCODE ) { return new Response("unauthorized", { status: 401 }); }
// Record liveness so /health can surface upstream silences. Reserved // __-prefixed key; operators must not use __-prefixed Figma file_keys. // Fire-and-forget so Figma keeps getting a fast 200. ctx.waitUntil( env.FIGMA_DISCORD_WEBHOOK.put( "__last_figma_request_at", new Date().toISOString(), ), );
// Figma sends PING immediately after webhook creation. if (payload.event_type === "PING") { console.log("received PING"); return ok(); }
if (payload.event_type !== "LIBRARY_PUBLISH") { // Forward-compat: accept unknown event types silently. console.log(`ignoring event_type=${payload.event_type}`); return ok(); }
const event = payload as LibraryPublishEvent;
if (isExcluded(event.description)) { console.log(`skipping ${event.file_key}: #exclude in description`); return ok(); }
const discordWebhookUrl = await env.FIGMA_DISCORD_WEBHOOK.get(event.file_key); if (!discordWebhookUrl) { console.warn( `no KV mapping for file_key=${event.file_key} (${event.file_name}); triggered_by=${event.triggered_by?.handle ?? "unknown"}; skipping`, ); return ok(); }
// Route to the per-file Durable Object; fire-and-forget so Figma gets 200 fast. const id = env.BATCHER.idFromName(event.file_key); const stub = env.BATCHER.get(id); const ingestReq = new Request("https://batcher/ingest", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ event, discordWebhookUrl }), });
ctx.waitUntil( (async () => { try { const res = await stub.fetch(ingestReq); if (!res.ok) { console.error( `Batcher ingest failed for ${event.file_key}: ${res.status}`, ); } } catch (err) { console.error(`Batcher ingest threw for ${event.file_key}:`, err); } })(), );
return ok();}
export default { async fetch( request: Request, env: Env, ctx: ExecutionContext, ): Promise<Response> { const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/health") { const lastFigmaRequestAt = await env.FIGMA_DISCORD_WEBHOOK.get( "__last_figma_request_at", ); return new Response( JSON.stringify({ status: "healthy", lastFigmaRequestAt }), { status: 200, headers: { "Content-Type": "application/json" } }, ); }
if (request.method === "POST" && url.pathname === "/figma") { return handleFigma(request, env, ctx); }
return new Response("not found", { status: 404 }); },} satisfies ExportedHandler<Env>;