@tangle-network/agent-integrations 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog.js +3 -3
- package/dist/{chunk-EPDUDWOD.js → chunk-EV7PFUCM.js} +2 -2
- package/dist/{chunk-2TW2QKGZ.js → chunk-XC54EOUQ.js} +14 -1
- package/dist/{chunk-2TW2QKGZ.js.map → chunk-XC54EOUQ.js.map} +1 -1
- package/dist/{chunk-G3S6OGY6.js → chunk-XECSOZRL.js} +2 -2
- package/dist/connectors/adapters/index.js +2 -2
- package/dist/connectors/index.d.ts +9 -1
- package/dist/connectors/index.js +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/mcp.js +3 -3
- package/dist/registry.js +3 -3
- package/dist/runtime.js +3 -3
- package/dist/webhooks/index.d.ts +34 -1
- package/dist/webhooks/index.js +32 -1
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-EPDUDWOD.js.map → chunk-EV7PFUCM.js.map} +0 -0
- /package/dist/{chunk-G3S6OGY6.js.map → chunk-XECSOZRL.js.map} +0 -0
package/dist/catalog.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
parseIntegrationToolName,
|
|
8
8
|
searchIntegrationTools,
|
|
9
9
|
toMcpTools
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XECSOZRL.js";
|
|
11
11
|
import "./chunk-S2BUASSV.js";
|
|
12
12
|
import "./chunk-LA76XB7M.js";
|
|
13
13
|
import "./chunk-H4XYLS7T.js";
|
|
@@ -15,8 +15,8 @@ import "./chunk-VLVSP7SP.js";
|
|
|
15
15
|
import "./chunk-IA5GCKM6.js";
|
|
16
16
|
import "./chunk-7P2LN4VT.js";
|
|
17
17
|
import "./chunk-376UBTNB.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-EV7PFUCM.js";
|
|
19
|
+
import "./chunk-XC54EOUQ.js";
|
|
20
20
|
import "./chunk-UWF33ESC.js";
|
|
21
21
|
import "./chunk-PZ5AY32C.js";
|
|
22
22
|
export {
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
firstHeader,
|
|
3
3
|
verifySlackSignature,
|
|
4
4
|
verifyStripeSignature
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XC54EOUQ.js";
|
|
6
6
|
import {
|
|
7
7
|
CredentialsExpired,
|
|
8
8
|
DEFAULT_TANGLE_PLATFORM_URL,
|
|
@@ -125506,4 +125506,4 @@ export {
|
|
|
125506
125506
|
demandbaseConnector,
|
|
125507
125507
|
adapters_exports
|
|
125508
125508
|
};
|
|
125509
|
-
//# sourceMappingURL=chunk-
|
|
125509
|
+
//# sourceMappingURL=chunk-EV7PFUCM.js.map
|
|
@@ -76,6 +76,18 @@ function verifyTwilioSignature(input, options = {}) {
|
|
|
76
76
|
if (expectedBuf.length !== sigBuf.length) return false;
|
|
77
77
|
return timingSafeEqual(expectedBuf, sigBuf);
|
|
78
78
|
}
|
|
79
|
+
function verifyEmailSignature(rawBody, signatureHeader, timestampHeader, secret, options = {}) {
|
|
80
|
+
const ts = Number(timestampHeader);
|
|
81
|
+
if (!Number.isFinite(ts)) return false;
|
|
82
|
+
const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
|
|
83
|
+
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
84
|
+
if (Math.abs(now - ts) > tolerance) return false;
|
|
85
|
+
const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
|
|
86
|
+
const expectedBuf = Buffer.from(expected, "utf8");
|
|
87
|
+
const sigBuf = Buffer.from(signatureHeader.toLowerCase(), "utf8");
|
|
88
|
+
if (sigBuf.length !== expectedBuf.length) return false;
|
|
89
|
+
return timingSafeEqual(sigBuf, expectedBuf);
|
|
90
|
+
}
|
|
79
91
|
function firstHeader(headers, name) {
|
|
80
92
|
const v = headers[name] ?? headers[name.toLowerCase()] ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
|
81
93
|
if (Array.isArray(v)) return v[0];
|
|
@@ -89,6 +101,7 @@ export {
|
|
|
89
101
|
verifySlackSignature,
|
|
90
102
|
verifyHmacSignature,
|
|
91
103
|
verifyTwilioSignature,
|
|
104
|
+
verifyEmailSignature,
|
|
92
105
|
firstHeader
|
|
93
106
|
};
|
|
94
|
-
//# sourceMappingURL=chunk-
|
|
107
|
+
//# sourceMappingURL=chunk-XC54EOUQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/connectors/webhooks.ts"],"sourcesContent":["/**\n * Inbound webhook signature verifiers — provider-specific HMAC schemes.\n *\n * Each signature scheme is a pure function:\n * (rawBody: string, headers, secret, now?) → boolean\n *\n * Constant-time comparison via `crypto.timingSafeEqual`. Timestamps are\n * checked against a configurable tolerance to bound replay risk; the default\n * mirrors the upstream provider's documented window (Stripe: 5 min, Slack: 5 min).\n *\n * These verifiers are the building blocks for any inbound-webhook receiver\n * (a route + a `verify` call + a per-event handler). They live in this\n * package so every consumer of the integration substrate gets correct\n * verification — not just one product reimplementing it.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto'\n\n/** Default replay-protection window. Providers commonly use 5 minutes. */\nexport const DEFAULT_SIGNATURE_TOLERANCE_SECONDS = 5 * 60\n\n// ─── Stripe ─────────────────────────────────────────────────────────────\n//\n// Stripe signs webhooks with a single header `Stripe-Signature` of the form\n//\n// t=<timestamp>,v1=<sig1>,v1=<sig2>,...\n//\n// where `t` is the Unix timestamp the event was generated, and each `v1`\n// is `HMAC-SHA256(secret, \"<t>.<rawBody>\")`. Multiple `v1` entries appear\n// during secret rotation — any one matching is sufficient.\n//\n// https://stripe.com/docs/webhooks/signatures\n\nexport interface ParsedStripeSignatureHeader {\n t: number\n sigs: string[]\n}\n\nexport function parseStripeSignatureHeader(header: string): ParsedStripeSignatureHeader | null {\n const acc: { ts?: number; sigs: string[] } = { sigs: [] }\n for (const part of header.split(',')) {\n const idx = part.indexOf('=')\n if (idx < 0) continue\n const key = part.slice(0, idx).trim()\n const val = part.slice(idx + 1).trim()\n if (key === 't') {\n const n = Number(val)\n if (Number.isFinite(n)) acc.ts = n\n } else if (key === 'v1') {\n acc.sigs.push(val)\n }\n }\n if (acc.ts === undefined || acc.sigs.length === 0) return null\n return { t: acc.ts, sigs: acc.sigs }\n}\n\nexport interface StripeVerifyOptions {\n /** Replay-protection window in seconds. Default 300. */\n toleranceSeconds?: number\n /** Override `now()` for tests. UTC seconds. */\n now?: number\n}\n\n/** Verify a Stripe webhook signature against the raw request body. */\nexport function verifyStripeSignature(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options: StripeVerifyOptions = {},\n): boolean {\n const parsed = parseStripeSignatureHeader(signatureHeader)\n if (!parsed) return false\n const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS\n const now = options.now ?? Math.floor(Date.now() / 1000)\n if (Math.abs(now - parsed.t) > tolerance) return false\n const expected = createHmac('sha256', secret).update(`${parsed.t}.${rawBody}`).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const sig of parsed.sigs) {\n const sigBuf = Buffer.from(sig, 'utf8')\n if (sigBuf.length !== expectedBuf.length) continue\n if (timingSafeEqual(sigBuf, expectedBuf)) return true\n }\n return false\n}\n\n// ─── Slack ──────────────────────────────────────────────────────────────\n//\n// Slack signs request bodies with two headers:\n//\n// X-Slack-Signature: v0=<HMAC-SHA256(secret, \"v0:<ts>:<body>\")>\n// X-Slack-Request-Timestamp: <ts>\n//\n// https://api.slack.com/authentication/verifying-requests-from-slack\n\nexport interface SlackVerifyOptions {\n toleranceSeconds?: number\n now?: number\n}\n\nexport function verifySlackSignature(\n rawBody: string,\n signatureHeader: string,\n timestampHeader: string,\n secret: string,\n options: SlackVerifyOptions = {},\n): boolean {\n if (!signatureHeader.startsWith('v0=')) return false\n const ts = Number(timestampHeader)\n if (!Number.isFinite(ts)) return false\n const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS\n const now = options.now ?? Math.floor(Date.now() / 1000)\n if (Math.abs(now - ts) > tolerance) return false\n const expected = 'v0=' + createHmac('sha256', secret).update(`v0:${ts}:${rawBody}`).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n const sigBuf = Buffer.from(signatureHeader, 'utf8')\n if (sigBuf.length !== expectedBuf.length) return false\n return timingSafeEqual(sigBuf, expectedBuf)\n}\n\n// ─── Generic HMAC ───────────────────────────────────────────────────────\n//\n// For \"we shipped a webhook URL with a shared HMAC secret\" patterns —\n// covers any custom integration where the operator picks the message\n// format. The signed message is the literal `rawBody` (no timestamp\n// prefix); replay protection is the caller's responsibility (use a\n// nonce header + a small dedup cache).\n\nexport interface GenericHmacVerifyOptions {\n /** sha256 (default) | sha1 | sha512 — matches the algorithm the receiver\n * computed at sign time. */\n algorithm?: 'sha256' | 'sha1' | 'sha512'\n /** Optional prefix the receiver prepends to the signature in the header\n * (e.g., `'sha256='`). Stripped before constant-time comparison. */\n signaturePrefix?: string\n /** Lowercase comparison (most providers emit hex-lowercase). Default true. */\n lowercaseHex?: boolean\n}\n\nexport function verifyHmacSignature(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options: GenericHmacVerifyOptions = {},\n): boolean {\n const algorithm = options.algorithm ?? 'sha256'\n const prefix = options.signaturePrefix ?? ''\n const lower = options.lowercaseHex ?? true\n let candidate = signatureHeader\n if (prefix) {\n if (!candidate.startsWith(prefix)) return false\n candidate = candidate.slice(prefix.length)\n }\n if (lower) candidate = candidate.toLowerCase()\n const expected = createHmac(algorithm, secret).update(rawBody).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n const sigBuf = Buffer.from(candidate, 'utf8')\n if (sigBuf.length !== expectedBuf.length) return false\n return timingSafeEqual(sigBuf, expectedBuf)\n}\n\n// ─── Twilio ─────────────────────────────────────────────────────────────\n//\n// Twilio's webhook signature scheme is unlike Stripe/Slack — it doesn't\n// sign the raw body. It signs the concatenation of:\n//\n// fullUrl + sortedConcatenatedParams\n//\n// where `fullUrl` is the public URL Twilio called (scheme + host + path\n// + query, exactly as Twilio constructed it from your console config),\n// and `sortedConcatenatedParams` is `key1+value1+key2+value2+...` over\n// the alphabetically-sorted keys of the POST body params (form-encoded).\n//\n// For JSON-bodied Twilio webhooks (Conversations API), the body is signed\n// as raw bytes — pass `{ bodyAsRaw: true, rawBody }` for that path.\n//\n// HMAC-SHA1, base64-encoded. Header: `X-Twilio-Signature`.\n//\n// https://www.twilio.com/docs/usage/webhooks/webhooks-security\n\nexport interface TwilioVerifyOptions {\n /** Skip verification when the auth token isn't configured. Useful in\n * dev where the receiver wants to accept any payload. Default `false`\n * — production should always require a configured token. */\n skipWhenAuthTokenMissing?: boolean\n /** When true, sign the raw body instead of the URL-encoded sorted-params\n * reduction. Twilio uses raw-body signing for `application/json`\n * webhook bodies. Default `false`. */\n bodyAsRaw?: boolean\n /** When `bodyAsRaw` is true, the raw body to sign. Ignored otherwise. */\n rawBody?: string\n}\n\n/** Verify a Twilio webhook signature. */\nexport function verifyTwilioSignature(\n input: {\n authToken: string | null | undefined\n signatureHeader: string | string[] | undefined\n fullUrl: string | null | undefined\n params: Record<string, string> | undefined\n },\n options: TwilioVerifyOptions = {},\n): boolean {\n if (!input.authToken) {\n return options.skipWhenAuthTokenMissing === true\n }\n const signature = input.signatureHeader\n if (!signature || Array.isArray(signature)) return false\n if (!input.fullUrl) return false\n\n const data = options.bodyAsRaw === true\n ? input.fullUrl + (options.rawBody ?? '')\n : Object.keys(input.params ?? {})\n .sort()\n .reduce((acc, key) => acc + key + (input.params![key] ?? ''), input.fullUrl)\n\n const expected = createHmac('sha1', input.authToken).update(data).digest('base64')\n const expectedBuf = Buffer.from(expected)\n const sigBuf = Buffer.from(signature)\n if (expectedBuf.length !== sigBuf.length) return false\n return timingSafeEqual(expectedBuf, sigBuf)\n}\n\n// ─── Header helper ──────────────────────────────────────────────────────\n//\n// Most fastify/express adapters expose request headers as\n// `Record<string, string | string[] | undefined>`. This helper picks the\n// first canonical value for a given name (case-insensitive).\n\nexport function firstHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string,\n): string | undefined {\n const v = headers[name]\n ?? headers[name.toLowerCase()]\n ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1]\n if (Array.isArray(v)) return v[0]\n return typeof v === 'string' ? v : undefined\n}\n"],"mappings":";AAgBA,SAAS,YAAY,uBAAuB;AAGrC,IAAM,sCAAsC,IAAI;AAmBhD,SAAS,2BAA2B,QAAoD;AAC7F,QAAM,MAAuC,EAAE,MAAM,CAAC,EAAE;AACxD,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,QAAI,MAAM,EAAG;AACb,UAAM,MAAM,KAAK,MAAM,GAAG,GAAG,EAAE,KAAK;AACpC,UAAM,MAAM,KAAK,MAAM,MAAM,CAAC,EAAE,KAAK;AACrC,QAAI,QAAQ,KAAK;AACf,YAAM,IAAI,OAAO,GAAG;AACpB,UAAI,OAAO,SAAS,CAAC,EAAG,KAAI,KAAK;AAAA,IACnC,WAAW,QAAQ,MAAM;AACvB,UAAI,KAAK,KAAK,GAAG;AAAA,IACnB;AAAA,EACF;AACA,MAAI,IAAI,OAAO,UAAa,IAAI,KAAK,WAAW,EAAG,QAAO;AAC1D,SAAO,EAAE,GAAG,IAAI,IAAI,MAAM,IAAI,KAAK;AACrC;AAUO,SAAS,sBACd,SACA,iBACA,QACA,UAA+B,CAAC,GACvB;AACT,QAAM,SAAS,2BAA2B,eAAe;AACzD,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,YAAY,QAAQ,oBAAoB;AAC9C,QAAM,MAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACvD,MAAI,KAAK,IAAI,MAAM,OAAO,CAAC,IAAI,UAAW,QAAO;AACjD,QAAM,WAAW,WAAW,UAAU,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,OAAO,EAAE,EAAE,OAAO,KAAK;AAC3F,QAAM,cAAc,OAAO,KAAK,UAAU,MAAM;AAChD,aAAW,OAAO,OAAO,MAAM;AAC7B,UAAM,SAAS,OAAO,KAAK,KAAK,MAAM;AACtC,QAAI,OAAO,WAAW,YAAY,OAAQ;AAC1C,QAAI,gBAAgB,QAAQ,WAAW,EAAG,QAAO;AAAA,EACnD;AACA,SAAO;AACT;AAgBO,SAAS,qBACd,SACA,iBACA,iBACA,QACA,UAA8B,CAAC,GACtB;AACT,MAAI,CAAC,gBAAgB,WAAW,KAAK,EAAG,QAAO;AAC/C,QAAM,KAAK,OAAO,eAAe;AACjC,MAAI,CAAC,OAAO,SAAS,EAAE,EAAG,QAAO;AACjC,QAAM,YAAY,QAAQ,oBAAoB;AAC9C,QAAM,MAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACvD,MAAI,KAAK,IAAI,MAAM,EAAE,IAAI,UAAW,QAAO;AAC3C,QAAM,WAAW,QAAQ,WAAW,UAAU,MAAM,EAAE,OAAO,MAAM,EAAE,IAAI,OAAO,EAAE,EAAE,OAAO,KAAK;AAChG,QAAM,cAAc,OAAO,KAAK,UAAU,MAAM;AAChD,QAAM,SAAS,OAAO,KAAK,iBAAiB,MAAM;AAClD,MAAI,OAAO,WAAW,YAAY,OAAQ,QAAO;AACjD,SAAO,gBAAgB,QAAQ,WAAW;AAC5C;AAqBO,SAAS,oBACd,SACA,iBACA,QACA,UAAoC,CAAC,GAC5B;AACT,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,SAAS,QAAQ,mBAAmB;AAC1C,QAAM,QAAQ,QAAQ,gBAAgB;AACtC,MAAI,YAAY;AAChB,MAAI,QAAQ;AACV,QAAI,CAAC,UAAU,WAAW,MAAM,EAAG,QAAO;AAC1C,gBAAY,UAAU,MAAM,OAAO,MAAM;AAAA,EAC3C;AACA,MAAI,MAAO,aAAY,UAAU,YAAY;AAC7C,QAAM,WAAW,WAAW,WAAW,MAAM,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC3E,QAAM,cAAc,OAAO,KAAK,UAAU,MAAM;AAChD,QAAM,SAAS,OAAO,KAAK,WAAW,MAAM;AAC5C,MAAI,OAAO,WAAW,YAAY,OAAQ,QAAO;AACjD,SAAO,gBAAgB,QAAQ,WAAW;AAC5C;AAmCO,SAAS,sBACd,OAMA,UAA+B,CAAC,GACvB;AACT,MAAI,CAAC,MAAM,WAAW;AACpB,WAAO,QAAQ,6BAA6B;AAAA,EAC9C;AACA,QAAM,YAAY,MAAM;AACxB,MAAI,CAAC,aAAa,MAAM,QAAQ,SAAS,EAAG,QAAO;AACnD,MAAI,CAAC,MAAM,QAAS,QAAO;AAE3B,QAAM,OAAO,QAAQ,cAAc,OAC/B,MAAM,WAAW,QAAQ,WAAW,MACpC,OAAO,KAAK,MAAM,UAAU,CAAC,CAAC,EAC3B,KAAK,EACL,OAAO,CAAC,KAAK,QAAQ,MAAM,OAAO,MAAM,OAAQ,GAAG,KAAK,KAAK,MAAM,OAAO;AAEjF,QAAM,WAAW,WAAW,QAAQ,MAAM,SAAS,EAAE,OAAO,IAAI,EAAE,OAAO,QAAQ;AACjF,QAAM,cAAc,OAAO,KAAK,QAAQ;AACxC,QAAM,SAAS,OAAO,KAAK,SAAS;AACpC,MAAI,YAAY,WAAW,OAAO,OAAQ,QAAO;AACjD,SAAO,gBAAgB,aAAa,MAAM;AAC5C;AAQO,SAAS,YACd,SACA,MACoB;AACpB,QAAM,IAAI,QAAQ,IAAI,KACjB,QAAQ,KAAK,YAAY,CAAC,KAC1B,OAAO,QAAQ,OAAO,EAAE,KAAK,CAAC,CAAC,GAAG,MAAM,IAAI,YAAY,MAAM,KAAK,YAAY,CAAC,IAAI,CAAC;AAC1F,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,CAAC;AAChC,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/connectors/webhooks.ts"],"sourcesContent":["/**\n * Inbound webhook signature verifiers — provider-specific HMAC schemes.\n *\n * Each signature scheme is a pure function:\n * (rawBody: string, headers, secret, now?) → boolean\n *\n * Constant-time comparison via `crypto.timingSafeEqual`. Timestamps are\n * checked against a configurable tolerance to bound replay risk; the default\n * mirrors the upstream provider's documented window (Stripe: 5 min, Slack: 5 min).\n *\n * These verifiers are the building blocks for any inbound-webhook receiver\n * (a route + a `verify` call + a per-event handler). They live in this\n * package so every consumer of the integration substrate gets correct\n * verification — not just one product reimplementing it.\n */\n\nimport { createHmac, timingSafeEqual } from 'node:crypto'\n\n/** Default replay-protection window. Providers commonly use 5 minutes. */\nexport const DEFAULT_SIGNATURE_TOLERANCE_SECONDS = 5 * 60\n\n// ─── Stripe ─────────────────────────────────────────────────────────────\n//\n// Stripe signs webhooks with a single header `Stripe-Signature` of the form\n//\n// t=<timestamp>,v1=<sig1>,v1=<sig2>,...\n//\n// where `t` is the Unix timestamp the event was generated, and each `v1`\n// is `HMAC-SHA256(secret, \"<t>.<rawBody>\")`. Multiple `v1` entries appear\n// during secret rotation — any one matching is sufficient.\n//\n// https://stripe.com/docs/webhooks/signatures\n\nexport interface ParsedStripeSignatureHeader {\n t: number\n sigs: string[]\n}\n\nexport function parseStripeSignatureHeader(header: string): ParsedStripeSignatureHeader | null {\n const acc: { ts?: number; sigs: string[] } = { sigs: [] }\n for (const part of header.split(',')) {\n const idx = part.indexOf('=')\n if (idx < 0) continue\n const key = part.slice(0, idx).trim()\n const val = part.slice(idx + 1).trim()\n if (key === 't') {\n const n = Number(val)\n if (Number.isFinite(n)) acc.ts = n\n } else if (key === 'v1') {\n acc.sigs.push(val)\n }\n }\n if (acc.ts === undefined || acc.sigs.length === 0) return null\n return { t: acc.ts, sigs: acc.sigs }\n}\n\nexport interface StripeVerifyOptions {\n /** Replay-protection window in seconds. Default 300. */\n toleranceSeconds?: number\n /** Override `now()` for tests. UTC seconds. */\n now?: number\n}\n\n/** Verify a Stripe webhook signature against the raw request body. */\nexport function verifyStripeSignature(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options: StripeVerifyOptions = {},\n): boolean {\n const parsed = parseStripeSignatureHeader(signatureHeader)\n if (!parsed) return false\n const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS\n const now = options.now ?? Math.floor(Date.now() / 1000)\n if (Math.abs(now - parsed.t) > tolerance) return false\n const expected = createHmac('sha256', secret).update(`${parsed.t}.${rawBody}`).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const sig of parsed.sigs) {\n const sigBuf = Buffer.from(sig, 'utf8')\n if (sigBuf.length !== expectedBuf.length) continue\n if (timingSafeEqual(sigBuf, expectedBuf)) return true\n }\n return false\n}\n\n// ─── Slack ──────────────────────────────────────────────────────────────\n//\n// Slack signs request bodies with two headers:\n//\n// X-Slack-Signature: v0=<HMAC-SHA256(secret, \"v0:<ts>:<body>\")>\n// X-Slack-Request-Timestamp: <ts>\n//\n// https://api.slack.com/authentication/verifying-requests-from-slack\n\nexport interface SlackVerifyOptions {\n toleranceSeconds?: number\n now?: number\n}\n\nexport function verifySlackSignature(\n rawBody: string,\n signatureHeader: string,\n timestampHeader: string,\n secret: string,\n options: SlackVerifyOptions = {},\n): boolean {\n if (!signatureHeader.startsWith('v0=')) return false\n const ts = Number(timestampHeader)\n if (!Number.isFinite(ts)) return false\n const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS\n const now = options.now ?? Math.floor(Date.now() / 1000)\n if (Math.abs(now - ts) > tolerance) return false\n const expected = 'v0=' + createHmac('sha256', secret).update(`v0:${ts}:${rawBody}`).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n const sigBuf = Buffer.from(signatureHeader, 'utf8')\n if (sigBuf.length !== expectedBuf.length) return false\n return timingSafeEqual(sigBuf, expectedBuf)\n}\n\n// ─── Generic HMAC ───────────────────────────────────────────────────────\n//\n// For \"we shipped a webhook URL with a shared HMAC secret\" patterns —\n// covers any custom integration where the operator picks the message\n// format. The signed message is the literal `rawBody` (no timestamp\n// prefix); replay protection is the caller's responsibility (use a\n// nonce header + a small dedup cache).\n\nexport interface GenericHmacVerifyOptions {\n /** sha256 (default) | sha1 | sha512 — matches the algorithm the receiver\n * computed at sign time. */\n algorithm?: 'sha256' | 'sha1' | 'sha512'\n /** Optional prefix the receiver prepends to the signature in the header\n * (e.g., `'sha256='`). Stripped before constant-time comparison. */\n signaturePrefix?: string\n /** Lowercase comparison (most providers emit hex-lowercase). Default true. */\n lowercaseHex?: boolean\n}\n\nexport function verifyHmacSignature(\n rawBody: string,\n signatureHeader: string,\n secret: string,\n options: GenericHmacVerifyOptions = {},\n): boolean {\n const algorithm = options.algorithm ?? 'sha256'\n const prefix = options.signaturePrefix ?? ''\n const lower = options.lowercaseHex ?? true\n let candidate = signatureHeader\n if (prefix) {\n if (!candidate.startsWith(prefix)) return false\n candidate = candidate.slice(prefix.length)\n }\n if (lower) candidate = candidate.toLowerCase()\n const expected = createHmac(algorithm, secret).update(rawBody).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n const sigBuf = Buffer.from(candidate, 'utf8')\n if (sigBuf.length !== expectedBuf.length) return false\n return timingSafeEqual(sigBuf, expectedBuf)\n}\n\n// ─── Twilio ─────────────────────────────────────────────────────────────\n//\n// Twilio's webhook signature scheme is unlike Stripe/Slack — it doesn't\n// sign the raw body. It signs the concatenation of:\n//\n// fullUrl + sortedConcatenatedParams\n//\n// where `fullUrl` is the public URL Twilio called (scheme + host + path\n// + query, exactly as Twilio constructed it from your console config),\n// and `sortedConcatenatedParams` is `key1+value1+key2+value2+...` over\n// the alphabetically-sorted keys of the POST body params (form-encoded).\n//\n// For JSON-bodied Twilio webhooks (Conversations API), the body is signed\n// as raw bytes — pass `{ bodyAsRaw: true, rawBody }` for that path.\n//\n// HMAC-SHA1, base64-encoded. Header: `X-Twilio-Signature`.\n//\n// https://www.twilio.com/docs/usage/webhooks/webhooks-security\n\nexport interface TwilioVerifyOptions {\n /** Skip verification when the auth token isn't configured. Useful in\n * dev where the receiver wants to accept any payload. Default `false`\n * — production should always require a configured token. */\n skipWhenAuthTokenMissing?: boolean\n /** When true, sign the raw body instead of the URL-encoded sorted-params\n * reduction. Twilio uses raw-body signing for `application/json`\n * webhook bodies. Default `false`. */\n bodyAsRaw?: boolean\n /** When `bodyAsRaw` is true, the raw body to sign. Ignored otherwise. */\n rawBody?: string\n}\n\n/** Verify a Twilio webhook signature. */\nexport function verifyTwilioSignature(\n input: {\n authToken: string | null | undefined\n signatureHeader: string | string[] | undefined\n fullUrl: string | null | undefined\n params: Record<string, string> | undefined\n },\n options: TwilioVerifyOptions = {},\n): boolean {\n if (!input.authToken) {\n return options.skipWhenAuthTokenMissing === true\n }\n const signature = input.signatureHeader\n if (!signature || Array.isArray(signature)) return false\n if (!input.fullUrl) return false\n\n const data = options.bodyAsRaw === true\n ? input.fullUrl + (options.rawBody ?? '')\n : Object.keys(input.params ?? {})\n .sort()\n .reduce((acc, key) => acc + key + (input.params![key] ?? ''), input.fullUrl)\n\n const expected = createHmac('sha1', input.authToken).update(data).digest('base64')\n const expectedBuf = Buffer.from(expected)\n const sigBuf = Buffer.from(signature)\n if (expectedBuf.length !== sigBuf.length) return false\n return timingSafeEqual(expectedBuf, sigBuf)\n}\n\n// ─── Inbound email ──────────────────────────────────────────────────────\n//\n// Tangle's own inbound-email edge (a Cloudflare Email Routing worker) receives\n// a message, serializes the parsed MIME to a JSON body, and signs it with the\n// connection's per-connection secret before POSTing it to the hub. There is no\n// upstream mail-vendor signature to trust — WE are both signer and verifier —\n// so the scheme is a timestamped HMAC (Slack/Stripe-style) to get replay\n// protection on the captured webhook:\n//\n// X-Tangle-Email-Signature: <HMAC-SHA256(secret, \"<ts>.<rawBody>\") hex>\n// X-Tangle-Email-Timestamp: <unix seconds>\n\nexport interface EmailVerifyOptions {\n /** Replay-protection window in seconds. Default 300. */\n toleranceSeconds?: number\n /** Override `now()` for tests. UTC seconds. */\n now?: number\n}\n\n/** Verify a Tangle inbound-email signature against the raw request body. */\nexport function verifyEmailSignature(\n rawBody: string,\n signatureHeader: string,\n timestampHeader: string,\n secret: string,\n options: EmailVerifyOptions = {},\n): boolean {\n const ts = Number(timestampHeader)\n if (!Number.isFinite(ts)) return false\n const tolerance = options.toleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS\n const now = options.now ?? Math.floor(Date.now() / 1000)\n if (Math.abs(now - ts) > tolerance) return false\n const expected = createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')\n const expectedBuf = Buffer.from(expected, 'utf8')\n const sigBuf = Buffer.from(signatureHeader.toLowerCase(), 'utf8')\n if (sigBuf.length !== expectedBuf.length) return false\n return timingSafeEqual(sigBuf, expectedBuf)\n}\n\n// ─── Header helper ──────────────────────────────────────────────────────\n//\n// Most fastify/express adapters expose request headers as\n// `Record<string, string | string[] | undefined>`. This helper picks the\n// first canonical value for a given name (case-insensitive).\n\nexport function firstHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string,\n): string | undefined {\n const v = headers[name]\n ?? headers[name.toLowerCase()]\n ?? Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1]\n if (Array.isArray(v)) return v[0]\n return typeof v === 'string' ? v : undefined\n}\n"],"mappings":"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","names":[]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-IA5GCKM6.js";
|
|
9
9
|
import {
|
|
10
10
|
adapters_exports
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EV7PFUCM.js";
|
|
12
12
|
|
|
13
13
|
// src/activepieces-catalog.ts
|
|
14
14
|
import { readFileSync } from "fs";
|
|
@@ -4334,4 +4334,4 @@ export {
|
|
|
4334
4334
|
signCapability,
|
|
4335
4335
|
verifyCapabilityToken
|
|
4336
4336
|
};
|
|
4337
|
-
//# sourceMappingURL=chunk-
|
|
4337
|
+
//# sourceMappingURL=chunk-XECSOZRL.js.map
|
|
@@ -539,8 +539,8 @@ import {
|
|
|
539
539
|
zohoDeskConnector,
|
|
540
540
|
zoominfoConnector,
|
|
541
541
|
zuoraConnector
|
|
542
|
-
} from "../../chunk-
|
|
543
|
-
import "../../chunk-
|
|
542
|
+
} from "../../chunk-EV7PFUCM.js";
|
|
543
|
+
import "../../chunk-XC54EOUQ.js";
|
|
544
544
|
import {
|
|
545
545
|
DEFAULT_TANGLE_PLATFORM_URL,
|
|
546
546
|
TANGLE_API_KEY_PREFIX,
|
|
@@ -177,6 +177,14 @@ declare function verifyTwilioSignature(input: {
|
|
|
177
177
|
fullUrl: string | null | undefined;
|
|
178
178
|
params: Record<string, string> | undefined;
|
|
179
179
|
}, options?: TwilioVerifyOptions): boolean;
|
|
180
|
+
interface EmailVerifyOptions {
|
|
181
|
+
/** Replay-protection window in seconds. Default 300. */
|
|
182
|
+
toleranceSeconds?: number;
|
|
183
|
+
/** Override `now()` for tests. UTC seconds. */
|
|
184
|
+
now?: number;
|
|
185
|
+
}
|
|
186
|
+
/** Verify a Tangle inbound-email signature against the raw request body. */
|
|
187
|
+
declare function verifyEmailSignature(rawBody: string, signatureHeader: string, timestampHeader: string, secret: string, options?: EmailVerifyOptions): boolean;
|
|
180
188
|
declare function firstHeader(headers: Record<string, string | string[] | undefined>, name: string): string | undefined;
|
|
181
189
|
|
|
182
|
-
export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, type ExchangeCodeInput, type GenericHmacVerifyOptions, InMemoryOAuthFlowStore, type OAuthFlowStore, type OAuthTokens, type ParsedStripeSignatureHeader, type PendingOAuthFlow, type RefreshInput, type SlackVerifyOptions, type StartOAuthInput, type StartOAuthOutput, type StripeVerifyOptions, type TwilioVerifyOptions, _resetPendingFlowsForTests, consumePendingFlow, exchangeAuthorizationCode, firstHeader, parseStripeSignatureHeader, refreshAccessToken, startOAuthFlow, verifyHmacSignature, verifySlackSignature, verifyStripeSignature, verifyTwilioSignature };
|
|
190
|
+
export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, type EmailVerifyOptions, type ExchangeCodeInput, type GenericHmacVerifyOptions, InMemoryOAuthFlowStore, type OAuthFlowStore, type OAuthTokens, type ParsedStripeSignatureHeader, type PendingOAuthFlow, type RefreshInput, type SlackVerifyOptions, type StartOAuthInput, type StartOAuthOutput, type StripeVerifyOptions, type TwilioVerifyOptions, _resetPendingFlowsForTests, consumePendingFlow, exchangeAuthorizationCode, firstHeader, parseStripeSignatureHeader, refreshAccessToken, startOAuthFlow, verifyEmailSignature, verifyHmacSignature, verifySlackSignature, verifyStripeSignature, verifyTwilioSignature };
|
package/dist/connectors/index.js
CHANGED
|
@@ -546,16 +546,17 @@ import {
|
|
|
546
546
|
zohoDeskConnector,
|
|
547
547
|
zoominfoConnector,
|
|
548
548
|
zuoraConnector
|
|
549
|
-
} from "../chunk-
|
|
549
|
+
} from "../chunk-EV7PFUCM.js";
|
|
550
550
|
import {
|
|
551
551
|
DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
|
|
552
552
|
firstHeader,
|
|
553
553
|
parseStripeSignatureHeader,
|
|
554
|
+
verifyEmailSignature,
|
|
554
555
|
verifyHmacSignature,
|
|
555
556
|
verifySlackSignature,
|
|
556
557
|
verifyStripeSignature,
|
|
557
558
|
verifyTwilioSignature
|
|
558
|
-
} from "../chunk-
|
|
559
|
+
} from "../chunk-XC54EOUQ.js";
|
|
559
560
|
import {
|
|
560
561
|
CredentialsExpired,
|
|
561
562
|
DEFAULT_TANGLE_PLATFORM_URL,
|
|
@@ -1091,6 +1092,7 @@ export {
|
|
|
1091
1092
|
vapiConnector,
|
|
1092
1093
|
vboutConnector,
|
|
1093
1094
|
vercelConnector,
|
|
1095
|
+
verifyEmailSignature,
|
|
1094
1096
|
verifyHmacSignature,
|
|
1095
1097
|
verifySlackSignature,
|
|
1096
1098
|
verifyStripeSignature,
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { A as AuthSpec, b as CASStrategy, c as Capability, d as CapabilityClass,
|
|
|
11
11
|
export { b as IntegrationErrorCode, I as IntegrationRuntimeError, a as IntegrationUserAction, N as NormalizedIntegrationError, n as normalizeIntegrationError, s as statusForCode } from './errors-Bg3_rxnQ.js';
|
|
12
12
|
export { ConnectFlowOptions, FinishConnectInput, FinishConnectOutput, InMemoryConnectStateStore, StartConnectInput, StartConnectOutput, finishConnectFlow, revokeConnectFlow, startConnectFlow } from './connect/index.js';
|
|
13
13
|
export { ExpressLikeRequest, ExpressLikeResponse, HonoLikeContext, RequireTangleAuthOptions, TangleAuthContext, TangleAuthOutcome, TangleAuthReason, expressTangleAuthMiddleware, extractToken, honoTangleAuthMiddleware, requireTangleAuth } from './middleware/index.js';
|
|
14
|
-
export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, ExchangeCodeInput, GenericHmacVerifyOptions, InMemoryOAuthFlowStore, OAuthFlowStore, OAuthTokens, ParsedStripeSignatureHeader, PendingOAuthFlow, RefreshInput, SlackVerifyOptions, StartOAuthInput, StartOAuthOutput, StripeVerifyOptions, TwilioVerifyOptions, _resetPendingFlowsForTests, consumePendingFlow, exchangeAuthorizationCode, firstHeader, parseStripeSignatureHeader, refreshAccessToken, startOAuthFlow, verifyHmacSignature, verifySlackSignature, verifyStripeSignature, verifyTwilioSignature } from './connectors/index.js';
|
|
14
|
+
export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, EmailVerifyOptions, ExchangeCodeInput, GenericHmacVerifyOptions, InMemoryOAuthFlowStore, OAuthFlowStore, OAuthTokens, ParsedStripeSignatureHeader, PendingOAuthFlow, RefreshInput, SlackVerifyOptions, StartOAuthInput, StartOAuthOutput, StripeVerifyOptions, TwilioVerifyOptions, _resetPendingFlowsForTests, consumePendingFlow, exchangeAuthorizationCode, firstHeader, parseStripeSignatureHeader, refreshAccessToken, startOAuthFlow, verifyEmailSignature, verifyHmacSignature, verifySlackSignature, verifyStripeSignature, verifyTwilioSignature } from './connectors/index.js';
|
|
15
15
|
export { DocuSealOptions, EtsyOptions, GmailOptions, GoogleCalendarOptions, GoogleDocsOptions, GoogleDriveOptions, GoogleErrorClass, GoogleFormsOptions, GoogleSheetsOptions, HelloSignOptions, HubSpotOptions, MicrosoftCalendarOptions, MicrosoftGraphOptions, MicrosoftTeamsOptions, NotionOptions, OneDriveOptions, OutlookMailOptions, PandaDocOptions, RestConnectorSpec, RestCredentialPlacement, RestOperationSpec, RestRequestSpec, SharePointOptions, SlackOptions, TELEGRAM_FILE_DOWNLOAD_ROOT, TwitterOptions, WhatsappBusinessOptions, activecampaignConnector, acumbamailConnector, adobeCreativeCloudConnector, adpConnector, afforaiConnector, agentxConnector, aidbaseConnector, aipriseConnector, airOpsConnector, aircallConnector, airtableConnector, airtopConnector, alaiConnector, altTextAiConnector, alttextifyConnector, amazonBedrockConnector, amazonSecretsManagerConnector, amazonSesConnector, amazonSnsConnector, amazonSqsConnector, amazonTextractConnector, aminosConnector, ampecoConnector, amplemarketConnector, anthropicConnector, apitableConnector, apitemplateIoConnector, apolloConnector, appfollowConnector, asanaConnector, ashbyConnector, asknewsConnector, assemblyaiConnector, attioConnector, auth0Connector, autoboundConnector, autocallsConnector, avianConnector, avomaConnector, azureAdConnector, azureCommunicationServicesConnector, azureOpenaiConnector, backblazeConnector, bamboohrConnector, barcodeLookupConnector, baremetricsConnector, basecampConnector, beamerConnector, bettercontactConnector, bettermodeConnector, bexioConnector, bigcommerceConnector, biginByZohoConnector, billplzConnector, bitlyConnector, blandAiConnector, blueskyConnector, bolnaConnector, bonjoroConnector, bookedinConnector, boxConnector, braveSearchConnector, brazeConnector, brightdataConnector, brilliantDirectoriesConnector, browseAiConnector, builtwithConnector, calComConnector, calendlyConnector, campaignMonitorConnector, cannyConnector, canvaConnector, capsuleCrmConnector, captainDataConnector, cashfreePaymentsConnector, certopusConnector, chainalysisApiConnector, chargebeeConnector, chargekeepConnector, chartlyConnector, chatDataConnector, chatbaseConnector, chatlingConnector, chatnodeConnector, chatwootConnector, checkoutConnector, circleConnector, classifyGoogleError, classifyGoogleReason, clayConnector, clearoutConnector, clicdataConnector, clickupConnector, clioConnector, clockifyConnector, clockodoConnector, closeConnector, cloudconvertConnector, cloudinaryConnector, cloutlyConnector, codaConnector, cognismConnector, cognitoFormsConnector, cohereConnector, cometapiConnector, comfyicuConnector, commonRoomConnector, confluenceConnector, constantContactConnector, contactoutConnector, contentfulConnector, convertkitConnector, copperConnector, copyAiConnector, crispConnector, crustdataConnector, cryptolensConnector, customerIoConnector, customgptConnector, dashworksConnector, datadogConnector, datafuelConnector, datocmsConnector, declarativeRestConnector, deepseekConnector, demandbaseConnector, denserAiConnector, descriptConnector, devinConnector, dialpadConnector, discordConnector, dittofeedConnector, doctlyConnector, documentproConnector, documergeConnector, docuseal, docusignConnector, dropboxConnector, dubConnector, dumplingAiConnector, dustConnector, easyPeasyAiConnector, ebayConnector, edenAiConnector, elasticEmailConnector, emailitConnector, emailoctopusConnector, enrichlayerConnector, esignaturesConnector, etsyConnector, exaConnector, facebookLeadsConnector, facebookPagesConnector, factorsAiConnector, fathomAnalyticsConnector, fathomConnector, figjamConnector, figmaConnector, filloutFormsConnector, findymailConnector, firebaseConnector, fireberryConnector, firecrawlConnector, firefliesAiConnector, flipandoConnector, flowiseConnector, flowluConnector, folkConnector, foreplayCoConnector, formbricksConnector, formstackConnector, fountainConnector, fragmentConnector, frameConnector, freeAgentConnector, freshsalesConnector, freshserviceConnector, frontConnector, fullenrichConnector, gameballConnector, geminiConnector, getresponseConnector, ghostcmsConnector, gistlyConnector, giteaConnector, githubConnector, gitlabConnector, glideConnector, gmail, gongConnector, googleAnalyticsConnector, googleApiError, googleBigqueryConnector, googleCalendar, googleCloudStorageConnector, googleContactsConnector, googleDocs, googleDrive, googleErrorReason, googleForms, googleGeminiConnector, googleMeetConnector, googleMyBusinessConnector, googleSearchConnector, googleSearchConsoleConnector, googleSheets, googleSlidesConnector, googleTasksConnector, googleTestFailureReason, googleVertexaiConnector, googlechatConnector, gorgiasConnector, gotifyConnector, gptzeroDetectAiConnector, granolaConnector, greenhouseConnector, greenptConnector, greipConnector, griptapeConnector, gristConnector, guideliteConnector, gustoConnector, harvestConnector, hashiCorpVaultConnector, hastewireConnector, heartbeatConnector, hedyConnector, hellosign, helpscoutConnector, heygenConnector, heymarketSmsConnector, heyreachConnector, hightouchConnector, housecallProConnector, hubspot, huggingFaceConnector, huggingfaceConnector, humeAiConnector, hunterConnector, hystructConnector, ibmCognoseConnector, imageRouterConnector, imapConnector, influencersClubConnector, insightlyConnector, insightoAiConnector, instaChartsConnector, instantlyAiConnector, instasentConnector, intercomConnector, invoiceninjaConnector, jinaAiConnector, jiraCloudConnector, jiraDataCenterConnector, joggAiConnector, jotformConnector, justInvoiceConnector, justcallConnector, kallabotAiConnector, kapsoConnector, kimaiConnector, kissflowConnector, kizeoFormsConnector, klaviyoConnector, klentyConnector, knackConnector, knockConnector, koFiConnector, kommoConnector, kudosityConnector, kustomerConnector, lagrowthmachineConnector, leadConnectorConnector, leadfeederConnector, leadmagicConnector, leapAiConnector, leexiConnector, lemlistConnector, lemonSqueezyConnector, letsCalendarConnector, lettaConnector, leverConnector, lightfunnelsConnector, linearConnector, linkaConnector, linkupConnector, llmrailsConnector, lobstermailConnector, localaiConnector, loftyConnector, logrocketConnector, logsnagConnector, lokaliseConnector, loopsConnector, lucidyaConnector, lushaConnector, luxuryPresenceConnector, magicalApiConnector, mailchainConnector, mailchimpConnector, mailerLiteConnector, mailercheckConnector, mailerooConnector, mailgunConnector, makeConnector, manychatConnector, marketoConnector, mastodonConnector, matomoConnector, matrixConnector, mattermostConnector, mauticConnector, medullarConnector, meetgeekAiConnector, memConnector, messagebirdConnector, metabaseConnector, microsoft365PeopleConnector, microsoft365PlannerConnector, microsoftCalendar, microsoftDynamics365BusinessCentralConnector, microsoftDynamicsCrmConnector, microsoftExcel365Connector, microsoftGraph, microsoftOnenoteConnector, microsoftPowerBiConnector, microsoftTeams, microsoftTodoConnector, millionverifierConnector, mindStudioConnector, mindeeConnector, miroConnector, missiveConnector, mixmaxConnector, mixpanelConnector, modjoConnector, mollieConnector, mondayConnector, mongodbConnector, n8nConnector, netlifyConnector, niftyConnector, ninjapipeConnector, nocodbConnector, nooksConnector, notion, notionConnector, ntfyConnector, oceanIoConnector, odooConnector, omniCoConnector, omnihrConnector, oneDrive, openPhoneConnector, openRouterConnector, openaiConnector, openmicAiConnector, opnformConnector, opportifyConnector, opsgenieConnector, oracleDatabaseConnector, orimonConnector, outlookMail, outreachConnector, paddleConnector, pagerdutyConnector, pandadoc, paperformConnector, parseurConnector, pastefyConnector, paychexConnector, paywhirlConnector, pdfmonkeyConnector, peekshotConnector, perplexityAiConnector, personalAiConnector, phantombusterConnector, phonyConnector, pinchPaymentsConnector, pineconeConnector, pipedreamConnector, pipedriveConnector, placidConnector, plausibleConnector, pocketbaseConnector, pollybotAiConnector, polytomicConnector, postgresConnector, posthogConnector, postizConnector, postmarkConnector, predictLeadsConnector, productboardConnector, proxycurlConnector, pushbulletConnector, pushoverConnector, qawafelConnector, qdrantConnector, quadernoConnector, quickbooksConnector, quickzuConnector, raiaAiConnector, rapidtextAiConnector, reachinboxConnector, recallAiConnector, redditConnector, reoonVerifierConnector, replyIoConnector, retableConnector, retellAiConnector, ringcentralConnector, ripplingConnector, robollyConnector, runwareConnector, saasticConnector, saleorConnector, salesforceConnector, saleshandyConnector, salesloftConnector, sanityConnector, sardisConnector, savvycalConnector, scrapegraphaiConnector, scrapelessConnector, seamlessAiConnector, seekTableConnector, semrushConnector, sendgridConnector, sendinblueConnector, sendpulseConnector, senjaConnector, sentryConnector, serperConnector, serpstatConnector, serviceNowConnector, sharepoint, shippoConnector, shopifyConnector, shortIoConnector, signNowConnector, signrequestConnector, signwellConnector, similarwebConnector, simplirouteConnector, simplybookmeConnector, skyprepConnector, slack, slackEventsConnector, smartleadConnector, smartsheetConnector, smartsuiteConnector, smooveConnector, smsmodeConnector, snovIoConnector, snowflakeConnector, socialkitConnector, squareConnector, stripeConnector, stripePackConnector, stripeWebhookReceiverConnector, supabaseConnector, supadataConnector, swarmnodeConnector, systemeIoConnector, tableauConnector, talkableConnector, tallyConnector, teableConnector, telegramConnector, telnyxConnector, tenzoConnector, textcortexAiConnector, theirstackConnector, ticktickConnector, timeOpsConnector, timelinesAiConnector, tlDvConnector, todoistConnector, togglTrackConnector, totalcmsConnector, trelloConnector, twentyConnector, twilioConnector, twilioSmsConnector, twinLabsConnector, twitter, twitterConnector, typeformConnector, typefullyConnector, umamiConnector, upgradechatConnector, uscreenConnector, usergemsConnector, validatedmailsConnector, vapiConnector, vboutConnector, vercelConnector, videoaskConnector, vidlab7Connector, vlmRunConnector, voucheryIoConnector, vtexConnector, vtigerConnector, wafeqConnector, warmlyConnector, weaviateConnector, webflowConnector, webhookConnector, webscrapingAiConnector, whatConvertsConnector, whatsappBusiness, whatsappConnector, whatsscaleConnector, wizaConnector, wonderchatConnector, woocommerceConnector, woodpeckerConnector, wootricConnector, wordpressConnector, workableConnector, workdayConnector, wrikeConnector, writesonicBulkConnector, wufooConnector, xeroConnector, youcanbookmeConnector, youtubeDataConnector, zagomailConnector, zapierConnector, zendeskConnector, zendeskSellConnector, zenrowsConnector, zeplinConnector, zerobounceConnector, zohoBookingsConnector, zohoCrmConnector, zohoDeskConnector, zoominfoConnector, zuoraConnector } from './connectors/adapters/index.js';
|
|
16
16
|
export { D as DEFAULT_TANGLE_PLATFORM_URL, d as TANGLE_API_KEY_PREFIX, e as TANGLE_SERVICE_TOKEN_PREFIX, b as TangleIdentityClient, T as TangleIdentityOptions, f as TangleIdentityUnreachableError, c as TangleTokenVerifyFailure, g as TangleTokenVerifyResult, a as TangleUserSummary, h as TangleWorkspaceSummary, i as createTangleIdentityClient, t as tangleIdentity } from './tangle-id-_uZ3ywCF.js';
|
|
17
17
|
export { IntegrationCatalogView, IntegrationToolDefinition, IntegrationToolSearchFilters, IntegrationToolSearchResult, McpToolDefinition, buildIntegrationCatalogView, buildIntegrationToolCatalog, describeIntegrationTool, flattenIntegrationToolDefinition, integrationToolName, parseIntegrationToolName, searchIntegrationTools, toMcpTools } from './catalog.js';
|
package/dist/index.js
CHANGED
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
validateIntegrationManifest,
|
|
120
120
|
validateProviderPassthroughRequest,
|
|
121
121
|
verifyCapabilityToken
|
|
122
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-XECSOZRL.js";
|
|
123
123
|
import {
|
|
124
124
|
InMemoryConnectStateStore,
|
|
125
125
|
finishConnectFlow,
|
|
@@ -714,16 +714,17 @@ import {
|
|
|
714
714
|
zohoDeskConnector,
|
|
715
715
|
zoominfoConnector,
|
|
716
716
|
zuoraConnector
|
|
717
|
-
} from "./chunk-
|
|
717
|
+
} from "./chunk-EV7PFUCM.js";
|
|
718
718
|
import {
|
|
719
719
|
DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
|
|
720
720
|
firstHeader,
|
|
721
721
|
parseStripeSignatureHeader,
|
|
722
|
+
verifyEmailSignature,
|
|
722
723
|
verifyHmacSignature,
|
|
723
724
|
verifySlackSignature,
|
|
724
725
|
verifyStripeSignature,
|
|
725
726
|
verifyTwilioSignature
|
|
726
|
-
} from "./chunk-
|
|
727
|
+
} from "./chunk-XC54EOUQ.js";
|
|
727
728
|
import {
|
|
728
729
|
CredentialsExpired,
|
|
729
730
|
DEFAULT_TANGLE_PLATFORM_URL,
|
|
@@ -1413,6 +1414,7 @@ export {
|
|
|
1413
1414
|
vboutConnector,
|
|
1414
1415
|
vercelConnector,
|
|
1415
1416
|
verifyCapabilityToken,
|
|
1417
|
+
verifyEmailSignature,
|
|
1416
1418
|
verifyHmacSignature,
|
|
1417
1419
|
verifySlackSignature,
|
|
1418
1420
|
verifyStripeSignature,
|
package/dist/mcp.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
IntegrationError,
|
|
3
3
|
createCatalogExecutorProvider,
|
|
4
4
|
importMcpConnector
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XECSOZRL.js";
|
|
6
6
|
import "./chunk-S2BUASSV.js";
|
|
7
7
|
import "./chunk-LA76XB7M.js";
|
|
8
8
|
import "./chunk-H4XYLS7T.js";
|
|
@@ -10,8 +10,8 @@ import "./chunk-VLVSP7SP.js";
|
|
|
10
10
|
import "./chunk-IA5GCKM6.js";
|
|
11
11
|
import "./chunk-7P2LN4VT.js";
|
|
12
12
|
import "./chunk-376UBTNB.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-EV7PFUCM.js";
|
|
14
|
+
import "./chunk-XC54EOUQ.js";
|
|
15
15
|
import "./chunk-UWF33ESC.js";
|
|
16
16
|
import "./chunk-PZ5AY32C.js";
|
|
17
17
|
|
package/dist/registry.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
composeIntegrationRegistry,
|
|
6
6
|
inferIntegrationSupportTier,
|
|
7
7
|
summarizeIntegrationRegistry
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-XECSOZRL.js";
|
|
9
9
|
import "./chunk-S2BUASSV.js";
|
|
10
10
|
import "./chunk-LA76XB7M.js";
|
|
11
11
|
import "./chunk-H4XYLS7T.js";
|
|
@@ -13,8 +13,8 @@ import "./chunk-VLVSP7SP.js";
|
|
|
13
13
|
import "./chunk-IA5GCKM6.js";
|
|
14
14
|
import "./chunk-7P2LN4VT.js";
|
|
15
15
|
import "./chunk-376UBTNB.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-EV7PFUCM.js";
|
|
17
|
+
import "./chunk-XC54EOUQ.js";
|
|
18
18
|
import "./chunk-UWF33ESC.js";
|
|
19
19
|
import "./chunk-PZ5AY32C.js";
|
|
20
20
|
export {
|
package/dist/runtime.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
InMemoryIntegrationGrantStore,
|
|
3
3
|
IntegrationRuntime,
|
|
4
4
|
createIntegrationRuntime
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XECSOZRL.js";
|
|
6
6
|
import "./chunk-S2BUASSV.js";
|
|
7
7
|
import "./chunk-LA76XB7M.js";
|
|
8
8
|
import "./chunk-H4XYLS7T.js";
|
|
@@ -10,8 +10,8 @@ import "./chunk-VLVSP7SP.js";
|
|
|
10
10
|
import "./chunk-IA5GCKM6.js";
|
|
11
11
|
import "./chunk-7P2LN4VT.js";
|
|
12
12
|
import "./chunk-376UBTNB.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-EV7PFUCM.js";
|
|
14
|
+
import "./chunk-XC54EOUQ.js";
|
|
15
15
|
import "./chunk-UWF33ESC.js";
|
|
16
16
|
import "./chunk-PZ5AY32C.js";
|
|
17
17
|
export {
|
package/dist/webhooks/index.d.ts
CHANGED
|
@@ -21,6 +21,39 @@ declare const stripeWebhookProvider: WebhookProvider;
|
|
|
21
21
|
declare const slackWebhookProvider: WebhookProvider;
|
|
22
22
|
/** DocuSeal webhook provider. Signature header `X-Docuseal-Signature`. */
|
|
23
23
|
declare const docusealWebhookProvider: WebhookProvider;
|
|
24
|
+
/** The normalized inbound-email payload the Tangle email edge posts and this
|
|
25
|
+
* provider surfaces on `WebhookEnvelope.payload`. `to` is the owner-resolution
|
|
26
|
+
* key — the platform matches it to a connection's provisioned inbound address;
|
|
27
|
+
* `messageId` is the idempotency key. */
|
|
28
|
+
interface InboundEmailPayload {
|
|
29
|
+
/** RFC5322 Message-ID. */
|
|
30
|
+
messageId?: string;
|
|
31
|
+
/** Envelope sender — the firm/customer that emailed in. */
|
|
32
|
+
from: string;
|
|
33
|
+
/** The provisioned inbound address the message was delivered to (owner key). */
|
|
34
|
+
to: string;
|
|
35
|
+
subject?: string;
|
|
36
|
+
text?: string;
|
|
37
|
+
html?: string;
|
|
38
|
+
/** Selected headers the edge forwards (Reply-To, In-Reply-To, References…). */
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
attachments?: Array<{
|
|
41
|
+
filename?: string;
|
|
42
|
+
contentType?: string;
|
|
43
|
+
/** Bytes as base64, OR a `url`/path the edge staged them at (large files). */
|
|
44
|
+
contentBase64?: string;
|
|
45
|
+
url?: string;
|
|
46
|
+
size?: number;
|
|
47
|
+
}>;
|
|
48
|
+
/** Epoch ms the edge received the message. */
|
|
49
|
+
receivedAt?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Tangle inbound-email provider. Our own Cloudflare Email Routing worker parses
|
|
52
|
+
* the MIME, JSON-serializes it, timestamped-HMAC signs it with the
|
|
53
|
+
* per-connection secret, and POSTs it here — there is no upstream mail-vendor
|
|
54
|
+
* signature, we are both signer and verifier. One inbound message → one
|
|
55
|
+
* `email.received` envelope; `payload.to` carries the owner-resolution address. */
|
|
56
|
+
declare const emailWebhookProvider: WebhookProvider;
|
|
24
57
|
/** Gmail push provider. Cloud Pub/Sub posts a JWT-signed envelope; the
|
|
25
58
|
* *payload* is base64 JSON describing the changed history range. The
|
|
26
59
|
* signature scheme here is the Pub/Sub JWT auth header — when supplied,
|
|
@@ -66,4 +99,4 @@ declare function genericHmacWebhookProvider(options: {
|
|
|
66
99
|
parse?: WebhookProvider['parse'];
|
|
67
100
|
}): WebhookProvider;
|
|
68
101
|
|
|
69
|
-
export { WebhookProvider, clayWebhookProvider, docusealWebhookProvider, gdriveWebhookProvider, genericHmacWebhookProvider, gmailWebhookProvider, hellosignWebhookProvider, slackWebhookProvider, stripeWebhookProvider, telegramWebhookProvider };
|
|
102
|
+
export { type InboundEmailPayload, WebhookProvider, clayWebhookProvider, docusealWebhookProvider, emailWebhookProvider, gdriveWebhookProvider, genericHmacWebhookProvider, gmailWebhookProvider, hellosignWebhookProvider, slackWebhookProvider, stripeWebhookProvider, telegramWebhookProvider };
|
package/dist/webhooks/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
firstHeader,
|
|
3
|
+
verifyEmailSignature,
|
|
3
4
|
verifyHmacSignature,
|
|
4
5
|
verifySlackSignature,
|
|
5
6
|
verifyStripeSignature
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-XC54EOUQ.js";
|
|
7
8
|
import "../chunk-PZ5AY32C.js";
|
|
8
9
|
|
|
9
10
|
// src/webhooks/router.ts
|
|
@@ -231,6 +232,35 @@ var docusealWebhookProvider = {
|
|
|
231
232
|
]
|
|
232
233
|
}
|
|
233
234
|
};
|
|
235
|
+
var emailWebhookProvider = {
|
|
236
|
+
id: "email",
|
|
237
|
+
verifySignature({ rawBody, headers, secret }) {
|
|
238
|
+
const sig = firstHeader(headers, "x-tangle-email-signature");
|
|
239
|
+
if (!sig) return { valid: false, reason: "missing_email_signature" };
|
|
240
|
+
const ts = firstHeader(headers, "x-tangle-email-timestamp");
|
|
241
|
+
if (!ts) return { valid: false, reason: "missing_email_timestamp" };
|
|
242
|
+
return verifyEmailSignature(rawBody, sig, ts, secret) ? { valid: true } : { valid: false, reason: "invalid_signature" };
|
|
243
|
+
},
|
|
244
|
+
parse({ rawBody, headers, now }) {
|
|
245
|
+
const msg = safeJson(rawBody);
|
|
246
|
+
if (!msg || typeof msg !== "object") return [];
|
|
247
|
+
if (typeof msg.from !== "string" || typeof msg.to !== "string") return [];
|
|
248
|
+
return [{
|
|
249
|
+
provider: "email",
|
|
250
|
+
eventType: "email.received",
|
|
251
|
+
providerEventId: typeof msg.messageId === "string" ? msg.messageId : void 0,
|
|
252
|
+
receivedAt: now ?? Date.now(),
|
|
253
|
+
payload: msg,
|
|
254
|
+
headers: normalizeHeaders(headers)
|
|
255
|
+
}];
|
|
256
|
+
},
|
|
257
|
+
eventCatalog: {
|
|
258
|
+
// We own the edge, so the inbound surface is exactly one closed event.
|
|
259
|
+
namespace: "email.",
|
|
260
|
+
closed: true,
|
|
261
|
+
events: [{ id: "email.received" }]
|
|
262
|
+
}
|
|
263
|
+
};
|
|
234
264
|
var gmailWebhookProvider = {
|
|
235
265
|
id: "gmail",
|
|
236
266
|
verifySignature({ headers, secret }) {
|
|
@@ -516,6 +546,7 @@ export {
|
|
|
516
546
|
WebhookRouter,
|
|
517
547
|
clayWebhookProvider,
|
|
518
548
|
docusealWebhookProvider,
|
|
549
|
+
emailWebhookProvider,
|
|
519
550
|
gdriveWebhookProvider,
|
|
520
551
|
genericHmacWebhookProvider,
|
|
521
552
|
gmailWebhookProvider,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/webhooks/router.ts","../../src/webhooks/providers.ts"],"sourcesContent":["/**\n * @stable Provider-agnostic inbound webhook router.\n *\n * Consumer hooks a single HTTP handler at `/webhook/:provider/:event`\n * (or whatever pathing they prefer) and forwards the request through\n * `WebhookRouter.handle()`. The router:\n *\n * 1. Resolves the registered provider entry.\n * 2. Calls the provider's `verifySignature(rawBody, headers, secrets)`.\n * Failure → 401 fast, no downstream work.\n * 3. Calls the provider's `parse(rawBody, headers)` to extract zero or\n * more normalized events.\n * 4. Enqueues each event for async processing via the consumer-supplied\n * `deliver(event)` callback (best-effort fire-and-forget — the\n * router does NOT block the HTTP response on the consumer's work).\n * 5. Returns 200 fast with `{received: events.length}`.\n *\n * Replay protection: providers that sign timestamps (Stripe, Slack)\n * already reject stale signatures inside `verifySignature`. For providers\n * that don't (DocuSeal, GDrive push), the router exposes a pluggable\n * `idempotency` hook: if `idempotency.seen(providerEventId)` returns\n * true, the router 200s without invoking `deliver()`. Consumers wire\n * this to a durable kv (D1 / Redis / Postgres unique-index).\n *\n * Why a router and not a per-provider express app: the runtime contract\n * a product cares about is \"an inbound event came in, here's the\n * normalized envelope\". Verification, parsing, and idempotency-dedup\n * are mechanical and provider-specific — the router owns them. The\n * consumer's `deliver()` is the only place product logic runs.\n *\n * Stability: `@stable` — additions to `WebhookEnvelope` must be\n * additive; the router's HTTP contract (paths, status codes) is frozen\n * at 200 (ok), 400 (bad request), 401 (bad signature), 404 (unknown\n * provider), 405 (provider has no inbound surface).\n */\n\nexport interface WebhookHeaders {\n [name: string]: string | string[] | undefined\n}\n\n/** Normalized inbound event the router emits after parsing. */\nexport interface WebhookEnvelope<TPayload = unknown> {\n /** Provider id (matches the `:provider` path segment). */\n provider: string\n /** Optional event class — e.g., 'customer.subscription.deleted'. The\n * provider's parser decides. Used for routing inside `deliver()`. */\n eventType: string\n /** Provider-emitted event id, when present. Used for the idempotency\n * short-circuit. */\n providerEventId?: string\n /** Wall-clock receive time. */\n receivedAt: number\n /** Provider payload, normalized to the provider's documented event\n * shape. The router does NOT reshape this — `parse()` is the contract. */\n payload: TPayload\n /** Headers passed through for downstream handlers that want them\n * (e.g., to extract custom routing metadata). Always lowercased keys. */\n headers: Record<string, string>\n}\n\nexport type SignatureVerification =\n | { valid: true }\n | { valid: false; reason: string }\n\n/** One declared triggerable event, in the runtime `eventType` namespace a\n * provider's `parse` emits. */\nexport interface TriggerEventDescriptor {\n /** The `eventType` a workflow `provider_event` trigger matches on\n * (`slack.app_mention`, `charge.succeeded`). */\n id: string\n /** Short human label for authoring surfaces; optional. */\n title?: string\n}\n\n/** The set of events a provider can deliver, for author-time validation of\n * workflow `provider_event` triggers. Co-located with the provider's `parse`\n * (the source of the runtime namespace) so the two cannot drift — see the\n * per-provider drift tests. Consumers validate an authored event with two\n * checks: it must carry `namespace` (a prefix-less event on a namespaced\n * provider can never be delivered), and — only when `closed` — be one of\n * `events` (an exhaustive set makes an unlisted event an authoring error). */\nexport interface TriggerEventCatalog {\n /** Required prefix every `parse`-emitted `eventType` carries (`slack.`,\n * `telegram.`), or `null` when the provider emits raw event names (stripe). */\n namespace: string | null\n /** Declared events. Exhaustive iff `closed`; otherwise authoring hints for an\n * open, provider-owned vocabulary. */\n events: readonly TriggerEventDescriptor[]\n /** True when `events` is the COMPLETE deliverable set, so an event outside it\n * is rejected. False when the provider owns an open-ended vocabulary it keeps\n * adding to (Stripe's ~250 types, Slack's Events API) and only `namespace`\n * can be authoritatively enforced. */\n closed: boolean\n}\n\n/** Per-provider plug-in. Stateless — the router calls `verifySignature`\n * then `parse` on every request. The provider's HTTP-shape concerns\n * (e.g., raw body required) are documented per provider. */\nexport interface WebhookProvider {\n /** Stable provider id (`stripe`, `docuseal`, `gdrive`, ...). */\n id: string\n /** Verify the inbound signature. Receives the EXACT raw body string —\n * consumers MUST preserve raw bytes through their HTTP server (do not\n * parse JSON before forwarding here). */\n verifySignature(input: {\n rawBody: string\n headers: WebhookHeaders\n secret: string\n }): SignatureVerification\n /** Parse the validated raw body into zero or more normalized events.\n * A single push payload may carry multiple events (e.g., Slack bulk\n * delivery). Return [] to ack the push as a no-op. */\n parse(input: {\n rawBody: string\n headers: WebhookHeaders\n now?: number\n }): WebhookEnvelope[] | Promise<WebhookEnvelope[]>\n /** Optional provider-specific 200 ACK the router returns on a verified\n * delivery, in place of the default `{ received, total }` JSON. Dropbox Sign\n * acknowledges a delivery only when the body is exactly\n * 'Hello API Event Received' and retries otherwise; this lets such a\n * provider declare the body/headers it needs. The frozen status contract is\n * unchanged — only the 200 body/headers are customized. */\n successResponse?: { body: string; headers?: Record<string, string> }\n /** Declared triggerable-event catalog, for author-time validation of workflow\n * `provider_event` triggers. Optional: a provider without one is left\n * unvalidated by consumers (the trigger still installs). */\n eventCatalog?: TriggerEventCatalog\n}\n\nexport interface WebhookIdempotencyStore {\n /** Returns true if this providerEventId has been processed already.\n * Implementations should be O(1) (Redis SETNX, D1 UNIQUE constraint). */\n seen(providerEventId: string): Promise<boolean> | boolean\n /** Marks a providerEventId as processed. Called AFTER `deliver()` has\n * been invoked. */\n remember(providerEventId: string, ttlMs: number): Promise<void> | void\n}\n\nexport interface WebhookRouterOptions {\n /** Provider registry. Pass any number of providers; routing is by id. */\n providers: WebhookProvider[]\n /** Async callback invoked with every accepted event. Fire-and-forget\n * from the router's perspective — the HTTP response is sent before\n * this resolves. Throws are caught and reported via `onError`. */\n deliver(event: WebhookEnvelope): Promise<void> | void\n /** Resolve the signing secret for a provider id at request time. The\n * router never holds secrets — the consumer's vault resolves them. */\n resolveSecret(providerId: string, headers: WebhookHeaders): Promise<string | null> | string | null\n /** Optional idempotency-dedup hook. Required for providers that don't\n * sign timestamps in their signature scheme (DocuSeal, Drive push). */\n idempotency?: WebhookIdempotencyStore\n /** TTL on idempotency entries. Default 7 days — long enough that a\n * provider's normal retry-window can't re-deliver. */\n idempotencyTtlMs?: number\n /** Surface delivery errors. Default: console.error. */\n onError?(err: unknown, context: { provider: string; eventType?: string; providerEventId?: string }): void\n /** Override `now()` for tests. */\n now?(): number\n}\n\nexport interface WebhookRouterRequest {\n providerId: string\n rawBody: string\n headers: WebhookHeaders\n}\n\nexport interface WebhookRouterResponse {\n status: number\n body: unknown\n headers?: Record<string, string>\n}\n\n/**\n * Router instance. Stateless aside from the provider registry — safe to\n * share across requests; build once per process.\n */\nexport class WebhookRouter {\n private readonly providers: Map<string, WebhookProvider>\n private readonly deliver: WebhookRouterOptions['deliver']\n private readonly resolveSecret: WebhookRouterOptions['resolveSecret']\n private readonly idempotency?: WebhookIdempotencyStore\n private readonly idempotencyTtlMs: number\n private readonly onError: NonNullable<WebhookRouterOptions['onError']>\n private readonly nowFn: () => number\n\n constructor(opts: WebhookRouterOptions) {\n this.providers = new Map(opts.providers.map((p) => [p.id, p]))\n this.deliver = opts.deliver\n this.resolveSecret = opts.resolveSecret\n this.idempotency = opts.idempotency\n this.idempotencyTtlMs = opts.idempotencyTtlMs ?? 7 * 24 * 60 * 60 * 1000\n this.onError = opts.onError ?? defaultOnError\n this.nowFn = opts.now ?? Date.now\n }\n\n /** Process one inbound webhook request. Pure with respect to side-\n * effects on the router instance — safe to call concurrently. */\n async handle(request: WebhookRouterRequest): Promise<WebhookRouterResponse> {\n const provider = this.providers.get(request.providerId)\n if (!provider) {\n return { status: 404, body: { error: 'unknown_provider', provider: request.providerId } }\n }\n const secret = await this.resolveSecret(provider.id, request.headers)\n if (!secret) {\n return { status: 401, body: { error: 'missing_secret', provider: provider.id } }\n }\n const verification = provider.verifySignature({\n rawBody: request.rawBody,\n headers: request.headers,\n secret,\n })\n if (!verification.valid) {\n return { status: 401, body: { error: 'invalid_signature', reason: verification.reason } }\n }\n\n let events: WebhookEnvelope[]\n try {\n events = await provider.parse({ rawBody: request.rawBody, headers: request.headers, now: this.nowFn() })\n } catch (err) {\n this.onError(err, { provider: provider.id })\n return { status: 400, body: { error: 'parse_error', message: errMessage(err) } }\n }\n\n const accepted: WebhookEnvelope[] = []\n for (const event of events) {\n if (event.providerEventId && this.idempotency) {\n const already = await this.idempotency.seen(event.providerEventId)\n if (already) continue\n }\n accepted.push(event)\n }\n\n // Deliver async — do NOT block the HTTP response. Errors land in\n // `onError`; the provider already got its 200 by then so it will\n // not retry.\n queueMicrotask(() => {\n void this.deliverEach(accepted)\n })\n\n if (provider.successResponse) {\n return {\n status: 200,\n body: provider.successResponse.body,\n headers: provider.successResponse.headers,\n }\n }\n return { status: 200, body: { received: accepted.length, total: events.length } }\n }\n\n private async deliverEach(events: WebhookEnvelope[]): Promise<void> {\n for (const event of events) {\n try {\n await this.deliver(event)\n if (event.providerEventId && this.idempotency) {\n await this.idempotency.remember(event.providerEventId, this.idempotencyTtlMs)\n }\n } catch (err) {\n this.onError(err, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n }\n }\n }\n}\n\nfunction defaultOnError(err: unknown, context: { provider: string; eventType?: string; providerEventId?: string }): void {\n // eslint-disable-next-line no-console\n console.error('[WebhookRouter]', context, err)\n}\n\nfunction errMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\n * Pre-built `WebhookProvider` implementations for the inbound surfaces\n * the substrate ships first-party verifiers for.\n *\n * Each provider implementation is intentionally thin: it delegates\n * signature verification to the corresponding pure function in\n * `connectors/webhooks.ts` and parses the body into one or more\n * normalized `WebhookEnvelope` rows. Anything provider-specific that\n * doesn't fit cleanly (Slack URL-verification handshake, etc.) is\n * surfaced via the envelope `eventType` so the consumer's `deliver()`\n * can branch.\n */\n\nimport {\n firstHeader,\n verifyHmacSignature,\n verifySlackSignature,\n verifyStripeSignature,\n} from '../connectors/webhooks.js'\nimport type { WebhookEnvelope, WebhookHeaders, WebhookProvider, SignatureVerification } from './router.js'\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto'\n\n/** Stripe webhook provider. Signature header `Stripe-Signature`. */\nexport const stripeWebhookProvider: WebhookProvider = {\n id: 'stripe',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const sig = firstHeader(headers, 'stripe-signature')\n if (!sig) return { valid: false, reason: 'missing_stripe_signature' }\n return verifyStripeSignature(rawBody, sig, secret)\n ? { valid: true }\n : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody)\n if (!evt || typeof evt !== 'object') return []\n const e = evt as { id?: unknown; type?: unknown }\n return [\n {\n provider: 'stripe',\n eventType: typeof e.type === 'string' ? e.type : 'stripe.unknown',\n providerEventId: typeof e.id === 'string' ? e.id : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n },\n ]\n },\n eventCatalog: {\n // Stripe emits the raw event `type` (`charge.succeeded`) — no namespace, and\n // an OPEN, versioned vocabulary (~250 types), so these are authoring hints,\n // not a gate.\n namespace: null,\n closed: false,\n events: [\n { id: 'checkout.session.completed' },\n { id: 'checkout.session.expired' },\n { id: 'customer.subscription.created' },\n { id: 'customer.subscription.updated' },\n { id: 'customer.subscription.deleted' },\n { id: 'invoice.paid' },\n { id: 'invoice.payment_failed' },\n { id: 'payment_intent.succeeded' },\n { id: 'payment_intent.payment_failed' },\n { id: 'charge.succeeded' },\n { id: 'charge.refunded' },\n ],\n },\n}\n\n/** Slack Events API provider. Handles the `url_verification` handshake\n * by emitting a synthetic event the consumer's `deliver()` can echo. */\nexport const slackWebhookProvider: WebhookProvider = {\n id: 'slack',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const sig = firstHeader(headers, 'x-slack-signature')\n const ts = firstHeader(headers, 'x-slack-request-timestamp')\n if (!sig || !ts) return { valid: false, reason: 'missing_slack_signature_or_timestamp' }\n return verifySlackSignature(rawBody, sig, ts, secret)\n ? { valid: true }\n : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as { type?: string; event_id?: string; event?: { type?: string } } | null\n if (!evt || typeof evt !== 'object') return []\n if (evt.type === 'url_verification') {\n return [{\n provider: 'slack',\n eventType: 'slack.url_verification',\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n }\n const eventType = `slack.${evt.event?.type ?? evt.type ?? 'unknown'}`\n return [{\n provider: 'slack',\n eventType,\n providerEventId: typeof evt.event_id === 'string' ? evt.event_id : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Slack namespaces every event `slack.<events-api-type>`, so a prefix-less\n // event is provably dead. The Events API vocabulary is open (Slack adds\n // types), so `events` are hints, not a gate. `slack.url_verification` (the\n // config handshake) is intentionally excluded — it carries no workflow\n // meaning and is answered before dispatch.\n namespace: 'slack.',\n closed: false,\n events: [\n { id: 'slack.message' },\n { id: 'slack.app_mention' },\n { id: 'slack.reaction_added' },\n { id: 'slack.reaction_removed' },\n { id: 'slack.channel_created' },\n { id: 'slack.member_joined_channel' },\n { id: 'slack.team_join' },\n { id: 'slack.app_home_opened' },\n { id: 'slack.file_shared' },\n ],\n },\n}\n\n/** DocuSeal webhook provider. Signature header `X-Docuseal-Signature`. */\nexport const docusealWebhookProvider: WebhookProvider = {\n id: 'docuseal',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const sig = firstHeader(headers, 'x-docuseal-signature')\n if (!sig) return { valid: false, reason: 'missing_docuseal_signature' }\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(sig.toLowerCase(), 'utf-8')\n const b = Buffer.from(expected, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as { event_type?: string; event_id?: string } | null\n if (!evt || typeof evt !== 'object') return []\n return [{\n provider: 'docuseal',\n eventType: `docuseal.${evt.event_type ?? 'unknown'}`,\n providerEventId: typeof evt.event_id === 'string' ? evt.event_id : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // DocuSeal namespaces every event `docuseal.<event_type>`. The documented\n // set is small but not derivable from `parse`, so it stays open (namespace\n // is the authoritative gate) with the documented types as hints.\n namespace: 'docuseal.',\n closed: false,\n events: [\n { id: 'docuseal.form.viewed' },\n { id: 'docuseal.form.started' },\n { id: 'docuseal.form.completed' },\n { id: 'docuseal.form.declined' },\n { id: 'docuseal.submission.created' },\n { id: 'docuseal.submission.completed' },\n { id: 'docuseal.submission.expired' },\n { id: 'docuseal.submission.archived' },\n ],\n },\n}\n\n/** Gmail push provider. Cloud Pub/Sub posts a JWT-signed envelope; the\n * *payload* is base64 JSON describing the changed history range. The\n * signature scheme here is the Pub/Sub JWT auth header — when supplied,\n * consumers SHOULD verify the JWT against Google's well-known\n * certificates. We accept the simpler \"Bearer <pubsub-shared-secret>\"\n * variant by default (matching `verifyHmacSignature`). */\nexport const gmailWebhookProvider: WebhookProvider = {\n id: 'gmail',\n verifySignature({ headers, secret }): SignatureVerification {\n const auth = firstHeader(headers, 'authorization')\n if (!auth) return { valid: false, reason: 'missing_authorization' }\n // Accept either \"Bearer <secret>\" or \"Token <secret>\" formats. This\n // is the simple per-tenant shared-secret path; JWT verification is\n // left to the consumer's `deliver()` when they need full Google JWT\n // chain validation.\n const m = /^(?:Bearer|Token)\\s+(.+)$/i.exec(auth)\n if (!m) return { valid: false, reason: 'invalid_authorization_format' }\n const a = Buffer.from(m[1]!, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const envelope = safeJson(rawBody) as { message?: { data?: string; messageId?: string; publishTime?: string } } | null\n if (!envelope?.message?.data) return []\n let payload: unknown\n try {\n payload = JSON.parse(Buffer.from(envelope.message.data, 'base64').toString('utf-8'))\n } catch {\n return []\n }\n const inner = payload as { historyId?: number | string; emailAddress?: string }\n return [{\n provider: 'gmail',\n eventType: 'gmail.history_changed',\n providerEventId: envelope.message.messageId,\n receivedAt: now ?? Date.now(),\n payload: { ...inner, publishTime: envelope.message.publishTime },\n headers: normalizeHeaders(headers),\n }]\n },\n}\n\n/** Google Drive push provider. Drive does NOT sign the body — it uses\n * the per-channel token (`X-Goog-Channel-Token`) as the shared secret.\n * The router compares it constant-time against the resolved secret. */\nexport const gdriveWebhookProvider: WebhookProvider = {\n id: 'gdrive',\n verifySignature({ headers, secret }): SignatureVerification {\n const token = firstHeader(headers, 'x-goog-channel-token')\n if (!token) return { valid: false, reason: 'missing_channel_token' }\n const a = Buffer.from(token, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ headers, now }): WebhookEnvelope[] {\n const resourceId = firstHeader(headers, 'x-goog-resource-id')\n const resourceState = firstHeader(headers, 'x-goog-resource-state') ?? 'unknown'\n const channelId = firstHeader(headers, 'x-goog-channel-id')\n const messageNumber = firstHeader(headers, 'x-goog-message-number')\n if (resourceState === 'sync') {\n return [{\n provider: 'gdrive',\n eventType: 'gdrive.channel.sync',\n providerEventId: messageNumber ? `${channelId}-${messageNumber}` : undefined,\n receivedAt: now ?? Date.now(),\n payload: { channelId, resourceId, resourceState },\n headers: normalizeHeaders(headers),\n }]\n }\n return [{\n provider: 'gdrive',\n eventType: `gdrive.resource.${resourceState}`,\n providerEventId: messageNumber ? `${channelId}-${messageNumber}` : undefined,\n receivedAt: now ?? Date.now(),\n payload: { channelId, resourceId, resourceState },\n headers: normalizeHeaders(headers),\n }]\n },\n}\n\n/** The Telegram update keys, in resolution order — the first present key names\n * the update kind (`telegram.message`, `telegram.callback_query`, …). */\nconst TELEGRAM_UPDATE_KEYS = [\n 'message',\n 'edited_message',\n 'channel_post',\n 'edited_channel_post',\n 'business_connection',\n 'business_message',\n 'edited_business_message',\n 'deleted_business_messages',\n 'message_reaction',\n 'message_reaction_count',\n 'inline_query',\n 'chosen_inline_result',\n 'callback_query',\n 'shipping_query',\n 'pre_checkout_query',\n 'purchased_paid_media',\n 'poll',\n 'poll_answer',\n 'my_chat_member',\n 'chat_member',\n 'chat_join_request',\n 'chat_boost',\n 'removed_chat_boost',\n] as const\n\n/** Telegram Bot API webhook provider. Telegram does NOT sign the body — it\n * authenticates by echoing the `secret_token` set at `setWebhook` time in the\n * `X-Telegram-Bot-Api-Secret-Token` header. We compare it constant-time to the\n * per-connection secret. `update_id` is the idempotency anchor. */\nexport const telegramWebhookProvider: WebhookProvider = {\n id: 'telegram',\n verifySignature({ headers, secret }): SignatureVerification {\n const token = firstHeader(headers, 'x-telegram-bot-api-secret-token')\n if (!token) return { valid: false, reason: 'missing_telegram_secret_token' }\n const a = Buffer.from(token, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_secret_token' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_secret_token' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as ({ update_id?: number } & Record<string, unknown>) | null\n if (!evt || typeof evt !== 'object') return []\n const kind = TELEGRAM_UPDATE_KEYS.find((k) => k in evt) ?? 'unknown'\n return [{\n provider: 'telegram',\n eventType: `telegram.${kind}`,\n providerEventId: typeof evt.update_id === 'number' ? String(evt.update_id) : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Derived from the SAME TELEGRAM_UPDATE_KEYS `parse` matches on, so the\n // catalog is exhaustive and cannot drift — hence `closed`. (`telegram.unknown`,\n // parse's fallback for an update with no recognized key, is intentionally\n // excluded: it is not a triggerable event.)\n namespace: 'telegram.',\n closed: true,\n events: TELEGRAM_UPDATE_KEYS.map((k) => ({ id: `telegram.${k}` })),\n },\n}\n\n/** Dropbox Sign (HelloSign) webhook provider. There is NO signature header:\n * the body carries `event.event_hash = HMAC_SHA256(apiKey, event_time +\n * event_type)` (hex), so the `secret` here is the account's API key (the\n * caller resolves it from the connection credential). The body is JSON, or a\n * `json=`/multipart `name=\"json\"` form field on older apps. */\nexport const hellosignWebhookProvider: WebhookProvider = {\n id: 'hellosign',\n // Dropbox Sign acknowledges a delivery only when the response body is exactly\n // 'Hello API Event Received'; any other body makes it retry the event.\n successResponse: { body: 'Hello API Event Received', headers: { 'content-type': 'text/plain' } },\n verifySignature({ rawBody, secret }): SignatureVerification {\n const body = parseHelloSignBody(rawBody)\n const event = body?.event\n if (\n !event ||\n typeof event.event_time !== 'string' ||\n typeof event.event_type !== 'string' ||\n typeof event.event_hash !== 'string'\n ) {\n return { valid: false, reason: 'missing_event_fields' }\n }\n const expected = createHmac('sha256', secret)\n .update(`${event.event_time}${event.event_type}`)\n .digest('hex')\n const a = Buffer.from(event.event_hash.toLowerCase(), 'utf-8')\n const b = Buffer.from(expected.toLowerCase(), 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const body = parseHelloSignBody(rawBody)\n const event = body?.event\n if (!event) return []\n return [{\n provider: 'hellosign',\n eventType: `hellosign.${event.event_type ?? 'unknown'}`,\n // Prefer Dropbox Sign's canonical `event.event_id` (stable across\n // redeliveries, correlates with the provider). Fall back to a digest of\n // the parsed body when it's absent: `event_hash` = HMAC(apiKey,\n // event_time + event_type), so two distinct same-type events in the same\n // second share it — the body digest stays distinct across differing\n // events and identical across redeliveries of one event.\n providerEventId:\n typeof event.event_id === 'string' && event.event_id.length > 0\n ? event.event_id\n : createHash('sha256').update(JSON.stringify(body)).digest('hex'),\n receivedAt: now ?? Date.now(),\n payload: body,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Dropbox Sign namespaces every event `hellosign.<event_type>`. Documented\n // set as hints; namespace is the authoritative gate. `callback_test` (the\n // app-config ping) is intentionally excluded — it is not a workflow event.\n namespace: 'hellosign.',\n closed: false,\n events: [\n { id: 'hellosign.signature_request_sent' },\n { id: 'hellosign.signature_request_viewed' },\n { id: 'hellosign.signature_request_signed' },\n { id: 'hellosign.signature_request_all_signed' },\n { id: 'hellosign.signature_request_declined' },\n { id: 'hellosign.signature_request_reassigned' },\n { id: 'hellosign.signature_request_remind' },\n { id: 'hellosign.signature_request_canceled' },\n { id: 'hellosign.signature_request_expired' },\n ],\n },\n}\n\n/** Keys Clay HTTP-API-action payloads commonly carry that can anchor\n * idempotency, in resolution order. Clay bodies are operator-defined, so we\n * fall back to a digest of the raw body when none is present. */\nconst CLAY_EVENT_ID_KEYS = ['id', 'rowId', 'recordId', 'clayRowId', 'eventId'] as const\n\n/** Clay webhook provider — receives rows Clay delivers via its outbound \"HTTP\n * API\" action. Clay does NOT sign its requests and adds no proprietary\n * headers (the `x-clay-signature: sha256=...` header seen in some third-party\n * code is unverified inference, not in Clay's docs). Authentication is\n * therefore a pre-shared secret the operator adds as a header on the Clay\n * action; we compare it constant-time against the per-connection secret.\n * Header: `x-clay-webhook-secret`. The body is whatever columns the operator\n * mapped (free-form JSON with no canonical event id), so idempotency anchors\n * on a common id field when present, else a sha256 of the raw body. */\nexport const clayWebhookProvider: WebhookProvider = {\n id: 'clay',\n verifySignature({ headers, secret }): SignatureVerification {\n const token = firstHeader(headers, 'x-clay-webhook-secret')\n if (!token) return { valid: false, reason: 'missing_clay_webhook_secret' }\n const a = Buffer.from(token, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody)\n if (!evt || typeof evt !== 'object') return []\n const record = evt as Record<string, unknown>\n const idKey = CLAY_EVENT_ID_KEYS.find(\n (k) => typeof record[k] === 'string' || typeof record[k] === 'number',\n )\n const providerEventId = idKey\n ? String(record[idKey])\n : createHash('sha256').update(rawBody).digest('hex')\n const eventType =\n typeof record.event === 'string'\n ? `clay.${record.event}`\n : typeof record.type === 'string'\n ? `clay.${record.type}`\n : 'clay.row'\n return [{\n provider: 'clay',\n eventType,\n providerEventId,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n}\n\ninterface HelloSignInboundBody {\n event?: {\n event_time?: string\n event_type?: string\n event_hash?: string\n event_id?: string\n }\n}\n\n/** Dropbox Sign posts JSON, a form-urlencoded `json=` field, or a\n * multipart `name=\"json\"` part depending on app vintage. Mirrors the\n * connector adapter's body extraction. */\nfunction parseHelloSignBody(rawBody: string): HelloSignInboundBody | null {\n try {\n const json = JSON.parse(rawBody) as HelloSignInboundBody\n if (json && typeof json === 'object') return json\n } catch {\n // fall through to form-data extraction\n }\n // application/x-www-form-urlencoded `json=` field. URLSearchParams decodes\n // `+` as a space (decodeURIComponent leaves it literal) and resolves\n // percent-escapes, so space-bearing string fields aren't corrupted.\n const formJson = new URLSearchParams(rawBody).get('json')\n if (formJson) {\n try {\n return JSON.parse(formJson) as HelloSignInboundBody\n } catch {\n return null\n }\n }\n // multipart/form-data with a `name=\"json\"` part. Extract linearly (indexOf +\n // fixed-shape separator probes, no unbounded lazy regex) so a hostile body\n // can't drive quadratic backtracking on this public ingress.\n return extractMultipartJsonField(rawBody)\n}\n\n/** Pull the `json` part out of a multipart/form-data body without an\n * unbounded backtracking regex. Returns null if the part isn't present or\n * isn't valid JSON. The separator probes (`\\r?\\n\\r?\\n`, `\\r?\\n--`) are\n * fixed-shape (no `*`/`+`), so each runs in a single linear scan. */\nfunction extractMultipartJsonField(rawBody: string): HelloSignInboundBody | null {\n const nameIdx = rawBody.indexOf('name=\"json\"')\n if (nameIdx === -1) return null\n const afterName = rawBody.slice(nameIdx)\n const sep = /\\r?\\n\\r?\\n/.exec(afterName)\n if (!sep) return null\n const partBody = afterName.slice(sep.index + sep[0].length)\n const end = /\\r?\\n--/.exec(partBody)\n if (!end) return null\n try {\n return JSON.parse(partBody.slice(0, end.index)) as HelloSignInboundBody\n } catch {\n return null\n }\n}\n\n/** Generic HMAC provider — for the long-tail webhook source where the\n * caller has standardised on a single sha256-of-body scheme. Header\n * `X-Signature` by default; override at provider-build time if needed. */\nexport function genericHmacWebhookProvider(options: {\n id: string\n signatureHeader?: string\n algorithm?: 'sha256' | 'sha1' | 'sha512'\n signaturePrefix?: string\n /** Parser to convert the raw body into envelopes. Defaults to\n * \"one event with eventType=<provider>.event and payload=JSON\". */\n parse?: WebhookProvider['parse']\n}): WebhookProvider {\n const header = options.signatureHeader ?? 'x-signature'\n return {\n id: options.id,\n verifySignature({ rawBody, headers, secret }) {\n const sig = firstHeader(headers, header)\n if (!sig) return { valid: false, reason: `missing_${header}` }\n return verifyHmacSignature(rawBody, sig, secret, {\n algorithm: options.algorithm ?? 'sha256',\n signaturePrefix: options.signaturePrefix,\n })\n ? { valid: true }\n : { valid: false, reason: 'invalid_signature' }\n },\n parse:\n options.parse ??\n (({ rawBody, headers, now }) => {\n const evt = safeJson(rawBody) ?? rawBody\n return [{\n provider: options.id,\n eventType: `${options.id}.event`,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n }),\n }\n}\n\nfunction safeJson(s: string): unknown {\n try {\n return JSON.parse(s)\n } catch {\n return null\n }\n}\n\nfunction normalizeHeaders(headers: WebhookHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(headers)) {\n if (v === undefined) continue\n const value = Array.isArray(v) ? v[0] : v\n if (typeof value === 'string') out[k.toLowerCase()] = value\n }\n return out\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/webhooks/router.ts","../../src/webhooks/providers.ts"],"sourcesContent":["/**\n * @stable Provider-agnostic inbound webhook router.\n *\n * Consumer hooks a single HTTP handler at `/webhook/:provider/:event`\n * (or whatever pathing they prefer) and forwards the request through\n * `WebhookRouter.handle()`. The router:\n *\n * 1. Resolves the registered provider entry.\n * 2. Calls the provider's `verifySignature(rawBody, headers, secrets)`.\n * Failure → 401 fast, no downstream work.\n * 3. Calls the provider's `parse(rawBody, headers)` to extract zero or\n * more normalized events.\n * 4. Enqueues each event for async processing via the consumer-supplied\n * `deliver(event)` callback (best-effort fire-and-forget — the\n * router does NOT block the HTTP response on the consumer's work).\n * 5. Returns 200 fast with `{received: events.length}`.\n *\n * Replay protection: providers that sign timestamps (Stripe, Slack)\n * already reject stale signatures inside `verifySignature`. For providers\n * that don't (DocuSeal, GDrive push), the router exposes a pluggable\n * `idempotency` hook: if `idempotency.seen(providerEventId)` returns\n * true, the router 200s without invoking `deliver()`. Consumers wire\n * this to a durable kv (D1 / Redis / Postgres unique-index).\n *\n * Why a router and not a per-provider express app: the runtime contract\n * a product cares about is \"an inbound event came in, here's the\n * normalized envelope\". Verification, parsing, and idempotency-dedup\n * are mechanical and provider-specific — the router owns them. The\n * consumer's `deliver()` is the only place product logic runs.\n *\n * Stability: `@stable` — additions to `WebhookEnvelope` must be\n * additive; the router's HTTP contract (paths, status codes) is frozen\n * at 200 (ok), 400 (bad request), 401 (bad signature), 404 (unknown\n * provider), 405 (provider has no inbound surface).\n */\n\nexport interface WebhookHeaders {\n [name: string]: string | string[] | undefined\n}\n\n/** Normalized inbound event the router emits after parsing. */\nexport interface WebhookEnvelope<TPayload = unknown> {\n /** Provider id (matches the `:provider` path segment). */\n provider: string\n /** Optional event class — e.g., 'customer.subscription.deleted'. The\n * provider's parser decides. Used for routing inside `deliver()`. */\n eventType: string\n /** Provider-emitted event id, when present. Used for the idempotency\n * short-circuit. */\n providerEventId?: string\n /** Wall-clock receive time. */\n receivedAt: number\n /** Provider payload, normalized to the provider's documented event\n * shape. The router does NOT reshape this — `parse()` is the contract. */\n payload: TPayload\n /** Headers passed through for downstream handlers that want them\n * (e.g., to extract custom routing metadata). Always lowercased keys. */\n headers: Record<string, string>\n}\n\nexport type SignatureVerification =\n | { valid: true }\n | { valid: false; reason: string }\n\n/** One declared triggerable event, in the runtime `eventType` namespace a\n * provider's `parse` emits. */\nexport interface TriggerEventDescriptor {\n /** The `eventType` a workflow `provider_event` trigger matches on\n * (`slack.app_mention`, `charge.succeeded`). */\n id: string\n /** Short human label for authoring surfaces; optional. */\n title?: string\n}\n\n/** The set of events a provider can deliver, for author-time validation of\n * workflow `provider_event` triggers. Co-located with the provider's `parse`\n * (the source of the runtime namespace) so the two cannot drift — see the\n * per-provider drift tests. Consumers validate an authored event with two\n * checks: it must carry `namespace` (a prefix-less event on a namespaced\n * provider can never be delivered), and — only when `closed` — be one of\n * `events` (an exhaustive set makes an unlisted event an authoring error). */\nexport interface TriggerEventCatalog {\n /** Required prefix every `parse`-emitted `eventType` carries (`slack.`,\n * `telegram.`), or `null` when the provider emits raw event names (stripe). */\n namespace: string | null\n /** Declared events. Exhaustive iff `closed`; otherwise authoring hints for an\n * open, provider-owned vocabulary. */\n events: readonly TriggerEventDescriptor[]\n /** True when `events` is the COMPLETE deliverable set, so an event outside it\n * is rejected. False when the provider owns an open-ended vocabulary it keeps\n * adding to (Stripe's ~250 types, Slack's Events API) and only `namespace`\n * can be authoritatively enforced. */\n closed: boolean\n}\n\n/** Per-provider plug-in. Stateless — the router calls `verifySignature`\n * then `parse` on every request. The provider's HTTP-shape concerns\n * (e.g., raw body required) are documented per provider. */\nexport interface WebhookProvider {\n /** Stable provider id (`stripe`, `docuseal`, `gdrive`, ...). */\n id: string\n /** Verify the inbound signature. Receives the EXACT raw body string —\n * consumers MUST preserve raw bytes through their HTTP server (do not\n * parse JSON before forwarding here). */\n verifySignature(input: {\n rawBody: string\n headers: WebhookHeaders\n secret: string\n }): SignatureVerification\n /** Parse the validated raw body into zero or more normalized events.\n * A single push payload may carry multiple events (e.g., Slack bulk\n * delivery). Return [] to ack the push as a no-op. */\n parse(input: {\n rawBody: string\n headers: WebhookHeaders\n now?: number\n }): WebhookEnvelope[] | Promise<WebhookEnvelope[]>\n /** Optional provider-specific 200 ACK the router returns on a verified\n * delivery, in place of the default `{ received, total }` JSON. Dropbox Sign\n * acknowledges a delivery only when the body is exactly\n * 'Hello API Event Received' and retries otherwise; this lets such a\n * provider declare the body/headers it needs. The frozen status contract is\n * unchanged — only the 200 body/headers are customized. */\n successResponse?: { body: string; headers?: Record<string, string> }\n /** Declared triggerable-event catalog, for author-time validation of workflow\n * `provider_event` triggers. Optional: a provider without one is left\n * unvalidated by consumers (the trigger still installs). */\n eventCatalog?: TriggerEventCatalog\n}\n\nexport interface WebhookIdempotencyStore {\n /** Returns true if this providerEventId has been processed already.\n * Implementations should be O(1) (Redis SETNX, D1 UNIQUE constraint). */\n seen(providerEventId: string): Promise<boolean> | boolean\n /** Marks a providerEventId as processed. Called AFTER `deliver()` has\n * been invoked. */\n remember(providerEventId: string, ttlMs: number): Promise<void> | void\n}\n\nexport interface WebhookRouterOptions {\n /** Provider registry. Pass any number of providers; routing is by id. */\n providers: WebhookProvider[]\n /** Async callback invoked with every accepted event. Fire-and-forget\n * from the router's perspective — the HTTP response is sent before\n * this resolves. Throws are caught and reported via `onError`. */\n deliver(event: WebhookEnvelope): Promise<void> | void\n /** Resolve the signing secret for a provider id at request time. The\n * router never holds secrets — the consumer's vault resolves them. */\n resolveSecret(providerId: string, headers: WebhookHeaders): Promise<string | null> | string | null\n /** Optional idempotency-dedup hook. Required for providers that don't\n * sign timestamps in their signature scheme (DocuSeal, Drive push). */\n idempotency?: WebhookIdempotencyStore\n /** TTL on idempotency entries. Default 7 days — long enough that a\n * provider's normal retry-window can't re-deliver. */\n idempotencyTtlMs?: number\n /** Surface delivery errors. Default: console.error. */\n onError?(err: unknown, context: { provider: string; eventType?: string; providerEventId?: string }): void\n /** Override `now()` for tests. */\n now?(): number\n}\n\nexport interface WebhookRouterRequest {\n providerId: string\n rawBody: string\n headers: WebhookHeaders\n}\n\nexport interface WebhookRouterResponse {\n status: number\n body: unknown\n headers?: Record<string, string>\n}\n\n/**\n * Router instance. Stateless aside from the provider registry — safe to\n * share across requests; build once per process.\n */\nexport class WebhookRouter {\n private readonly providers: Map<string, WebhookProvider>\n private readonly deliver: WebhookRouterOptions['deliver']\n private readonly resolveSecret: WebhookRouterOptions['resolveSecret']\n private readonly idempotency?: WebhookIdempotencyStore\n private readonly idempotencyTtlMs: number\n private readonly onError: NonNullable<WebhookRouterOptions['onError']>\n private readonly nowFn: () => number\n\n constructor(opts: WebhookRouterOptions) {\n this.providers = new Map(opts.providers.map((p) => [p.id, p]))\n this.deliver = opts.deliver\n this.resolveSecret = opts.resolveSecret\n this.idempotency = opts.idempotency\n this.idempotencyTtlMs = opts.idempotencyTtlMs ?? 7 * 24 * 60 * 60 * 1000\n this.onError = opts.onError ?? defaultOnError\n this.nowFn = opts.now ?? Date.now\n }\n\n /** Process one inbound webhook request. Pure with respect to side-\n * effects on the router instance — safe to call concurrently. */\n async handle(request: WebhookRouterRequest): Promise<WebhookRouterResponse> {\n const provider = this.providers.get(request.providerId)\n if (!provider) {\n return { status: 404, body: { error: 'unknown_provider', provider: request.providerId } }\n }\n const secret = await this.resolveSecret(provider.id, request.headers)\n if (!secret) {\n return { status: 401, body: { error: 'missing_secret', provider: provider.id } }\n }\n const verification = provider.verifySignature({\n rawBody: request.rawBody,\n headers: request.headers,\n secret,\n })\n if (!verification.valid) {\n return { status: 401, body: { error: 'invalid_signature', reason: verification.reason } }\n }\n\n let events: WebhookEnvelope[]\n try {\n events = await provider.parse({ rawBody: request.rawBody, headers: request.headers, now: this.nowFn() })\n } catch (err) {\n this.onError(err, { provider: provider.id })\n return { status: 400, body: { error: 'parse_error', message: errMessage(err) } }\n }\n\n const accepted: WebhookEnvelope[] = []\n for (const event of events) {\n if (event.providerEventId && this.idempotency) {\n const already = await this.idempotency.seen(event.providerEventId)\n if (already) continue\n }\n accepted.push(event)\n }\n\n // Deliver async — do NOT block the HTTP response. Errors land in\n // `onError`; the provider already got its 200 by then so it will\n // not retry.\n queueMicrotask(() => {\n void this.deliverEach(accepted)\n })\n\n if (provider.successResponse) {\n return {\n status: 200,\n body: provider.successResponse.body,\n headers: provider.successResponse.headers,\n }\n }\n return { status: 200, body: { received: accepted.length, total: events.length } }\n }\n\n private async deliverEach(events: WebhookEnvelope[]): Promise<void> {\n for (const event of events) {\n try {\n await this.deliver(event)\n if (event.providerEventId && this.idempotency) {\n await this.idempotency.remember(event.providerEventId, this.idempotencyTtlMs)\n }\n } catch (err) {\n this.onError(err, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n }\n }\n }\n}\n\nfunction defaultOnError(err: unknown, context: { provider: string; eventType?: string; providerEventId?: string }): void {\n // eslint-disable-next-line no-console\n console.error('[WebhookRouter]', context, err)\n}\n\nfunction errMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\n * Pre-built `WebhookProvider` implementations for the inbound surfaces\n * the substrate ships first-party verifiers for.\n *\n * Each provider implementation is intentionally thin: it delegates\n * signature verification to the corresponding pure function in\n * `connectors/webhooks.ts` and parses the body into one or more\n * normalized `WebhookEnvelope` rows. Anything provider-specific that\n * doesn't fit cleanly (Slack URL-verification handshake, etc.) is\n * surfaced via the envelope `eventType` so the consumer's `deliver()`\n * can branch.\n */\n\nimport {\n firstHeader,\n verifyEmailSignature,\n verifyHmacSignature,\n verifySlackSignature,\n verifyStripeSignature,\n} from '../connectors/webhooks.js'\nimport type { WebhookEnvelope, WebhookHeaders, WebhookProvider, SignatureVerification } from './router.js'\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto'\n\n/** Stripe webhook provider. Signature header `Stripe-Signature`. */\nexport const stripeWebhookProvider: WebhookProvider = {\n id: 'stripe',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const sig = firstHeader(headers, 'stripe-signature')\n if (!sig) return { valid: false, reason: 'missing_stripe_signature' }\n return verifyStripeSignature(rawBody, sig, secret)\n ? { valid: true }\n : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody)\n if (!evt || typeof evt !== 'object') return []\n const e = evt as { id?: unknown; type?: unknown }\n return [\n {\n provider: 'stripe',\n eventType: typeof e.type === 'string' ? e.type : 'stripe.unknown',\n providerEventId: typeof e.id === 'string' ? e.id : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n },\n ]\n },\n eventCatalog: {\n // Stripe emits the raw event `type` (`charge.succeeded`) — no namespace, and\n // an OPEN, versioned vocabulary (~250 types), so these are authoring hints,\n // not a gate.\n namespace: null,\n closed: false,\n events: [\n { id: 'checkout.session.completed' },\n { id: 'checkout.session.expired' },\n { id: 'customer.subscription.created' },\n { id: 'customer.subscription.updated' },\n { id: 'customer.subscription.deleted' },\n { id: 'invoice.paid' },\n { id: 'invoice.payment_failed' },\n { id: 'payment_intent.succeeded' },\n { id: 'payment_intent.payment_failed' },\n { id: 'charge.succeeded' },\n { id: 'charge.refunded' },\n ],\n },\n}\n\n/** Slack Events API provider. Handles the `url_verification` handshake\n * by emitting a synthetic event the consumer's `deliver()` can echo. */\nexport const slackWebhookProvider: WebhookProvider = {\n id: 'slack',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const sig = firstHeader(headers, 'x-slack-signature')\n const ts = firstHeader(headers, 'x-slack-request-timestamp')\n if (!sig || !ts) return { valid: false, reason: 'missing_slack_signature_or_timestamp' }\n return verifySlackSignature(rawBody, sig, ts, secret)\n ? { valid: true }\n : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as { type?: string; event_id?: string; event?: { type?: string } } | null\n if (!evt || typeof evt !== 'object') return []\n if (evt.type === 'url_verification') {\n return [{\n provider: 'slack',\n eventType: 'slack.url_verification',\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n }\n const eventType = `slack.${evt.event?.type ?? evt.type ?? 'unknown'}`\n return [{\n provider: 'slack',\n eventType,\n providerEventId: typeof evt.event_id === 'string' ? evt.event_id : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Slack namespaces every event `slack.<events-api-type>`, so a prefix-less\n // event is provably dead. The Events API vocabulary is open (Slack adds\n // types), so `events` are hints, not a gate. `slack.url_verification` (the\n // config handshake) is intentionally excluded — it carries no workflow\n // meaning and is answered before dispatch.\n namespace: 'slack.',\n closed: false,\n events: [\n { id: 'slack.message' },\n { id: 'slack.app_mention' },\n { id: 'slack.reaction_added' },\n { id: 'slack.reaction_removed' },\n { id: 'slack.channel_created' },\n { id: 'slack.member_joined_channel' },\n { id: 'slack.team_join' },\n { id: 'slack.app_home_opened' },\n { id: 'slack.file_shared' },\n ],\n },\n}\n\n/** DocuSeal webhook provider. Signature header `X-Docuseal-Signature`. */\nexport const docusealWebhookProvider: WebhookProvider = {\n id: 'docuseal',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const sig = firstHeader(headers, 'x-docuseal-signature')\n if (!sig) return { valid: false, reason: 'missing_docuseal_signature' }\n const expected = createHmac('sha256', secret).update(rawBody).digest('hex')\n const a = Buffer.from(sig.toLowerCase(), 'utf-8')\n const b = Buffer.from(expected, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as { event_type?: string; event_id?: string } | null\n if (!evt || typeof evt !== 'object') return []\n return [{\n provider: 'docuseal',\n eventType: `docuseal.${evt.event_type ?? 'unknown'}`,\n providerEventId: typeof evt.event_id === 'string' ? evt.event_id : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // DocuSeal namespaces every event `docuseal.<event_type>`. The documented\n // set is small but not derivable from `parse`, so it stays open (namespace\n // is the authoritative gate) with the documented types as hints.\n namespace: 'docuseal.',\n closed: false,\n events: [\n { id: 'docuseal.form.viewed' },\n { id: 'docuseal.form.started' },\n { id: 'docuseal.form.completed' },\n { id: 'docuseal.form.declined' },\n { id: 'docuseal.submission.created' },\n { id: 'docuseal.submission.completed' },\n { id: 'docuseal.submission.expired' },\n { id: 'docuseal.submission.archived' },\n ],\n },\n}\n\n/** The normalized inbound-email payload the Tangle email edge posts and this\n * provider surfaces on `WebhookEnvelope.payload`. `to` is the owner-resolution\n * key — the platform matches it to a connection's provisioned inbound address;\n * `messageId` is the idempotency key. */\nexport interface InboundEmailPayload {\n /** RFC5322 Message-ID. */\n messageId?: string\n /** Envelope sender — the firm/customer that emailed in. */\n from: string\n /** The provisioned inbound address the message was delivered to (owner key). */\n to: string\n subject?: string\n text?: string\n html?: string\n /** Selected headers the edge forwards (Reply-To, In-Reply-To, References…). */\n headers?: Record<string, string>\n attachments?: Array<{\n filename?: string\n contentType?: string\n /** Bytes as base64, OR a `url`/path the edge staged them at (large files). */\n contentBase64?: string\n url?: string\n size?: number\n }>\n /** Epoch ms the edge received the message. */\n receivedAt?: number\n}\n\n/** Tangle inbound-email provider. Our own Cloudflare Email Routing worker parses\n * the MIME, JSON-serializes it, timestamped-HMAC signs it with the\n * per-connection secret, and POSTs it here — there is no upstream mail-vendor\n * signature, we are both signer and verifier. One inbound message → one\n * `email.received` envelope; `payload.to` carries the owner-resolution address. */\nexport const emailWebhookProvider: WebhookProvider = {\n id: 'email',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const sig = firstHeader(headers, 'x-tangle-email-signature')\n if (!sig) return { valid: false, reason: 'missing_email_signature' }\n const ts = firstHeader(headers, 'x-tangle-email-timestamp')\n if (!ts) return { valid: false, reason: 'missing_email_timestamp' }\n return verifyEmailSignature(rawBody, sig, ts, secret)\n ? { valid: true }\n : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const msg = safeJson(rawBody) as Partial<InboundEmailPayload> | null\n if (!msg || typeof msg !== 'object') return []\n // `from` + `to` are load-bearing (owner resolution keys off `to`); a body\n // missing either is a malformed edge post — ack as a no-op rather than\n // dispatch an unroutable event.\n if (typeof msg.from !== 'string' || typeof msg.to !== 'string') return []\n return [{\n provider: 'email',\n eventType: 'email.received',\n providerEventId: typeof msg.messageId === 'string' ? msg.messageId : undefined,\n receivedAt: now ?? Date.now(),\n payload: msg as InboundEmailPayload,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // We own the edge, so the inbound surface is exactly one closed event.\n namespace: 'email.',\n closed: true,\n events: [{ id: 'email.received' }],\n },\n}\n\n/** Gmail push provider. Cloud Pub/Sub posts a JWT-signed envelope; the\n * *payload* is base64 JSON describing the changed history range. The\n * signature scheme here is the Pub/Sub JWT auth header — when supplied,\n * consumers SHOULD verify the JWT against Google's well-known\n * certificates. We accept the simpler \"Bearer <pubsub-shared-secret>\"\n * variant by default (matching `verifyHmacSignature`). */\nexport const gmailWebhookProvider: WebhookProvider = {\n id: 'gmail',\n verifySignature({ headers, secret }): SignatureVerification {\n const auth = firstHeader(headers, 'authorization')\n if (!auth) return { valid: false, reason: 'missing_authorization' }\n // Accept either \"Bearer <secret>\" or \"Token <secret>\" formats. This\n // is the simple per-tenant shared-secret path; JWT verification is\n // left to the consumer's `deliver()` when they need full Google JWT\n // chain validation.\n const m = /^(?:Bearer|Token)\\s+(.+)$/i.exec(auth)\n if (!m) return { valid: false, reason: 'invalid_authorization_format' }\n const a = Buffer.from(m[1]!, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const envelope = safeJson(rawBody) as { message?: { data?: string; messageId?: string; publishTime?: string } } | null\n if (!envelope?.message?.data) return []\n let payload: unknown\n try {\n payload = JSON.parse(Buffer.from(envelope.message.data, 'base64').toString('utf-8'))\n } catch {\n return []\n }\n const inner = payload as { historyId?: number | string; emailAddress?: string }\n return [{\n provider: 'gmail',\n eventType: 'gmail.history_changed',\n providerEventId: envelope.message.messageId,\n receivedAt: now ?? Date.now(),\n payload: { ...inner, publishTime: envelope.message.publishTime },\n headers: normalizeHeaders(headers),\n }]\n },\n}\n\n/** Google Drive push provider. Drive does NOT sign the body — it uses\n * the per-channel token (`X-Goog-Channel-Token`) as the shared secret.\n * The router compares it constant-time against the resolved secret. */\nexport const gdriveWebhookProvider: WebhookProvider = {\n id: 'gdrive',\n verifySignature({ headers, secret }): SignatureVerification {\n const token = firstHeader(headers, 'x-goog-channel-token')\n if (!token) return { valid: false, reason: 'missing_channel_token' }\n const a = Buffer.from(token, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ headers, now }): WebhookEnvelope[] {\n const resourceId = firstHeader(headers, 'x-goog-resource-id')\n const resourceState = firstHeader(headers, 'x-goog-resource-state') ?? 'unknown'\n const channelId = firstHeader(headers, 'x-goog-channel-id')\n const messageNumber = firstHeader(headers, 'x-goog-message-number')\n if (resourceState === 'sync') {\n return [{\n provider: 'gdrive',\n eventType: 'gdrive.channel.sync',\n providerEventId: messageNumber ? `${channelId}-${messageNumber}` : undefined,\n receivedAt: now ?? Date.now(),\n payload: { channelId, resourceId, resourceState },\n headers: normalizeHeaders(headers),\n }]\n }\n return [{\n provider: 'gdrive',\n eventType: `gdrive.resource.${resourceState}`,\n providerEventId: messageNumber ? `${channelId}-${messageNumber}` : undefined,\n receivedAt: now ?? Date.now(),\n payload: { channelId, resourceId, resourceState },\n headers: normalizeHeaders(headers),\n }]\n },\n}\n\n/** The Telegram update keys, in resolution order — the first present key names\n * the update kind (`telegram.message`, `telegram.callback_query`, …). */\nconst TELEGRAM_UPDATE_KEYS = [\n 'message',\n 'edited_message',\n 'channel_post',\n 'edited_channel_post',\n 'business_connection',\n 'business_message',\n 'edited_business_message',\n 'deleted_business_messages',\n 'message_reaction',\n 'message_reaction_count',\n 'inline_query',\n 'chosen_inline_result',\n 'callback_query',\n 'shipping_query',\n 'pre_checkout_query',\n 'purchased_paid_media',\n 'poll',\n 'poll_answer',\n 'my_chat_member',\n 'chat_member',\n 'chat_join_request',\n 'chat_boost',\n 'removed_chat_boost',\n] as const\n\n/** Telegram Bot API webhook provider. Telegram does NOT sign the body — it\n * authenticates by echoing the `secret_token` set at `setWebhook` time in the\n * `X-Telegram-Bot-Api-Secret-Token` header. We compare it constant-time to the\n * per-connection secret. `update_id` is the idempotency anchor. */\nexport const telegramWebhookProvider: WebhookProvider = {\n id: 'telegram',\n verifySignature({ headers, secret }): SignatureVerification {\n const token = firstHeader(headers, 'x-telegram-bot-api-secret-token')\n if (!token) return { valid: false, reason: 'missing_telegram_secret_token' }\n const a = Buffer.from(token, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_secret_token' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_secret_token' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as ({ update_id?: number } & Record<string, unknown>) | null\n if (!evt || typeof evt !== 'object') return []\n const kind = TELEGRAM_UPDATE_KEYS.find((k) => k in evt) ?? 'unknown'\n return [{\n provider: 'telegram',\n eventType: `telegram.${kind}`,\n providerEventId: typeof evt.update_id === 'number' ? String(evt.update_id) : undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Derived from the SAME TELEGRAM_UPDATE_KEYS `parse` matches on, so the\n // catalog is exhaustive and cannot drift — hence `closed`. (`telegram.unknown`,\n // parse's fallback for an update with no recognized key, is intentionally\n // excluded: it is not a triggerable event.)\n namespace: 'telegram.',\n closed: true,\n events: TELEGRAM_UPDATE_KEYS.map((k) => ({ id: `telegram.${k}` })),\n },\n}\n\n/** Dropbox Sign (HelloSign) webhook provider. There is NO signature header:\n * the body carries `event.event_hash = HMAC_SHA256(apiKey, event_time +\n * event_type)` (hex), so the `secret` here is the account's API key (the\n * caller resolves it from the connection credential). The body is JSON, or a\n * `json=`/multipart `name=\"json\"` form field on older apps. */\nexport const hellosignWebhookProvider: WebhookProvider = {\n id: 'hellosign',\n // Dropbox Sign acknowledges a delivery only when the response body is exactly\n // 'Hello API Event Received'; any other body makes it retry the event.\n successResponse: { body: 'Hello API Event Received', headers: { 'content-type': 'text/plain' } },\n verifySignature({ rawBody, secret }): SignatureVerification {\n const body = parseHelloSignBody(rawBody)\n const event = body?.event\n if (\n !event ||\n typeof event.event_time !== 'string' ||\n typeof event.event_type !== 'string' ||\n typeof event.event_hash !== 'string'\n ) {\n return { valid: false, reason: 'missing_event_fields' }\n }\n const expected = createHmac('sha256', secret)\n .update(`${event.event_time}${event.event_type}`)\n .digest('hex')\n const a = Buffer.from(event.event_hash.toLowerCase(), 'utf-8')\n const b = Buffer.from(expected.toLowerCase(), 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const body = parseHelloSignBody(rawBody)\n const event = body?.event\n if (!event) return []\n return [{\n provider: 'hellosign',\n eventType: `hellosign.${event.event_type ?? 'unknown'}`,\n // Prefer Dropbox Sign's canonical `event.event_id` (stable across\n // redeliveries, correlates with the provider). Fall back to a digest of\n // the parsed body when it's absent: `event_hash` = HMAC(apiKey,\n // event_time + event_type), so two distinct same-type events in the same\n // second share it — the body digest stays distinct across differing\n // events and identical across redeliveries of one event.\n providerEventId:\n typeof event.event_id === 'string' && event.event_id.length > 0\n ? event.event_id\n : createHash('sha256').update(JSON.stringify(body)).digest('hex'),\n receivedAt: now ?? Date.now(),\n payload: body,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Dropbox Sign namespaces every event `hellosign.<event_type>`. Documented\n // set as hints; namespace is the authoritative gate. `callback_test` (the\n // app-config ping) is intentionally excluded — it is not a workflow event.\n namespace: 'hellosign.',\n closed: false,\n events: [\n { id: 'hellosign.signature_request_sent' },\n { id: 'hellosign.signature_request_viewed' },\n { id: 'hellosign.signature_request_signed' },\n { id: 'hellosign.signature_request_all_signed' },\n { id: 'hellosign.signature_request_declined' },\n { id: 'hellosign.signature_request_reassigned' },\n { id: 'hellosign.signature_request_remind' },\n { id: 'hellosign.signature_request_canceled' },\n { id: 'hellosign.signature_request_expired' },\n ],\n },\n}\n\n/** Keys Clay HTTP-API-action payloads commonly carry that can anchor\n * idempotency, in resolution order. Clay bodies are operator-defined, so we\n * fall back to a digest of the raw body when none is present. */\nconst CLAY_EVENT_ID_KEYS = ['id', 'rowId', 'recordId', 'clayRowId', 'eventId'] as const\n\n/** Clay webhook provider — receives rows Clay delivers via its outbound \"HTTP\n * API\" action. Clay does NOT sign its requests and adds no proprietary\n * headers (the `x-clay-signature: sha256=...` header seen in some third-party\n * code is unverified inference, not in Clay's docs). Authentication is\n * therefore a pre-shared secret the operator adds as a header on the Clay\n * action; we compare it constant-time against the per-connection secret.\n * Header: `x-clay-webhook-secret`. The body is whatever columns the operator\n * mapped (free-form JSON with no canonical event id), so idempotency anchors\n * on a common id field when present, else a sha256 of the raw body. */\nexport const clayWebhookProvider: WebhookProvider = {\n id: 'clay',\n verifySignature({ headers, secret }): SignatureVerification {\n const token = firstHeader(headers, 'x-clay-webhook-secret')\n if (!token) return { valid: false, reason: 'missing_clay_webhook_secret' }\n const a = Buffer.from(token, 'utf-8')\n const b = Buffer.from(secret, 'utf-8')\n if (a.length !== b.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody)\n if (!evt || typeof evt !== 'object') return []\n const record = evt as Record<string, unknown>\n const idKey = CLAY_EVENT_ID_KEYS.find(\n (k) => typeof record[k] === 'string' || typeof record[k] === 'number',\n )\n const providerEventId = idKey\n ? String(record[idKey])\n : createHash('sha256').update(rawBody).digest('hex')\n const eventType =\n typeof record.event === 'string'\n ? `clay.${record.event}`\n : typeof record.type === 'string'\n ? `clay.${record.type}`\n : 'clay.row'\n return [{\n provider: 'clay',\n eventType,\n providerEventId,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n}\n\ninterface HelloSignInboundBody {\n event?: {\n event_time?: string\n event_type?: string\n event_hash?: string\n event_id?: string\n }\n}\n\n/** Dropbox Sign posts JSON, a form-urlencoded `json=` field, or a\n * multipart `name=\"json\"` part depending on app vintage. Mirrors the\n * connector adapter's body extraction. */\nfunction parseHelloSignBody(rawBody: string): HelloSignInboundBody | null {\n try {\n const json = JSON.parse(rawBody) as HelloSignInboundBody\n if (json && typeof json === 'object') return json\n } catch {\n // fall through to form-data extraction\n }\n // application/x-www-form-urlencoded `json=` field. URLSearchParams decodes\n // `+` as a space (decodeURIComponent leaves it literal) and resolves\n // percent-escapes, so space-bearing string fields aren't corrupted.\n const formJson = new URLSearchParams(rawBody).get('json')\n if (formJson) {\n try {\n return JSON.parse(formJson) as HelloSignInboundBody\n } catch {\n return null\n }\n }\n // multipart/form-data with a `name=\"json\"` part. Extract linearly (indexOf +\n // fixed-shape separator probes, no unbounded lazy regex) so a hostile body\n // can't drive quadratic backtracking on this public ingress.\n return extractMultipartJsonField(rawBody)\n}\n\n/** Pull the `json` part out of a multipart/form-data body without an\n * unbounded backtracking regex. Returns null if the part isn't present or\n * isn't valid JSON. The separator probes (`\\r?\\n\\r?\\n`, `\\r?\\n--`) are\n * fixed-shape (no `*`/`+`), so each runs in a single linear scan. */\nfunction extractMultipartJsonField(rawBody: string): HelloSignInboundBody | null {\n const nameIdx = rawBody.indexOf('name=\"json\"')\n if (nameIdx === -1) return null\n const afterName = rawBody.slice(nameIdx)\n const sep = /\\r?\\n\\r?\\n/.exec(afterName)\n if (!sep) return null\n const partBody = afterName.slice(sep.index + sep[0].length)\n const end = /\\r?\\n--/.exec(partBody)\n if (!end) return null\n try {\n return JSON.parse(partBody.slice(0, end.index)) as HelloSignInboundBody\n } catch {\n return null\n }\n}\n\n/** Generic HMAC provider — for the long-tail webhook source where the\n * caller has standardised on a single sha256-of-body scheme. Header\n * `X-Signature` by default; override at provider-build time if needed. */\nexport function genericHmacWebhookProvider(options: {\n id: string\n signatureHeader?: string\n algorithm?: 'sha256' | 'sha1' | 'sha512'\n signaturePrefix?: string\n /** Parser to convert the raw body into envelopes. Defaults to\n * \"one event with eventType=<provider>.event and payload=JSON\". */\n parse?: WebhookProvider['parse']\n}): WebhookProvider {\n const header = options.signatureHeader ?? 'x-signature'\n return {\n id: options.id,\n verifySignature({ rawBody, headers, secret }) {\n const sig = firstHeader(headers, header)\n if (!sig) return { valid: false, reason: `missing_${header}` }\n return verifyHmacSignature(rawBody, sig, secret, {\n algorithm: options.algorithm ?? 'sha256',\n signaturePrefix: options.signaturePrefix,\n })\n ? { valid: true }\n : { valid: false, reason: 'invalid_signature' }\n },\n parse:\n options.parse ??\n (({ rawBody, headers, now }) => {\n const evt = safeJson(rawBody) ?? rawBody\n return [{\n provider: options.id,\n eventType: `${options.id}.event`,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n }),\n }\n}\n\nfunction safeJson(s: string): unknown {\n try {\n return JSON.parse(s)\n } catch {\n return null\n }\n}\n\nfunction normalizeHeaders(headers: WebhookHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(headers)) {\n if (v === undefined) continue\n const value = Array.isArray(v) ? v[0] : v\n if (typeof value === 'string') out[k.toLowerCase()] = value\n }\n return out\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-integrations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Vendor-neutral integration contracts and runtime helpers for sandbox and agent apps.",
|
|
5
5
|
"homepage": "https://github.com/tangle-network/agent-integrations#readme",
|
|
6
6
|
"repository": {
|
|
File without changes
|
|
File without changes
|