import { createHmac } from "node:crypto"; import type { GenerationJob, WebhookLastStatus } from "@/lib/types"; export type JobWebhookPayload = { jobId: string; status: GenerationJob["status"]; capability: GenerationJob["capability"]; outputAssetIds: string[]; error?: GenerationJob["error"]; updatedAt: string; }; const MAX_WEBHOOK_ATTEMPTS = 3; export function buildJobWebhookPayload(job: GenerationJob): JobWebhookPayload { return { jobId: job.id, status: job.status, capability: job.capability, outputAssetIds: job.outputAssetIds, error: job.error, updatedAt: job.updatedAt }; } export function signWebhookBody(body: string, secret = process.env.ZHINIAN_WEBHOOK_SECRET): string | undefined { if (!secret?.trim()) return undefined; return `sha256=${createHmac("sha256", secret.trim()).update(body).digest("hex")}`; } export async function deliverJobWebhook(job: GenerationJob): Promise<{ attempts: number; lastStatus?: WebhookLastStatus; }> { if (!job.webhookUrl) return { attempts: job.webhookAttempts || 0 }; let attempts = job.webhookAttempts || 0; let lastStatus: WebhookLastStatus | undefined = job.webhookLastStatus; const payload = buildJobWebhookPayload(job); const body = JSON.stringify(payload); const signature = signWebhookBody(body); while (attempts < MAX_WEBHOOK_ATTEMPTS) { attempts += 1; const attemptedAt = new Date().toISOString(); try { const response = await fetch(job.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": "zhinian-aigc-webhook/1.0", ...(signature ? { "X-Zhinian-Signature": signature } : {}) }, body }); lastStatus = { ok: response.ok, status: response.status, attemptedAt }; if (response.ok) break; } catch (error) { lastStatus = { ok: false, error: error instanceof Error ? error.message : String(error), attemptedAt }; } } return { attempts, lastStatus }; }