songs2vid/lib/webhooks.ts

53 lines
1.4 KiB
TypeScript
Raw Normal View History

/**
* Deliver optional job webhooks for n8n / automation consumers.
* Fire-and-forget; failures are logged and never fail the encode/upload path.
*/
type WebhookPayload = {
event: "job.item.completed" | "job.item.failed" | "job.completed" | "job.failed" | "job.partial";
jobId: string;
status: string;
itemId?: string;
youtubeVideoId?: string | null;
error?: string | null;
completedAt?: string | null;
};
export async function deliverJobWebhook(
webhookUrl: string | null | undefined,
payload: WebhookPayload,
): Promise<void> {
if (!webhookUrl?.trim()) return;
let url: URL;
try {
url = new URL(webhookUrl.trim());
} catch {
console.warn(`[webhook] invalid URL for job ${payload.jobId}`);
return;
}
if (url.protocol !== "https:" && url.protocol !== "http:") {
console.warn(`[webhook] refused non-http(s) URL for job ${payload.jobId}`);
return;
}
try {
const res = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "Songs2VID-Webhook/1.0",
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(8_000),
});
if (!res.ok) {
console.warn(`[webhook] ${payload.jobId} → ${res.status}`);
}
} catch (err) {
console.warn(
`[webhook] delivery failed for ${payload.jobId}:`,
err instanceof Error ? err.message : err,
);
}
}