kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { createHmac } from "node:crypto";
2import { assertPublicWebhookDestination } from "./config";
3
4type GenericWebhookPayload = Record<string, unknown>;
5
6const GENERIC_WEBHOOK_TIMEOUT_MS = 10_000;
7
8export async function postToGenericWebhook(
9 webhookUrl: string,
10 payload: GenericWebhookPayload,
11 secret?: string,
12): Promise<void> {
13 await assertPublicWebhookDestination(webhookUrl);
14
15 const body = JSON.stringify(payload);
16 const headers: Record<string, string> = {
17 "Content-Type": "application/json",
18 };
19
20 if (secret) {
21 headers["X-Kaneo-Signature"] = createHmac("sha256", secret)
22 .update(body)
23 .digest("hex");
24 }
25
26 const controller = new AbortController();
27 const timeoutId = setTimeout(
28 () => controller.abort(),
29 GENERIC_WEBHOOK_TIMEOUT_MS,
30 );
31
32 try {
33 const response = await fetch(webhookUrl, {
34 method: "POST",
35 headers,
36 body,
37 signal: controller.signal,
38 });
39
40 if (!response.ok) {
41 const errorText = await response.text();
42 throw new Error(
43 `Generic webhook request failed (${response.status}): ${errorText}`,
44 );
45 }
46 } catch (error) {
47 if (error instanceof Error && error.name === "AbortError") {
48 throw new Error(
49 `Generic webhook request timed out after ${GENERIC_WEBHOOK_TIMEOUT_MS}ms`,
50 );
51 }
52
53 throw error;
54 } finally {
55 clearTimeout(timeoutId);
56 }
57}