import { createHash, timingSafeEqual } from "crypto"; export default async function schedule( request: { headers?: Record }, response: { status(code: number): { end(): void } } ): Promise { // Vercel Cron attaches Authorization from the project env named CRON_SECRET. // This function checks and forwards VERCEL_CRON_SECRET, so those two values must match. const secret = process.env.VERCEL_CRON_SECRET; const url = process.env.SCHEDULE_WORKER_TRIGGER_URL; if (!secret || !url) { throw new Error("VERCEL_CRON_SECRET and SCHEDULE_WORKER_TRIGGER_URL are required"); } const header = headerValue(request, "authorization"); if (!sameBearer(header, secret)) { throw new Error("Invalid cron authorization"); } let upstream: { ok: boolean; status: number }; try { upstream = await fetch(url, { method: "POST", headers: { Authorization: `Bearer ${secret}` }, signal: AbortSignal.timeout(10000) }); } catch (error) { throw error instanceof Error ? error : new Error("Schedule worker request failed"); } if (!upstream.ok) { throw new Error(`Schedule worker failed with status ${upstream.status}`); } response.status(200).end(); } function headerValue(request: { headers?: Record }, name: string): string { const value = request.headers ? request.headers[name] : ""; return Array.isArray(value) ? String(value[0] || "") : String(value || ""); } function sameBearer(actual: string, secret: string): boolean { const actualHash = createHash("sha256").update(actual).digest(); const expectedHash = createHash("sha256").update(`Bearer ${secret}`).digest(); return timingSafeEqual(actualHash, expectedHash); }