Files
NianAIGC/lib/server/webhook.ts
2026-05-29 12:32:02 +08:00

72 lines
2.0 KiB
TypeScript

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 };
}