Payment-free self-hosted builds keep full API access with optional webhookUrl callbacks and the published n8n-nodes-songs2vid package source under integrations/n8n.
53 lines
1.4 KiB
TypeScript
53 lines
1.4 KiB
TypeScript
/**
|
|
* 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,
|
|
);
|
|
}
|
|
}
|