@tangle-network/agent-integrations 0.73.0 → 0.74.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/data/bundled-adapter-manifests.json +1 -1
- package/dist/{adapters-D8TvHRA1.js → adapters-CngYgl1W.js} +168 -605
- package/dist/{adapters-D8TvHRA1.js.map → adapters-CngYgl1W.js.map} +1 -1
- package/dist/{bundled-manifest-data-MdURriam.js → bundled-manifest-data-DituXLws.js} +197 -5
- package/dist/{bundled-manifest-data-MdURriam.js.map → bundled-manifest-data-DituXLws.js.map} +1 -1
- package/dist/cloudbeds-BL5EF3xl.js +1195 -0
- package/dist/cloudbeds-BL5EF3xl.js.map +1 -0
- package/dist/connect/index.js +1 -1
- package/dist/connectors/adapters/index.js +3 -2
- package/dist/connectors/index.js +5 -4
- package/dist/consumer.js +1 -1
- package/dist/index-udV3gGjx.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/internal/bundled-manifest-runtime.js +1 -1
- package/dist/mcp.js +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/registry.js +1 -1
- package/dist/{specs-BYUQOsSC.js → specs-BeC45S22.js} +2 -2
- package/dist/{specs-BYUQOsSC.js.map → specs-BeC45S22.js.map} +1 -1
- package/dist/specs.js +1 -1
- package/dist/{src-c7Ru9_WT.js → src-B4-qkoxS.js} +4 -4
- package/dist/{src-c7Ru9_WT.js.map → src-B4-qkoxS.js.map} +1 -1
- package/dist/{tangle-id-D2kPF0rX.js → tangle-id-81ShqJ7k.js} +3 -262
- package/dist/tangle-id-81ShqJ7k.js.map +1 -0
- package/dist/types-BSDXGSi5.js +263 -0
- package/dist/types-BSDXGSi5.js.map +1 -0
- package/dist/webhooks/index.d.ts +5 -0
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +92 -2
- package/dist/webhooks/index.js.map +1 -1
- package/docs/hospitality-connectors.md +91 -11
- package/package.json +1 -1
- package/dist/tangle-id-D2kPF0rX.js.map +0 -1
- package/dist/webhooks-FsjqRN_0.js +0 -117
- package/dist/webhooks-FsjqRN_0.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/webhooks/router.ts","../../src/webhooks/providers.ts","../../src/webhooks/twilio.ts","../../src/webhooks/messaging.ts","../../src/webhooks/whatsapp.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. Awaits the consumer-supplied `deliver(event)` callback.\n * 5. Returns 2xx only after every accepted event finishes durably. A failed\n * or concurrently active delivery returns 503 so the provider retries.\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 atomically claims every\n * parsed event before scheduling `deliver()`. Consumers wire this to a\n * durable SETNX/unique insert (D1, Redis, or Postgres) across workers.\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), and 503 (delivery\n * failed or another worker still owns the delivery).\n */\n\nimport { createHash } from 'node:crypto'\nimport {\n FileSystemAtomicIdempotencyStore,\n InMemoryAtomicIdempotencyStore,\n resolveAtomicIdempotencyStore,\n type AtomicIdempotencyStore,\n type IdempotencyRuntime,\n} from '../idempotency.js'\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 extends AtomicIdempotencyStore {}\n\n/** Process-local atomic store for tests and explicitly single-process apps. */\nexport class InMemoryWebhookIdempotencyStore extends InMemoryAtomicIdempotencyStore implements WebhookIdempotencyStore {}\n\n/** Durable store for workers that share the same filesystem directory. */\nexport class FileSystemWebhookIdempotencyStore extends FileSystemAtomicIdempotencyStore implements WebhookIdempotencyStore {}\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. The callback must\n * finish its durable enqueue or processing before it resolves. */\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 /** Atomic idempotency store. Production requires shared atomic storage. */\n idempotency?: WebhookIdempotencyStore\n /** Runtime controls the safe default. Production fails closed without a\n * shared store; test/development use an in-memory store when omitted. */\n runtime?: IdempotencyRuntime\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 = resolveAtomicIdempotencyStore({\n component: 'WebhookRouter',\n store: opts.idempotency,\n runtime: opts.runtime,\n })\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: Array<{ event: WebhookEnvelope; key: string }> = []\n let inProgress = 0\n try {\n for (const [index, event] of events.entries()) {\n const key = eventKey(provider.id, event, index, request.rawBody)\n const status = await this.idempotency.claimStatus(key, this.idempotencyTtlMs)\n if (status === 'acquired') accepted.push({ event, key })\n if (status === 'in_progress') inProgress++\n }\n } catch (err) {\n // Do not strand earlier claims when a later claim detects an unavailable\n // or corrupt shared store. The request remains failed closed.\n await Promise.allSettled(accepted.map(({ key }) => this.idempotency.release(key)))\n throw err\n }\n\n const delivery = await this.deliverEach(accepted)\n if (delivery.failed > 0 || inProgress > 0) {\n return {\n status: 503,\n body: {\n error: delivery.failed > 0 ? 'delivery_failed' : 'delivery_in_progress',\n received: delivery.succeeded,\n failed: delivery.failed,\n inProgress,\n total: events.length,\n },\n }\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(\n events: Array<{ event: WebhookEnvelope; key: string }>,\n ): Promise<{ succeeded: number; failed: number }> {\n let succeeded = 0\n let failed = 0\n for (const { event, key } of events) {\n try {\n await this.deliver(event)\n } catch (err) {\n try {\n await this.idempotency.release(key)\n } catch (releaseError) {\n this.onError(releaseError, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n }\n this.onError(err, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n failed++\n continue\n }\n try {\n await this.idempotency.complete(key)\n succeeded++\n } catch (err) {\n try {\n await this.idempotency.release(key)\n } catch (releaseError) {\n this.onError(releaseError, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n }\n this.onError(err, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n failed++\n }\n }\n return { succeeded, failed }\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\nfunction eventKey(providerId: string, event: WebhookEnvelope, index: number, rawBody: string): string {\n if (event.providerEventId?.trim()) return `${providerId}:id:${event.providerEventId.trim()}`\n // Providers without stable event ids still need collision-resistant keys.\n // A 32-bit hash could drop a distinct signed event after a birthday collision.\n const bodyHash = createHash('sha256').update(rawBody, 'utf8').digest('hex')\n return `${providerId}:body:${event.eventType}:${index}:${bodyHash}`\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'\nimport type { InboundEmailPayload } from '../conversation-events/index.js'\n\nexport type { InboundEmailPayload } from '../conversation-events/index.js'\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. */\n/**\n * Answer for Slack's app-config handshake, or `null` when the body is not one.\n *\n * Slack accepts an app install only when the response body carries the\n * `challenge` value from the request. `WebhookRouter` shapes its own 200 body\n * and `deliver()` returns void, so the handshake cannot be answered through the\n * router — call this in the HTTP handler ahead of `router.handle` and return\n * its result when it is not `null`.\n */\nexport function slackHandshakeResponse(rawBody: string): { challenge: string } | null {\n const parsed = safeJson(rawBody) as { type?: unknown; challenge?: unknown } | null\n if (!parsed || typeof parsed !== 'object') return null\n if (parsed.type !== 'url_verification') return null\n return { challenge: typeof parsed.challenge === 'string' ? parsed.challenge : '' }\n}\n\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`. */\n/**\n * Clerk webhooks, delivered by Svix. The signing secret is the `whsec_`\n * value Clerk shows for the endpoint; its base64 tail is the HMAC key. Svix\n * signs `${svix-id}.${svix-timestamp}.${body}` with HMAC-SHA256 and sends one\n * or more space-separated `v1,<base64>` signatures. Deliveries older than five\n * minutes are refused, so a captured request cannot be replayed later.\n */\nconst SVIX_TOLERANCE_SECONDS = 5 * 60\n\nexport const clerkWebhookProvider: WebhookProvider = {\n id: 'clerk',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const id = firstHeader(headers, 'svix-id')\n const timestamp = firstHeader(headers, 'svix-timestamp')\n const signatures = firstHeader(headers, 'svix-signature')\n if (!id || !timestamp || !signatures) return { valid: false, reason: 'missing_svix_headers' }\n const sent = Number(timestamp)\n if (!Number.isFinite(sent) || Math.abs(Date.now() / 1000 - sent) > SVIX_TOLERANCE_SECONDS) {\n return { valid: false, reason: 'stale_svix_timestamp' }\n }\n const encodedKey = secret.startsWith('whsec_') ? secret.slice('whsec_'.length) : secret\n let key: Buffer\n try {\n key = Buffer.from(encodedKey, 'base64')\n } catch {\n return { valid: false, reason: 'invalid_secret' }\n }\n if (key.length === 0) return { valid: false, reason: 'invalid_secret' }\n const expected = Buffer.from(createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64'))\n for (const entry of signatures.split(' ')) {\n const [version, signature] = entry.split(',', 2)\n if (version !== 'v1' || !signature) continue\n const actual = Buffer.from(signature)\n if (actual.length === expected.length && timingSafeEqual(actual, expected)) return { valid: true }\n }\n return { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as { type?: unknown; data?: unknown } | null\n if (!evt || typeof evt !== 'object' || typeof evt.type !== 'string') return []\n return [{\n provider: 'clerk',\n eventType: `clerk.${evt.type}`,\n providerEventId: firstHeader(headers, 'svix-id') ?? undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Clerk names events `<object>.<action>`; the vocabulary is Clerk's and open.\n namespace: 'clerk.',\n closed: false,\n events: [\n { id: 'clerk.user.created' },\n { id: 'clerk.user.updated' },\n { id: 'clerk.user.deleted' },\n { id: 'clerk.session.created' },\n { id: 'clerk.organization.created' },\n { id: 'clerk.organizationMembership.created' },\n { id: 'clerk.waitlistEntry.created' },\n ],\n },\n}\n\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/** 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","import { authenticateTwilioForm } from '../twilio/index.js'\nimport type { WebhookProvider } from './router.js'\n\n/** Bind to a trusted exact external route; forwarded Host headers are not a signing authority. */\nexport function createTwilioWebhookProvider(options: {\n url: string\n accountSid: string\n kind: 'message' | 'status'\n providerId?: 'twilio' | 'twilio-sms'\n}): WebhookProvider {\n if (!['message', 'status'].includes(options.kind)) throw new Error('Choose message or status webhook mode')\n const bound = { ...options }\n const providerId = bound.providerId ?? 'twilio'\n return {\n id: providerId,\n verifySignature({ rawBody, headers, secret }) {\n const key = Object.keys(headers).find(k => k.toLowerCase() === 'x-twilio-signature')\n const signature = key ? headers[key] : undefined\n try {\n authenticateTwilioForm({ url: bound.url, rawBody, signature, authToken: secret, accountSid: bound.accountSid })\n return { valid: true }\n } catch { return { valid: false, reason: 'invalid_twilio_form_or_signature' } }\n },\n parse({ rawBody, now }) {\n const data = Object.fromEntries(new URLSearchParams(rawBody))\n if (!/^(SM|MM)[a-f0-9]{32}$/i.test(data.MessageSid ?? '')) throw new Error('Twilio event has no valid message identity')\n const status = bound.kind === 'status' ? data.MessageStatus : 'received'\n if (!status || !/^[a-z_]+$/.test(status)) throw new Error('Twilio status event has no status')\n return [{ provider: providerId, eventType: `${providerId}.message.${bound.kind === 'status' ? 'status' : 'received'}`,\n // A message SID alone would incorrectly deduplicate all subsequent delivery states.\n providerEventId: `${bound.accountSid}:${data.MessageSid}:${bound.kind}:${status}`,\n receivedAt: now ?? Date.now(), payload: { ...data, __receivedAt: new Date(now ?? Date.now()).toISOString() }, headers: {} }]\n },\n successResponse: { body: '<Response/>', headers: { 'Content-Type': 'text/xml; charset=utf-8' } },\n eventCatalog: { namespace: `${providerId}.`, closed: true,\n events: [{ id: `${providerId}.message.received` }, { id: `${providerId}.message.status` }] },\n }\n}\n\n/** Register this provider for Twilio SMS/MMS Hub lines. The older constructor retains its twilio event IDs. */\nexport function createTwilioSmsWebhookProvider(options: {\n url: string\n accountSid: string\n kind: 'message' | 'status'\n}): WebhookProvider {\n return createTwilioWebhookProvider({ ...options, providerId: 'twilio-sms' })\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto'\nimport { verifyHmacSignature, verifyStripeSignature } from '../connectors/webhooks.js'\nimport type { WebhookEnvelope, WebhookHeaders, WebhookProvider } from './router.js'\n\n/** A repeated authentication header is ambiguous, never an invitation to pick one. */\nfunction header(headers: WebhookHeaders, name: string): string | null {\n const entries = Object.entries(headers).filter(([key]) => key.toLowerCase() === name)\n if (entries.length !== 1) return null\n const value = entries[0]?.[1]\n return typeof value === 'string' && value.length > 0 && value.length <= 4096 ? value : null\n}\n\nfunction fresh(timestamp: string | null, now: number): timestamp is string {\n return timestamp !== null && /^\\d{1,12}$/.test(timestamp)\n && Number.isSafeInteger(Number(timestamp))\n && Number.isFinite(now) && Math.abs(now - Number(timestamp)) <= 300\n}\n\n/** Inkbox signs the literal identity secret and request-id.timestamp.raw-body. */\nexport function verifyInkboxWebhook(rawBody: string, headers: WebhookHeaders, secret: string, now = Date.now() / 1000): boolean {\n const id = header(headers, 'x-inkbox-request-id')\n const timestamp = header(headers, 'x-inkbox-timestamp')\n const signature = header(headers, 'x-inkbox-signature')\n return Boolean(secret && id && signature && fresh(timestamp, now)\n && verifyHmacSignature(`${id}.${timestamp}.${rawBody}`, signature, secret, { signaturePrefix: 'sha256=' }))\n}\n\n/** Standard Webhooks signs the exact request bytes and three bounded headers. */\nfunction verifyStandardWebhook(rawBody: string, headers: WebhookHeaders, secret: string, prefix: 'webhook' | 'svix', now: number): boolean {\n const id = header(headers, `${prefix}-id`)\n const timestamp = header(headers, `${prefix}-timestamp`)\n const signature = header(headers, `${prefix}-signature`)\n if (!id || !signature || !fresh(timestamp, now) || !secret.startsWith('whsec_')) return false\n const encoded = secret.slice(6)\n if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) return false\n const key = Buffer.from(encoded, 'base64')\n if (key.length < 16 || key.toString('base64') !== encoded) return false\n const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64')\n return signature.split(' ').some((part) => part.startsWith('v1,')\n && part.slice(3).length === expected.length\n && timingSafeEqual(Buffer.from(part.slice(3)), Buffer.from(expected)))\n}\n\n/** Both Linq services use Standard Webhooks with a base64-decoded signing key. */\nexport function verifyLinqWebhook(rawBody: string, headers: WebhookHeaders, secret: string, now = Date.now() / 1000): boolean {\n return verifyStandardWebhook(rawBody, headers, secret, 'webhook', now)\n}\n\n/** Resend uses the Svix header names for the same signed-body protocol. */\nexport function verifyResendWebhook(rawBody: string, headers: WebhookHeaders, secret: string, now = Date.now() / 1000): boolean {\n return verifyStandardWebhook(rawBody, headers, secret, 'svix', now)\n}\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction parse(provider: string, typeField: string, idField: string, input: Parameters<WebhookProvider['parse']>[0]): WebhookEnvelope[] {\n if (Buffer.byteLength(input.rawBody, 'utf8') > 1_048_576) throw new Error('Webhook exceeds 1 MiB')\n const value: unknown = JSON.parse(input.rawBody)\n if (!object(value) || typeof value[typeField] !== 'string' || typeof value[idField] !== 'string'\n || !/^[a-z][a-z0-9_.-]{0,100}$/.test(value[typeField] as string)\n || !(value[idField] as string).length || (value[idField] as string).length > 256) {\n throw new Error('Provider event requires a bounded event type and stable id')\n }\n return [{ provider, eventType: `${provider}.${value[typeField]}`, providerEventId: value[idField] as string,\n receivedAt: input.now ?? Date.now(), payload: value, headers: {} }]\n}\n\nexport const inkboxWebhookProvider: WebhookProvider = {\n id: 'inkbox',\n verifySignature: ({ rawBody, headers, secret }) => verifyInkboxWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => parse('inkbox', 'event_type', 'id', input),\n eventCatalog: { namespace: 'inkbox.', closed: false, events: [\n { id: 'inkbox.imessage.received' }, { id: 'inkbox.text.received' }, { id: 'inkbox.message.received' },\n ] },\n}\n\nexport const linqWebhookProvider: WebhookProvider = {\n id: 'linq',\n verifySignature: ({ rawBody, headers, secret }) => verifyLinqWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => {\n const events = parse('linq', 'event_type', 'event_id', input)\n const id = header(input.headers, 'webhook-id')\n if (!id) throw new Error('Missing signed event identity')\n return events.map((event) => ({ ...event, providerEventId: id }))\n },\n eventCatalog: { namespace: 'linq.', closed: false, events: [{ id: 'linq.message.received' }] },\n}\n\nexport const contiguityWebhookProvider: WebhookProvider = {\n id: 'contiguity',\n verifySignature: ({ rawBody, headers, secret }) => {\n const signature = header(headers, 'contiguity-signature')\n return secret && signature && verifyStripeSignature(rawBody, signature, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse: (input) => parse('contiguity', 'type', 'id', input),\n eventCatalog: { namespace: 'contiguity.', closed: false, events: [\n { id: 'contiguity.imessage.incoming' }, { id: 'contiguity.text.incoming.sms' },\n { id: 'contiguity.text.incoming.mms' },\n ] },\n}\n\nexport const linqWhatsappWebhookProvider: WebhookProvider = {\n id: 'linq-whatsapp',\n verifySignature: ({ rawBody, headers, secret }) => verifyLinqWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => {\n const events = parse('linq-whatsapp', 'type', 'id', input)\n if (events[0]?.providerEventId !== header(input.headers, 'webhook-id')) throw new Error('Signed event identity mismatch')\n return events\n },\n eventCatalog: { namespace: 'linq-whatsapp.', closed: false, events: [{ id: 'linq-whatsapp.message.received' }] },\n}\n\nexport const resendWebhookProvider: WebhookProvider = {\n id: 'resend',\n verifySignature: ({ rawBody, headers, secret }) => verifyResendWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => {\n if (Buffer.byteLength(input.rawBody, 'utf8') > 1_048_576) throw new Error('Webhook exceeds 1 MiB')\n const value: unknown = JSON.parse(input.rawBody)\n if (!object(value) || value.type !== 'email.received' || !object(value.data)\n || typeof value.data.email_id !== 'string' || !/^[a-f\\d]{8}(?:-[a-f\\d]{4}){3}-[a-f\\d]{12}$/i.test(value.data.email_id)) {\n throw new Error('Resend event requires a received email id')\n }\n const signedId = header(input.headers, 'svix-id')\n if (!signedId) throw new Error('Missing signed event identity')\n return [{ provider: 'resend', eventType: 'resend.email.received', providerEventId: signedId,\n receivedAt: input.now ?? Date.now(), payload: value, headers: {} }]\n },\n eventCatalog: { namespace: 'resend.', closed: true, events: [{ id: 'resend.email.received' }] },\n}\n\n/** Sendblue sends a shared secret verbatim, with no signed timestamp. Deduplicate by message_handle. */\nexport const sendblueWebhookProvider: WebhookProvider = {\n id: 'sendblue',\n verifySignature: ({ headers, secret }) => {\n const supplied = header(headers, 'sb-signing-secret')\n if (!secret || !supplied) return { valid: false, reason: 'invalid_signature' }\n const actual = Buffer.from(supplied), expected = Buffer.from(secret)\n if (actual.length !== expected.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(actual, expected)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse: (input) => {\n if (Buffer.byteLength(input.rawBody, 'utf8') > 1_048_576) throw new Error('Webhook exceeds 1 MiB')\n const value: unknown = JSON.parse(input.rawBody)\n if (!object(value)) throw new Error('Sendblue webhook requires an object')\n const messageType = typeof value.message_type === 'string' ? value.message_type.toLowerCase() : null\n if (messageType !== 'message' && messageType !== 'group') return []\n if (typeof value.message_handle !== 'string' || !value.message_handle\n || value.message_handle.length > 256 || typeof value.is_outbound !== 'boolean') {\n throw new Error('Sendblue event requires a stable message handle and direction')\n }\n if (value.is_outbound) return []\n return [{ provider: 'sendblue', eventType: 'sendblue.message.received',\n providerEventId: value.message_handle, receivedAt: input.now ?? Date.now(), payload: value, headers: {} }]\n },\n eventCatalog: { namespace: 'sendblue.', closed: true, events: [{ id: 'sendblue.message.received' }] },\n}\n","import { verifyHmacSignature, firstHeader } from '../connectors/webhooks.js'\nimport type { WebhookProvider, WebhookEnvelope } from './router.js'\n\nexport type MetaWhatsappProvider = 'whatsapp' | 'whatsapp-business'\nconst record = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v)\nconst graphId = (v: unknown): v is string => typeof v === 'string' && /^[0-9]{1,64}$/.test(v)\n\n/** Bind one callback to one WABA and number. The app secret authenticates Meta,\n * not the tenant: app-wide signatures alone cannot establish account ownership. */\nexport function createWhatsappWebhookProvider(options: {\n providerId: MetaWhatsappProvider\n wabaId: string\n phoneNumberId: string\n}): WebhookProvider {\n if (!graphId(options.wabaId) || !graphId(options.phoneNumberId)) throw new Error('Meta webhook requires a WABA and phone number ID')\n return {\n id: options.providerId,\n verifySignature({ rawBody, headers, secret }) {\n const signature = firstHeader(headers, 'x-hub-signature-256')\n return signature && /^sha256=[a-f0-9]{64}$/.test(signature)\n && verifyHmacSignature(rawBody, signature, secret, { algorithm: 'sha256', signaturePrefix: 'sha256=' })\n ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }) {\n const body: unknown = JSON.parse(rawBody)\n if (!record(body) || body.object !== 'whatsapp_business_account' || !Array.isArray(body.entry) || body.entry.length > 100) throw new Error('Invalid WhatsApp webhook envelope')\n const events: WebhookEnvelope[] = []\n for (const entry of body.entry) {\n if (!record(entry) || !Array.isArray(entry.changes) || entry.changes.length > 100) throw new Error('Invalid WhatsApp entry')\n if (entry.id !== options.wabaId) continue\n for (const change of entry.changes) {\n if (!record(change) || change.field !== 'messages' || !record(change.value)) continue\n const value = change.value\n if (value.messaging_product !== 'whatsapp' || !record(value.metadata)\n || value.metadata.phone_number_id !== options.phoneNumberId) continue\n if (value.messages === undefined) continue // Delivery status is not an incoming command.\n if (!Array.isArray(value.messages) || value.messages.length > 100) throw new Error('Invalid WhatsApp messages')\n for (const message of value.messages) {\n if (!record(message) || typeof message.id !== 'string' || !message.id || message.id.length > 256) throw new Error('Invalid WhatsApp message identity')\n events.push({ provider: options.providerId, eventType: `${options.providerId}.message.received`,\n providerEventId: `${options.phoneNumberId}:${message.id}`, receivedAt: now ?? Date.now(),\n payload: { wabaId: options.wabaId, phoneNumberId: options.phoneNumberId, message },\n headers: Object.fromEntries(Object.entries(headers).flatMap(([k,v]) => typeof v === 'string' ? [[k.toLowerCase(), v]] : [])),\n })\n }\n }\n }\n return events\n },\n eventCatalog: { namespace: `${options.providerId}.`, closed: true, events: [{ id: `${options.providerId}.message.received` }] },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/webhooks/router.ts","../../src/webhooks/providers.ts","../../src/webhooks/twilio.ts","../../src/webhooks/messaging.ts","../../src/webhooks/whatsapp.ts","../../src/webhooks/cloudbeds.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. Awaits the consumer-supplied `deliver(event)` callback.\n * 5. Returns 2xx only after every accepted event finishes durably. A failed\n * or concurrently active delivery returns 503 so the provider retries.\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 atomically claims every\n * parsed event before scheduling `deliver()`. Consumers wire this to a\n * durable SETNX/unique insert (D1, Redis, or Postgres) across workers.\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), and 503 (delivery\n * failed or another worker still owns the delivery).\n */\n\nimport { createHash } from 'node:crypto'\nimport {\n FileSystemAtomicIdempotencyStore,\n InMemoryAtomicIdempotencyStore,\n resolveAtomicIdempotencyStore,\n type AtomicIdempotencyStore,\n type IdempotencyRuntime,\n} from '../idempotency.js'\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 extends AtomicIdempotencyStore {}\n\n/** Process-local atomic store for tests and explicitly single-process apps. */\nexport class InMemoryWebhookIdempotencyStore extends InMemoryAtomicIdempotencyStore implements WebhookIdempotencyStore {}\n\n/** Durable store for workers that share the same filesystem directory. */\nexport class FileSystemWebhookIdempotencyStore extends FileSystemAtomicIdempotencyStore implements WebhookIdempotencyStore {}\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. The callback must\n * finish its durable enqueue or processing before it resolves. */\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 /** Atomic idempotency store. Production requires shared atomic storage. */\n idempotency?: WebhookIdempotencyStore\n /** Runtime controls the safe default. Production fails closed without a\n * shared store; test/development use an in-memory store when omitted. */\n runtime?: IdempotencyRuntime\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 = resolveAtomicIdempotencyStore({\n component: 'WebhookRouter',\n store: opts.idempotency,\n runtime: opts.runtime,\n })\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: Array<{ event: WebhookEnvelope; key: string }> = []\n let inProgress = 0\n try {\n for (const [index, event] of events.entries()) {\n const key = eventKey(provider.id, event, index, request.rawBody)\n const status = await this.idempotency.claimStatus(key, this.idempotencyTtlMs)\n if (status === 'acquired') accepted.push({ event, key })\n if (status === 'in_progress') inProgress++\n }\n } catch (err) {\n // Do not strand earlier claims when a later claim detects an unavailable\n // or corrupt shared store. The request remains failed closed.\n await Promise.allSettled(accepted.map(({ key }) => this.idempotency.release(key)))\n throw err\n }\n\n const delivery = await this.deliverEach(accepted)\n if (delivery.failed > 0 || inProgress > 0) {\n return {\n status: 503,\n body: {\n error: delivery.failed > 0 ? 'delivery_failed' : 'delivery_in_progress',\n received: delivery.succeeded,\n failed: delivery.failed,\n inProgress,\n total: events.length,\n },\n }\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(\n events: Array<{ event: WebhookEnvelope; key: string }>,\n ): Promise<{ succeeded: number; failed: number }> {\n let succeeded = 0\n let failed = 0\n for (const { event, key } of events) {\n try {\n await this.deliver(event)\n } catch (err) {\n try {\n await this.idempotency.release(key)\n } catch (releaseError) {\n this.onError(releaseError, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n }\n this.onError(err, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n failed++\n continue\n }\n try {\n await this.idempotency.complete(key)\n succeeded++\n } catch (err) {\n try {\n await this.idempotency.release(key)\n } catch (releaseError) {\n this.onError(releaseError, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n }\n this.onError(err, {\n provider: event.provider,\n eventType: event.eventType,\n providerEventId: event.providerEventId,\n })\n failed++\n }\n }\n return { succeeded, failed }\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\nfunction eventKey(providerId: string, event: WebhookEnvelope, index: number, rawBody: string): string {\n if (event.providerEventId?.trim()) return `${providerId}:id:${event.providerEventId.trim()}`\n // Providers without stable event ids still need collision-resistant keys.\n // A 32-bit hash could drop a distinct signed event after a birthday collision.\n const bodyHash = createHash('sha256').update(rawBody, 'utf8').digest('hex')\n return `${providerId}:body:${event.eventType}:${index}:${bodyHash}`\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'\nimport type { InboundEmailPayload } from '../conversation-events/index.js'\n\nexport type { InboundEmailPayload } from '../conversation-events/index.js'\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. */\n/**\n * Answer for Slack's app-config handshake, or `null` when the body is not one.\n *\n * Slack accepts an app install only when the response body carries the\n * `challenge` value from the request. `WebhookRouter` shapes its own 200 body\n * and `deliver()` returns void, so the handshake cannot be answered through the\n * router — call this in the HTTP handler ahead of `router.handle` and return\n * its result when it is not `null`.\n */\nexport function slackHandshakeResponse(rawBody: string): { challenge: string } | null {\n const parsed = safeJson(rawBody) as { type?: unknown; challenge?: unknown } | null\n if (!parsed || typeof parsed !== 'object') return null\n if (parsed.type !== 'url_verification') return null\n return { challenge: typeof parsed.challenge === 'string' ? parsed.challenge : '' }\n}\n\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`. */\n/**\n * Clerk webhooks, delivered by Svix. The signing secret is the `whsec_`\n * value Clerk shows for the endpoint; its base64 tail is the HMAC key. Svix\n * signs `${svix-id}.${svix-timestamp}.${body}` with HMAC-SHA256 and sends one\n * or more space-separated `v1,<base64>` signatures. Deliveries older than five\n * minutes are refused, so a captured request cannot be replayed later.\n */\nconst SVIX_TOLERANCE_SECONDS = 5 * 60\n\nexport const clerkWebhookProvider: WebhookProvider = {\n id: 'clerk',\n verifySignature({ rawBody, headers, secret }): SignatureVerification {\n const id = firstHeader(headers, 'svix-id')\n const timestamp = firstHeader(headers, 'svix-timestamp')\n const signatures = firstHeader(headers, 'svix-signature')\n if (!id || !timestamp || !signatures) return { valid: false, reason: 'missing_svix_headers' }\n const sent = Number(timestamp)\n if (!Number.isFinite(sent) || Math.abs(Date.now() / 1000 - sent) > SVIX_TOLERANCE_SECONDS) {\n return { valid: false, reason: 'stale_svix_timestamp' }\n }\n const encodedKey = secret.startsWith('whsec_') ? secret.slice('whsec_'.length) : secret\n let key: Buffer\n try {\n key = Buffer.from(encodedKey, 'base64')\n } catch {\n return { valid: false, reason: 'invalid_secret' }\n }\n if (key.length === 0) return { valid: false, reason: 'invalid_secret' }\n const expected = Buffer.from(createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64'))\n for (const entry of signatures.split(' ')) {\n const [version, signature] = entry.split(',', 2)\n if (version !== 'v1' || !signature) continue\n const actual = Buffer.from(signature)\n if (actual.length === expected.length && timingSafeEqual(actual, expected)) return { valid: true }\n }\n return { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n const evt = safeJson(rawBody) as { type?: unknown; data?: unknown } | null\n if (!evt || typeof evt !== 'object' || typeof evt.type !== 'string') return []\n return [{\n provider: 'clerk',\n eventType: `clerk.${evt.type}`,\n providerEventId: firstHeader(headers, 'svix-id') ?? undefined,\n receivedAt: now ?? Date.now(),\n payload: evt,\n headers: normalizeHeaders(headers),\n }]\n },\n eventCatalog: {\n // Clerk names events `<object>.<action>`; the vocabulary is Clerk's and open.\n namespace: 'clerk.',\n closed: false,\n events: [\n { id: 'clerk.user.created' },\n { id: 'clerk.user.updated' },\n { id: 'clerk.user.deleted' },\n { id: 'clerk.session.created' },\n { id: 'clerk.organization.created' },\n { id: 'clerk.organizationMembership.created' },\n { id: 'clerk.waitlistEntry.created' },\n ],\n },\n}\n\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/** 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","import { authenticateTwilioForm } from '../twilio/index.js'\nimport type { WebhookProvider } from './router.js'\n\n/** Bind to a trusted exact external route; forwarded Host headers are not a signing authority. */\nexport function createTwilioWebhookProvider(options: {\n url: string\n accountSid: string\n kind: 'message' | 'status'\n providerId?: 'twilio' | 'twilio-sms'\n}): WebhookProvider {\n if (!['message', 'status'].includes(options.kind)) throw new Error('Choose message or status webhook mode')\n const bound = { ...options }\n const providerId = bound.providerId ?? 'twilio'\n return {\n id: providerId,\n verifySignature({ rawBody, headers, secret }) {\n const key = Object.keys(headers).find(k => k.toLowerCase() === 'x-twilio-signature')\n const signature = key ? headers[key] : undefined\n try {\n authenticateTwilioForm({ url: bound.url, rawBody, signature, authToken: secret, accountSid: bound.accountSid })\n return { valid: true }\n } catch { return { valid: false, reason: 'invalid_twilio_form_or_signature' } }\n },\n parse({ rawBody, now }) {\n const data = Object.fromEntries(new URLSearchParams(rawBody))\n if (!/^(SM|MM)[a-f0-9]{32}$/i.test(data.MessageSid ?? '')) throw new Error('Twilio event has no valid message identity')\n const status = bound.kind === 'status' ? data.MessageStatus : 'received'\n if (!status || !/^[a-z_]+$/.test(status)) throw new Error('Twilio status event has no status')\n return [{ provider: providerId, eventType: `${providerId}.message.${bound.kind === 'status' ? 'status' : 'received'}`,\n // A message SID alone would incorrectly deduplicate all subsequent delivery states.\n providerEventId: `${bound.accountSid}:${data.MessageSid}:${bound.kind}:${status}`,\n receivedAt: now ?? Date.now(), payload: { ...data, __receivedAt: new Date(now ?? Date.now()).toISOString() }, headers: {} }]\n },\n successResponse: { body: '<Response/>', headers: { 'Content-Type': 'text/xml; charset=utf-8' } },\n eventCatalog: { namespace: `${providerId}.`, closed: true,\n events: [{ id: `${providerId}.message.received` }, { id: `${providerId}.message.status` }] },\n }\n}\n\n/** Register this provider for Twilio SMS/MMS Hub lines. The older constructor retains its twilio event IDs. */\nexport function createTwilioSmsWebhookProvider(options: {\n url: string\n accountSid: string\n kind: 'message' | 'status'\n}): WebhookProvider {\n return createTwilioWebhookProvider({ ...options, providerId: 'twilio-sms' })\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto'\nimport { verifyHmacSignature, verifyStripeSignature } from '../connectors/webhooks.js'\nimport type { WebhookEnvelope, WebhookHeaders, WebhookProvider } from './router.js'\n\n/** A repeated authentication header is ambiguous, never an invitation to pick one. */\nfunction header(headers: WebhookHeaders, name: string): string | null {\n const entries = Object.entries(headers).filter(([key]) => key.toLowerCase() === name)\n if (entries.length !== 1) return null\n const value = entries[0]?.[1]\n return typeof value === 'string' && value.length > 0 && value.length <= 4096 ? value : null\n}\n\nfunction fresh(timestamp: string | null, now: number): timestamp is string {\n return timestamp !== null && /^\\d{1,12}$/.test(timestamp)\n && Number.isSafeInteger(Number(timestamp))\n && Number.isFinite(now) && Math.abs(now - Number(timestamp)) <= 300\n}\n\n/** Inkbox signs the literal identity secret and request-id.timestamp.raw-body. */\nexport function verifyInkboxWebhook(rawBody: string, headers: WebhookHeaders, secret: string, now = Date.now() / 1000): boolean {\n const id = header(headers, 'x-inkbox-request-id')\n const timestamp = header(headers, 'x-inkbox-timestamp')\n const signature = header(headers, 'x-inkbox-signature')\n return Boolean(secret && id && signature && fresh(timestamp, now)\n && verifyHmacSignature(`${id}.${timestamp}.${rawBody}`, signature, secret, { signaturePrefix: 'sha256=' }))\n}\n\n/** Standard Webhooks signs the exact request bytes and three bounded headers. */\nfunction verifyStandardWebhook(rawBody: string, headers: WebhookHeaders, secret: string, prefix: 'webhook' | 'svix', now: number): boolean {\n const id = header(headers, `${prefix}-id`)\n const timestamp = header(headers, `${prefix}-timestamp`)\n const signature = header(headers, `${prefix}-signature`)\n if (!id || !signature || !fresh(timestamp, now) || !secret.startsWith('whsec_')) return false\n const encoded = secret.slice(6)\n if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) return false\n const key = Buffer.from(encoded, 'base64')\n if (key.length < 16 || key.toString('base64') !== encoded) return false\n const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${rawBody}`).digest('base64')\n return signature.split(' ').some((part) => part.startsWith('v1,')\n && part.slice(3).length === expected.length\n && timingSafeEqual(Buffer.from(part.slice(3)), Buffer.from(expected)))\n}\n\n/** Both Linq services use Standard Webhooks with a base64-decoded signing key. */\nexport function verifyLinqWebhook(rawBody: string, headers: WebhookHeaders, secret: string, now = Date.now() / 1000): boolean {\n return verifyStandardWebhook(rawBody, headers, secret, 'webhook', now)\n}\n\n/** Resend uses the Svix header names for the same signed-body protocol. */\nexport function verifyResendWebhook(rawBody: string, headers: WebhookHeaders, secret: string, now = Date.now() / 1000): boolean {\n return verifyStandardWebhook(rawBody, headers, secret, 'svix', now)\n}\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction parse(provider: string, typeField: string, idField: string, input: Parameters<WebhookProvider['parse']>[0]): WebhookEnvelope[] {\n if (Buffer.byteLength(input.rawBody, 'utf8') > 1_048_576) throw new Error('Webhook exceeds 1 MiB')\n const value: unknown = JSON.parse(input.rawBody)\n if (!object(value) || typeof value[typeField] !== 'string' || typeof value[idField] !== 'string'\n || !/^[a-z][a-z0-9_.-]{0,100}$/.test(value[typeField] as string)\n || !(value[idField] as string).length || (value[idField] as string).length > 256) {\n throw new Error('Provider event requires a bounded event type and stable id')\n }\n return [{ provider, eventType: `${provider}.${value[typeField]}`, providerEventId: value[idField] as string,\n receivedAt: input.now ?? Date.now(), payload: value, headers: {} }]\n}\n\nexport const inkboxWebhookProvider: WebhookProvider = {\n id: 'inkbox',\n verifySignature: ({ rawBody, headers, secret }) => verifyInkboxWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => parse('inkbox', 'event_type', 'id', input),\n eventCatalog: { namespace: 'inkbox.', closed: false, events: [\n { id: 'inkbox.imessage.received' }, { id: 'inkbox.text.received' }, { id: 'inkbox.message.received' },\n ] },\n}\n\nexport const linqWebhookProvider: WebhookProvider = {\n id: 'linq',\n verifySignature: ({ rawBody, headers, secret }) => verifyLinqWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => {\n const events = parse('linq', 'event_type', 'event_id', input)\n const id = header(input.headers, 'webhook-id')\n if (!id) throw new Error('Missing signed event identity')\n return events.map((event) => ({ ...event, providerEventId: id }))\n },\n eventCatalog: { namespace: 'linq.', closed: false, events: [{ id: 'linq.message.received' }] },\n}\n\nexport const contiguityWebhookProvider: WebhookProvider = {\n id: 'contiguity',\n verifySignature: ({ rawBody, headers, secret }) => {\n const signature = header(headers, 'contiguity-signature')\n return secret && signature && verifyStripeSignature(rawBody, signature, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse: (input) => parse('contiguity', 'type', 'id', input),\n eventCatalog: { namespace: 'contiguity.', closed: false, events: [\n { id: 'contiguity.imessage.incoming' }, { id: 'contiguity.text.incoming.sms' },\n { id: 'contiguity.text.incoming.mms' },\n ] },\n}\n\nexport const linqWhatsappWebhookProvider: WebhookProvider = {\n id: 'linq-whatsapp',\n verifySignature: ({ rawBody, headers, secret }) => verifyLinqWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => {\n const events = parse('linq-whatsapp', 'type', 'id', input)\n if (events[0]?.providerEventId !== header(input.headers, 'webhook-id')) throw new Error('Signed event identity mismatch')\n return events\n },\n eventCatalog: { namespace: 'linq-whatsapp.', closed: false, events: [{ id: 'linq-whatsapp.message.received' }] },\n}\n\nexport const resendWebhookProvider: WebhookProvider = {\n id: 'resend',\n verifySignature: ({ rawBody, headers, secret }) => verifyResendWebhook(rawBody, headers, secret)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' },\n parse: (input) => {\n if (Buffer.byteLength(input.rawBody, 'utf8') > 1_048_576) throw new Error('Webhook exceeds 1 MiB')\n const value: unknown = JSON.parse(input.rawBody)\n if (!object(value) || value.type !== 'email.received' || !object(value.data)\n || typeof value.data.email_id !== 'string' || !/^[a-f\\d]{8}(?:-[a-f\\d]{4}){3}-[a-f\\d]{12}$/i.test(value.data.email_id)) {\n throw new Error('Resend event requires a received email id')\n }\n const signedId = header(input.headers, 'svix-id')\n if (!signedId) throw new Error('Missing signed event identity')\n return [{ provider: 'resend', eventType: 'resend.email.received', providerEventId: signedId,\n receivedAt: input.now ?? Date.now(), payload: value, headers: {} }]\n },\n eventCatalog: { namespace: 'resend.', closed: true, events: [{ id: 'resend.email.received' }] },\n}\n\n/** Sendblue sends a shared secret verbatim, with no signed timestamp. Deduplicate by message_handle. */\nexport const sendblueWebhookProvider: WebhookProvider = {\n id: 'sendblue',\n verifySignature: ({ headers, secret }) => {\n const supplied = header(headers, 'sb-signing-secret')\n if (!secret || !supplied) return { valid: false, reason: 'invalid_signature' }\n const actual = Buffer.from(supplied), expected = Buffer.from(secret)\n if (actual.length !== expected.length) return { valid: false, reason: 'invalid_signature' }\n return timingSafeEqual(actual, expected)\n ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse: (input) => {\n if (Buffer.byteLength(input.rawBody, 'utf8') > 1_048_576) throw new Error('Webhook exceeds 1 MiB')\n const value: unknown = JSON.parse(input.rawBody)\n if (!object(value)) throw new Error('Sendblue webhook requires an object')\n const messageType = typeof value.message_type === 'string' ? value.message_type.toLowerCase() : null\n if (messageType !== 'message' && messageType !== 'group') return []\n if (typeof value.message_handle !== 'string' || !value.message_handle\n || value.message_handle.length > 256 || typeof value.is_outbound !== 'boolean') {\n throw new Error('Sendblue event requires a stable message handle and direction')\n }\n if (value.is_outbound) return []\n return [{ provider: 'sendblue', eventType: 'sendblue.message.received',\n providerEventId: value.message_handle, receivedAt: input.now ?? Date.now(), payload: value, headers: {} }]\n },\n eventCatalog: { namespace: 'sendblue.', closed: true, events: [{ id: 'sendblue.message.received' }] },\n}\n","import { verifyHmacSignature, firstHeader } from '../connectors/webhooks.js'\nimport type { WebhookProvider, WebhookEnvelope } from './router.js'\n\nexport type MetaWhatsappProvider = 'whatsapp' | 'whatsapp-business'\nconst record = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v)\nconst graphId = (v: unknown): v is string => typeof v === 'string' && /^[0-9]{1,64}$/.test(v)\n\n/** Bind one callback to one WABA and number. The app secret authenticates Meta,\n * not the tenant: app-wide signatures alone cannot establish account ownership. */\nexport function createWhatsappWebhookProvider(options: {\n providerId: MetaWhatsappProvider\n wabaId: string\n phoneNumberId: string\n}): WebhookProvider {\n if (!graphId(options.wabaId) || !graphId(options.phoneNumberId)) throw new Error('Meta webhook requires a WABA and phone number ID')\n return {\n id: options.providerId,\n verifySignature({ rawBody, headers, secret }) {\n const signature = firstHeader(headers, 'x-hub-signature-256')\n return signature && /^sha256=[a-f0-9]{64}$/.test(signature)\n && verifyHmacSignature(rawBody, signature, secret, { algorithm: 'sha256', signaturePrefix: 'sha256=' })\n ? { valid: true } : { valid: false, reason: 'invalid_signature' }\n },\n parse({ rawBody, headers, now }) {\n const body: unknown = JSON.parse(rawBody)\n if (!record(body) || body.object !== 'whatsapp_business_account' || !Array.isArray(body.entry) || body.entry.length > 100) throw new Error('Invalid WhatsApp webhook envelope')\n const events: WebhookEnvelope[] = []\n for (const entry of body.entry) {\n if (!record(entry) || !Array.isArray(entry.changes) || entry.changes.length > 100) throw new Error('Invalid WhatsApp entry')\n if (entry.id !== options.wabaId) continue\n for (const change of entry.changes) {\n if (!record(change) || change.field !== 'messages' || !record(change.value)) continue\n const value = change.value\n if (value.messaging_product !== 'whatsapp' || !record(value.metadata)\n || value.metadata.phone_number_id !== options.phoneNumberId) continue\n if (value.messages === undefined) continue // Delivery status is not an incoming command.\n if (!Array.isArray(value.messages) || value.messages.length > 100) throw new Error('Invalid WhatsApp messages')\n for (const message of value.messages) {\n if (!record(message) || typeof message.id !== 'string' || !message.id || message.id.length > 256) throw new Error('Invalid WhatsApp message identity')\n events.push({ provider: options.providerId, eventType: `${options.providerId}.message.received`,\n providerEventId: `${options.phoneNumberId}:${message.id}`, receivedAt: now ?? Date.now(),\n payload: { wabaId: options.wabaId, phoneNumberId: options.phoneNumberId, message },\n headers: Object.fromEntries(Object.entries(headers).flatMap(([k,v]) => typeof v === 'string' ? [[k.toLowerCase(), v]] : [])),\n })\n }\n }\n }\n return events\n },\n eventCatalog: { namespace: `${options.providerId}.`, closed: true, events: [{ id: `${options.providerId}.message.received` }] },\n }\n}\n","/**\n * Cloudbeds PMS webhook provider.\n *\n * Cloudbeds does not sign webhook bodies. `postWebhook` accepts an\n * `authHeaderName`/`authHeaderValue` pair, sends that header on every delivery,\n * never returns the value from `getWebhooks`, and rotates it on re-post\n * (pms-v1.3 PostWebhookRequest). Subscribe with `authHeaderName` set to\n * `CLOUDBEDS_WEBHOOK_AUTH_HEADER` and a per-connection secret as the value; the\n * verifier compares that header with the secret in constant time.\n *\n * The header proves the delivery came from a subscription holding the secret,\n * but it does not bind the body. Every envelope is a hint: consumers re-read the\n * reservation through `reservations.get` before acting on it.\n */\n\nimport { createHash, timingSafeEqual } from 'node:crypto'\nimport { CLOUDBEDS_WEBHOOK_EVENTS } from '../connectors/adapters/cloudbeds.js'\nimport { firstHeader } from '../connectors/webhooks.js'\nimport type { SignatureVerification, WebhookEnvelope, WebhookHeaders, WebhookProvider } from './router.js'\n\n/** Header name to pass as `authHeaderName` to `webhooks.subscribe`. */\nexport const CLOUDBEDS_WEBHOOK_AUTH_HEADER = 'x-tangle-webhook-token'\n\nconst EVENTS = new Set<string>(CLOUDBEDS_WEBHOOK_EVENTS.map(({ object, action }) => `${object}/${action}`))\nconst NUMERIC_ID = /^[1-9]\\d{0,19}$/\nconst RESERVATION_ID = /^[A-Za-z0-9_-]{1,64}$/\n\nfunction idText(value: unknown, pattern: RegExp): string | null {\n const text = typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : value\n return typeof text === 'string' && pattern.test(text) ? text : null\n}\n\nfunction headersOf(headers: WebhookHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined || key.toLowerCase() === CLOUDBEDS_WEBHOOK_AUTH_HEADER) continue\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\nexport const cloudbedsWebhookProvider: WebhookProvider = {\n id: 'cloudbeds',\n verifySignature({ headers, secret }): SignatureVerification {\n const token = firstHeader(headers, CLOUDBEDS_WEBHOOK_AUTH_HEADER)\n if (!token) return { valid: false, reason: 'missing_cloudbeds_webhook_token' }\n if (!secret) return { valid: false, reason: 'missing_secret' }\n const actual = Buffer.from(token, 'utf-8')\n const expected = Buffer.from(secret, 'utf-8')\n if (actual.length !== expected.length) return { valid: false, reason: 'invalid_webhook_token' }\n return timingSafeEqual(actual, expected) ? { valid: true } : { valid: false, reason: 'invalid_webhook_token' }\n },\n parse({ rawBody, headers, now }): WebhookEnvelope[] {\n let body: unknown\n try {\n body = JSON.parse(rawBody)\n } catch {\n throw new Error('cloudbeds webhook body is not JSON')\n }\n if (!body || typeof body !== 'object' || Array.isArray(body)) throw new Error('cloudbeds webhook body is not an object')\n const evt = body as Record<string, unknown>\n // Unsubscribed events are acknowledged as no-ops so Cloudbeds stops retrying.\n if (typeof evt.event !== 'string' || !EVENTS.has(evt.event)) return []\n // The Cloudbeds guide warns that ID spelling varies by event.\n const propertyId = idText(evt.propertyID, NUMERIC_ID) ?? idText(evt.propertyID_str, NUMERIC_ID) ??\n idText(evt.propertyId, NUMERIC_ID) ?? idText(evt.propertyId_str, NUMERIC_ID)\n const reservationId = idText(evt.reservationID, RESERVATION_ID) ?? idText(evt.reservationId, RESERVATION_ID)\n if (!propertyId || !reservationId) throw new Error('cloudbeds webhook is missing propertyId or reservationId')\n const timestamp = typeof evt.timestamp === 'number' || typeof evt.timestamp === 'string' ? String(evt.timestamp) : ''\n const [object, action] = evt.event.split('/')\n return [{\n provider: 'cloudbeds',\n eventType: `cloudbeds.${object}.${action}`,\n // Cloudbeds keeps the original timestamp across its five retries.\n providerEventId: createHash('sha256').update(`${evt.event}|${propertyId}|${reservationId}|${timestamp}`).digest('hex'),\n receivedAt: now ?? Date.now(),\n payload: { ...evt, propertyId, reservationId },\n headers: headersOf(headers),\n }]\n },\n eventCatalog: {\n // Derived from the same allowlist `webhooks.subscribe` accepts and `parse` emits.\n namespace: 'cloudbeds.',\n closed: true,\n events: CLOUDBEDS_WEBHOOK_EVENTS.map(({ object, action }) => ({ id: `cloudbeds.${object}.${action}` })),\n },\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6IA,IAAa,kCAAb,cAAqD,+BAAkE,CAAC;;AAGxH,IAAa,oCAAb,cAAuD,iCAAoE,CAAC;;;;;AAyC5H,IAAa,gBAAb,MAA2B;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,MAA4B;EACtC,KAAK,YAAY,IAAI,IAAI,KAAK,UAAU,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;EAC7D,KAAK,UAAU,KAAK;EACpB,KAAK,gBAAgB,KAAK;EAC1B,KAAK,cAAc,8BAA8B;GAC/C,WAAW;GACX,OAAO,KAAK;GACZ,SAAS,KAAK;EAChB,CAAC;EACD,KAAK,mBAAmB,KAAK,oBAAoB;EACjD,KAAK,UAAU,KAAK,WAAW;EAC/B,KAAK,QAAQ,KAAK,OAAO,KAAK;CAChC;;;CAIA,MAAM,OAAO,SAA+D;EAC1E,MAAM,WAAW,KAAK,UAAU,IAAI,QAAQ,UAAU;EACtD,IAAI,CAAC,UACH,OAAO;GAAE,QAAQ;GAAK,MAAM;IAAE,OAAO;IAAoB,UAAU,QAAQ;GAAW;EAAE;EAE1F,MAAM,SAAS,MAAM,KAAK,cAAc,SAAS,IAAI,QAAQ,OAAO;EACpE,IAAI,CAAC,QACH,OAAO;GAAE,QAAQ;GAAK,MAAM;IAAE,OAAO;IAAkB,UAAU,SAAS;GAAG;EAAE;EAEjF,MAAM,eAAe,SAAS,gBAAgB;GAC5C,SAAS,QAAQ;GACjB,SAAS,QAAQ;GACjB;EACF,CAAC;EACD,IAAI,CAAC,aAAa,OAChB,OAAO;GAAE,QAAQ;GAAK,MAAM;IAAE,OAAO;IAAqB,QAAQ,aAAa;GAAO;EAAE;EAG1F,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,SAAS,MAAM;IAAE,SAAS,QAAQ;IAAS,SAAS,QAAQ;IAAS,KAAK,KAAK,MAAM;GAAE,CAAC;EACzG,SAAS,KAAK;GACZ,KAAK,QAAQ,KAAK,EAAE,UAAU,SAAS,GAAG,CAAC;GAC3C,OAAO;IAAE,QAAQ;IAAK,MAAM;KAAE,OAAO;KAAe,SAAS,WAAW,GAAG;IAAE;GAAE;EACjF;EAEA,MAAM,WAA2D,CAAC;EAClE,IAAI,aAAa;EACjB,IAAI;GACF,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAAG;IAC7C,MAAM,MAAM,SAAS,SAAS,IAAI,OAAO,OAAO,QAAQ,OAAO;IAC/D,MAAM,SAAS,MAAM,KAAK,YAAY,YAAY,KAAK,KAAK,gBAAgB;IAC5E,IAAI,WAAW,YAAY,SAAS,KAAK;KAAE;KAAO;IAAI,CAAC;IACvD,IAAI,WAAW,eAAe;GAChC;EACF,SAAS,KAAK;GAGZ,MAAM,QAAQ,WAAW,SAAS,KAAK,EAAE,UAAU,KAAK,YAAY,QAAQ,GAAG,CAAC,CAAC;GACjF,MAAM;EACR;EAEA,MAAM,WAAW,MAAM,KAAK,YAAY,QAAQ;EAChD,IAAI,SAAS,SAAS,KAAK,aAAa,GACtC,OAAO;GACL,QAAQ;GACR,MAAM;IACJ,OAAO,SAAS,SAAS,IAAI,oBAAoB;IACjD,UAAU,SAAS;IACnB,QAAQ,SAAS;IACjB;IACA,OAAO,OAAO;GAChB;EACF;EAGF,IAAI,SAAS,iBACX,OAAO;GACL,QAAQ;GACR,MAAM,SAAS,gBAAgB;GAC/B,SAAS,SAAS,gBAAgB;EACpC;EAEF,OAAO;GAAE,QAAQ;GAAK,MAAM;IAAE,UAAU,SAAS;IAAQ,OAAO,OAAO;GAAO;EAAE;CAClF;CAEA,MAAc,YACZ,QACgD;EAChD,IAAI,YAAY;EAChB,IAAI,SAAS;EACb,KAAK,MAAM,EAAE,OAAO,SAAS,QAAQ;GACnC,IAAI;IACF,MAAM,KAAK,QAAQ,KAAK;GAC1B,SAAS,KAAK;IACZ,IAAI;KACF,MAAM,KAAK,YAAY,QAAQ,GAAG;IACpC,SAAS,cAAc;KACrB,KAAK,QAAQ,cAAc;MACzB,UAAU,MAAM;MAChB,WAAW,MAAM;MACjB,iBAAiB,MAAM;KACzB,CAAC;IACH;IACA,KAAK,QAAQ,KAAK;KAChB,UAAU,MAAM;KAChB,WAAW,MAAM;KACjB,iBAAiB,MAAM;IACzB,CAAC;IACD;IACA;GACF;GACA,IAAI;IACF,MAAM,KAAK,YAAY,SAAS,GAAG;IACnC;GACF,SAAS,KAAK;IACZ,IAAI;KACF,MAAM,KAAK,YAAY,QAAQ,GAAG;IACpC,SAAS,cAAc;KACrB,KAAK,QAAQ,cAAc;MACzB,UAAU,MAAM;MAChB,WAAW,MAAM;MACjB,iBAAiB,MAAM;KACzB,CAAC;IACH;IACA,KAAK,QAAQ,KAAK;KAChB,UAAU,MAAM;KAChB,WAAW,MAAM;KACjB,iBAAiB,MAAM;IACzB,CAAC;IACD;GACF;EACF;EACA,OAAO;GAAE;GAAW;EAAO;CAC7B;AACF;AAEA,SAAS,eAAe,KAAc,SAAmF;CAEvH,QAAQ,MAAM,mBAAmB,SAAS,GAAG;AAC/C;AAEA,SAAS,WAAW,KAAsB;CACxC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,SAAS,SAAS,YAAoB,OAAwB,OAAe,SAAyB;CACpG,IAAI,MAAM,iBAAiB,KAAK,GAAG,OAAO,GAAG,WAAW,MAAM,MAAM,gBAAgB,KAAK;CAGzF,MAAM,WAAW,WAAW,QAAQ,CAAC,CAAC,OAAO,SAAS,MAAM,CAAC,CAAC,OAAO,KAAK;CAC1E,OAAO,GAAG,WAAW,QAAQ,MAAM,UAAU,GAAG,MAAM,GAAG;AAC3D;;;;;;;;;;;;;;;;AC3TA,MAAa,wBAAyC;CACpD,IAAI;CACJ,gBAAgB,EAAE,SAAS,SAAS,UAAiC;EACnE,MAAM,MAAM,YAAY,SAAS,kBAAkB;EACnD,IAAI,CAAC,KAAK,OAAO;GAAE,OAAO;GAAO,QAAQ;EAA2B;EACpE,OAAO,sBAAsB,SAAS,KAAK,MAAM,IAC7C,EAAE,OAAO,KAAK,IACd;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAClD;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,MAAM,SAAS,OAAO;EAC5B,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,CAAC;EAC7C,MAAM,IAAI;EACV,OAAO,CACL;GACE,UAAU;GACV,WAAW,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;GACjD,iBAAiB,OAAO,EAAE,OAAO,WAAW,EAAE,KAAK,KAAA;GACnD,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CACF;CACF;CACA,cAAc;EAIZ,WAAW;EACX,QAAQ;EACR,QAAQ;GACN,EAAE,IAAI,6BAA6B;GACnC,EAAE,IAAI,2BAA2B;GACjC,EAAE,IAAI,gCAAgC;GACtC,EAAE,IAAI,gCAAgC;GACtC,EAAE,IAAI,gCAAgC;GACtC,EAAE,IAAI,eAAe;GACrB,EAAE,IAAI,yBAAyB;GAC/B,EAAE,IAAI,2BAA2B;GACjC,EAAE,IAAI,gCAAgC;GACtC,EAAE,IAAI,mBAAmB;GACzB,EAAE,IAAI,kBAAkB;EAC1B;CACF;AACF;;;;;;;;;;;;AAaA,SAAgB,uBAAuB,SAA+C;CACpF,MAAM,SAAS,SAAS,OAAO;CAC/B,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,IAAI,OAAO,SAAS,oBAAoB,OAAO;CAC/C,OAAO,EAAE,WAAW,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY,GAAG;AACnF;AAEA,MAAa,uBAAwC;CACnD,IAAI;CACJ,gBAAgB,EAAE,SAAS,SAAS,UAAiC;EACnE,MAAM,MAAM,YAAY,SAAS,mBAAmB;EACpD,MAAM,KAAK,YAAY,SAAS,2BAA2B;EAC3D,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAuC;EACvF,OAAO,qBAAqB,SAAS,KAAK,IAAI,MAAM,IAChD,EAAE,OAAO,KAAK,IACd;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAClD;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,MAAM,SAAS,OAAO;EAC5B,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,CAAC;EAC7C,IAAI,IAAI,SAAS,oBACf,OAAO,CAAC;GACN,UAAU;GACV,WAAW;GACX,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;EAGH,OAAO,CAAC;GACN,UAAU;GACV,WAAA,SAHyB,IAAI,OAAO,QAAQ,IAAI,QAAQ;GAIxD,iBAAiB,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW,KAAA;GACnE,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;CACA,cAAc;EAMZ,WAAW;EACX,QAAQ;EACR,QAAQ;GACN,EAAE,IAAI,gBAAgB;GACtB,EAAE,IAAI,oBAAoB;GAC1B,EAAE,IAAI,uBAAuB;GAC7B,EAAE,IAAI,yBAAyB;GAC/B,EAAE,IAAI,wBAAwB;GAC9B,EAAE,IAAI,8BAA8B;GACpC,EAAE,IAAI,kBAAkB;GACxB,EAAE,IAAI,wBAAwB;GAC9B,EAAE,IAAI,oBAAoB;EAC5B;CACF;AACF;;;;;;;;;AAUA,MAAM,yBAAyB;AAE/B,MAAa,uBAAwC;CACnD,IAAI;CACJ,gBAAgB,EAAE,SAAS,SAAS,UAAiC;EACnE,MAAM,KAAK,YAAY,SAAS,SAAS;EACzC,MAAM,YAAY,YAAY,SAAS,gBAAgB;EACvD,MAAM,aAAa,YAAY,SAAS,gBAAgB;EACxD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAuB;EAC5F,MAAM,OAAO,OAAO,SAAS;EAC7B,IAAI,CAAC,OAAO,SAAS,IAAI,KAAK,KAAK,IAAI,KAAK,IAAI,IAAI,MAAO,IAAI,IAAI,wBACjE,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAuB;EAExD,MAAM,aAAa,OAAO,WAAW,QAAQ,IAAI,OAAO,MAAM,CAAe,IAAI;EACjF,IAAI;EACJ,IAAI;GACF,MAAM,OAAO,KAAK,YAAY,QAAQ;EACxC,QAAQ;GACN,OAAO;IAAE,OAAO;IAAO,QAAQ;GAAiB;EAClD;EACA,IAAI,IAAI,WAAW,GAAG,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAiB;EACtE,MAAM,WAAW,OAAO,KAAK,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,GAAG,GAAG,GAAG,UAAU,GAAG,SAAS,CAAC,CAAC,OAAO,QAAQ,CAAC;EAC/G,KAAK,MAAM,SAAS,WAAW,MAAM,GAAG,GAAG;GACzC,MAAM,CAAC,SAAS,aAAa,MAAM,MAAM,KAAK,CAAC;GAC/C,IAAI,YAAY,QAAQ,CAAC,WAAW;GACpC,MAAM,SAAS,OAAO,KAAK,SAAS;GACpC,IAAI,OAAO,WAAW,SAAS,UAAU,gBAAgB,QAAQ,QAAQ,GAAG,OAAO,EAAE,OAAO,KAAK;EACnG;EACA,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;CACrD;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,MAAM,SAAS,OAAO;EAC5B,IAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,OAAO,IAAI,SAAS,UAAU,OAAO,CAAC;EAC7E,OAAO,CAAC;GACN,UAAU;GACV,WAAW,SAAS,IAAI;GACxB,iBAAiB,YAAY,SAAS,SAAS,KAAK,KAAA;GACpD,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;CACA,cAAc;EAEZ,WAAW;EACX,QAAQ;EACR,QAAQ;GACN,EAAE,IAAI,qBAAqB;GAC3B,EAAE,IAAI,qBAAqB;GAC3B,EAAE,IAAI,qBAAqB;GAC3B,EAAE,IAAI,wBAAwB;GAC9B,EAAE,IAAI,6BAA6B;GACnC,EAAE,IAAI,uCAAuC;GAC7C,EAAE,IAAI,8BAA8B;EACtC;CACF;AACF;AAEA,MAAa,0BAA2C;CACtD,IAAI;CACJ,gBAAgB,EAAE,SAAS,SAAS,UAAiC;EACnE,MAAM,MAAM,YAAY,SAAS,sBAAsB;EACvD,IAAI,CAAC,KAAK,OAAO;GAAE,OAAO;GAAO,QAAQ;EAA6B;EACtE,MAAM,WAAW,WAAW,UAAU,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,OAAO,KAAK;EAC1E,MAAM,IAAI,OAAO,KAAK,IAAI,YAAY,GAAG,OAAO;EAChD,MAAM,IAAI,OAAO,KAAK,UAAU,OAAO;EACvC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;EAC9E,OAAO,gBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAC/F;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,MAAM,SAAS,OAAO;EAC5B,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,CAAC;EAC7C,OAAO,CAAC;GACN,UAAU;GACV,WAAW,YAAY,IAAI,cAAc;GACzC,iBAAiB,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW,KAAA;GACnE,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;CACA,cAAc;EAIZ,WAAW;EACX,QAAQ;EACR,QAAQ;GACN,EAAE,IAAI,uBAAuB;GAC7B,EAAE,IAAI,wBAAwB;GAC9B,EAAE,IAAI,0BAA0B;GAChC,EAAE,IAAI,yBAAyB;GAC/B,EAAE,IAAI,8BAA8B;GACpC,EAAE,IAAI,gCAAgC;GACtC,EAAE,IAAI,8BAA8B;GACpC,EAAE,IAAI,+BAA+B;EACvC;CACF;AACF;;;;;;AAOA,MAAa,uBAAwC;CACnD,IAAI;CACJ,gBAAgB,EAAE,SAAS,SAAS,UAAiC;EACnE,MAAM,MAAM,YAAY,SAAS,0BAA0B;EAC3D,IAAI,CAAC,KAAK,OAAO;GAAE,OAAO;GAAO,QAAQ;EAA0B;EACnE,MAAM,KAAK,YAAY,SAAS,0BAA0B;EAC1D,IAAI,CAAC,IAAI,OAAO;GAAE,OAAO;GAAO,QAAQ;EAA0B;EAClE,OAAO,qBAAqB,SAAS,KAAK,IAAI,MAAM,IAChD,EAAE,OAAO,KAAK,IACd;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAClD;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,MAAM,SAAS,OAAO;EAC5B,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,CAAC;EAI7C,IAAI,OAAO,IAAI,SAAS,YAAY,OAAO,IAAI,OAAO,UAAU,OAAO,CAAC;EACxE,OAAO,CAAC;GACN,UAAU;GACV,WAAW;GACX,iBAAiB,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY,KAAA;GACrE,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;CACA,cAAc;EAEZ,WAAW;EACX,QAAQ;EACR,QAAQ,CAAC,EAAE,IAAI,iBAAiB,CAAC;CACnC;AACF;;;;;;;AAQA,MAAa,uBAAwC;CACnD,IAAI;CACJ,gBAAgB,EAAE,SAAS,UAAiC;EAC1D,MAAM,OAAO,YAAY,SAAS,eAAe;EACjD,IAAI,CAAC,MAAM,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAwB;EAKlE,MAAM,IAAI,6BAA6B,KAAK,IAAI;EAChD,IAAI,CAAC,GAAG,OAAO;GAAE,OAAO;GAAO,QAAQ;EAA+B;EACtE,MAAM,IAAI,OAAO,KAAK,EAAE,IAAK,OAAO;EACpC,MAAM,IAAI,OAAO,KAAK,QAAQ,OAAO;EACrC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;EAC9E,OAAO,gBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAC/F;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,WAAW,SAAS,OAAO;EACjC,IAAI,CAAC,UAAU,SAAS,MAAM,OAAO,CAAC;EACtC,IAAI;EACJ,IAAI;GACF,UAAU,KAAK,MAAM,OAAO,KAAK,SAAS,QAAQ,MAAM,QAAQ,CAAC,CAAC,SAAS,OAAO,CAAC;EACrF,QAAQ;GACN,OAAO,CAAC;EACV;EACA,MAAM,QAAQ;EACd,OAAO,CAAC;GACN,UAAU;GACV,WAAW;GACX,iBAAiB,SAAS,QAAQ;GAClC,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;IAAE,GAAG;IAAO,aAAa,SAAS,QAAQ;GAAY;GAC/D,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;AACF;;;;AAKA,MAAa,wBAAyC;CACpD,IAAI;CACJ,gBAAgB,EAAE,SAAS,UAAiC;EAC1D,MAAM,QAAQ,YAAY,SAAS,sBAAsB;EACzD,IAAI,CAAC,OAAO,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAwB;EACnE,MAAM,IAAI,OAAO,KAAK,OAAO,OAAO;EACpC,MAAM,IAAI,OAAO,KAAK,QAAQ,OAAO;EACrC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;EAC9E,OAAO,gBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAC/F;CACA,MAAM,EAAE,SAAS,OAA0B;EACzC,MAAM,aAAa,YAAY,SAAS,oBAAoB;EAC5D,MAAM,gBAAgB,YAAY,SAAS,uBAAuB,KAAK;EACvE,MAAM,YAAY,YAAY,SAAS,mBAAmB;EAC1D,MAAM,gBAAgB,YAAY,SAAS,uBAAuB;EAClE,IAAI,kBAAkB,QACpB,OAAO,CAAC;GACN,UAAU;GACV,WAAW;GACX,iBAAiB,gBAAgB,GAAG,UAAU,GAAG,kBAAkB,KAAA;GACnE,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;IAAE;IAAW;IAAY;GAAc;GAChD,SAAS,iBAAiB,OAAO;EACnC,CAAC;EAEH,OAAO,CAAC;GACN,UAAU;GACV,WAAW,mBAAmB;GAC9B,iBAAiB,gBAAgB,GAAG,UAAU,GAAG,kBAAkB,KAAA;GACnE,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;IAAE;IAAW;IAAY;GAAc;GAChD,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;AACF;;;AAIA,MAAM,uBAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,MAAa,0BAA2C;CACtD,IAAI;CACJ,gBAAgB,EAAE,SAAS,UAAiC;EAC1D,MAAM,QAAQ,YAAY,SAAS,iCAAiC;EACpE,IAAI,CAAC,OAAO,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAgC;EAC3E,MAAM,IAAI,OAAO,KAAK,OAAO,OAAO;EACpC,MAAM,IAAI,OAAO,KAAK,QAAQ,OAAO;EACrC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAuB;EACjF,OAAO,gBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAuB;CAClG;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,MAAM,SAAS,OAAO;EAC5B,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,CAAC;EAE7C,OAAO,CAAC;GACN,UAAU;GACV,WAAW,YAHA,qBAAqB,MAAM,MAAM,KAAK,GAAG,KAAK;GAIzD,iBAAiB,OAAO,IAAI,cAAc,WAAW,OAAO,IAAI,SAAS,IAAI,KAAA;GAC7E,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;CACA,cAAc;EAKZ,WAAW;EACX,QAAQ;EACR,QAAQ,qBAAqB,KAAK,OAAO,EAAE,IAAI,YAAY,IAAI,EAAE;CACnE;AACF;;;;;;AAOA,MAAa,2BAA4C;CACvD,IAAI;CAGJ,iBAAiB;EAAE,MAAM;EAA4B,SAAS,EAAE,gBAAgB,aAAa;CAAE;CAC/F,gBAAgB,EAAE,SAAS,UAAiC;EAE1D,MAAM,QADO,mBAAmB,OACf,CAAC,EAAE;EACpB,IACE,CAAC,SACD,OAAO,MAAM,eAAe,YAC5B,OAAO,MAAM,eAAe,YAC5B,OAAO,MAAM,eAAe,UAE5B,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAuB;EAExD,MAAM,WAAW,WAAW,UAAU,MAAM,CAAC,CAC1C,OAAO,GAAG,MAAM,aAAa,MAAM,YAAY,CAAC,CAChD,OAAO,KAAK;EACf,MAAM,IAAI,OAAO,KAAK,MAAM,WAAW,YAAY,GAAG,OAAO;EAC7D,MAAM,IAAI,OAAO,KAAK,SAAS,YAAY,GAAG,OAAO;EACrD,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;EAC9E,OAAO,gBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAC/F;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,OAAO,mBAAmB,OAAO;EACvC,MAAM,QAAQ,MAAM;EACpB,IAAI,CAAC,OAAO,OAAO,CAAC;EACpB,OAAO,CAAC;GACN,UAAU;GACV,WAAW,aAAa,MAAM,cAAc;GAO5C,iBACE,OAAO,MAAM,aAAa,YAAY,MAAM,SAAS,SAAS,IAC1D,MAAM,WACN,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK;GACpE,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;CACA,cAAc;EAIZ,WAAW;EACX,QAAQ;EACR,QAAQ;GACN,EAAE,IAAI,mCAAmC;GACzC,EAAE,IAAI,qCAAqC;GAC3C,EAAE,IAAI,qCAAqC;GAC3C,EAAE,IAAI,yCAAyC;GAC/C,EAAE,IAAI,uCAAuC;GAC7C,EAAE,IAAI,yCAAyC;GAC/C,EAAE,IAAI,qCAAqC;GAC3C,EAAE,IAAI,uCAAuC;GAC7C,EAAE,IAAI,sCAAsC;EAC9C;CACF;AACF;;;;AAKA,MAAM,qBAAqB;CAAC;CAAM;CAAS;CAAY;CAAa;AAAS;;;;;;;;;;AAW7E,MAAa,sBAAuC;CAClD,IAAI;CACJ,gBAAgB,EAAE,SAAS,UAAiC;EAC1D,MAAM,QAAQ,YAAY,SAAS,uBAAuB;EAC1D,IAAI,CAAC,OAAO,OAAO;GAAE,OAAO;GAAO,QAAQ;EAA8B;EACzE,MAAM,IAAI,OAAO,KAAK,OAAO,OAAO;EACpC,MAAM,IAAI,OAAO,KAAK,QAAQ,OAAO;EACrC,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;EAC9E,OAAO,gBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAoB;CAC/F;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,MAAM,MAAM,SAAS,OAAO;EAC5B,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO,CAAC;EAC7C,MAAM,SAAS;EACf,MAAM,QAAQ,mBAAmB,MAC9B,MAAM,OAAO,OAAO,OAAO,YAAY,OAAO,OAAO,OAAO,QAC/D;EACA,MAAM,kBAAkB,QACpB,OAAO,OAAO,MAAM,IACpB,WAAW,QAAQ,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,OAAO,KAAK;EAOrD,OAAO,CAAC;GACN,UAAU;GACV,WAPA,OAAO,OAAO,UAAU,WACpB,QAAQ,OAAO,UACf,OAAO,OAAO,SAAS,WACrB,QAAQ,OAAO,SACf;GAIN;GACA,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;GACT,SAAS,iBAAiB,OAAO;EACnC,CAAC;CACH;AACF;;;;AAcA,SAAS,mBAAmB,SAA8C;CACxE,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,OAAO;EAC/B,IAAI,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC/C,QAAQ,CAER;CAIA,MAAM,WAAW,IAAI,gBAAgB,OAAO,CAAC,CAAC,IAAI,MAAM;CACxD,IAAI,UACF,IAAI;EACF,OAAO,KAAK,MAAM,QAAQ;CAC5B,QAAQ;EACN,OAAO;CACT;CAKF,OAAO,0BAA0B,OAAO;AAC1C;;;;;AAMA,SAAS,0BAA0B,SAA8C;CAC/E,MAAM,UAAU,QAAQ,QAAQ,eAAa;CAC7C,IAAI,YAAY,IAAI,OAAO;CAC3B,MAAM,YAAY,QAAQ,MAAM,OAAO;CACvC,MAAM,MAAM,aAAa,KAAK,SAAS;CACvC,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,WAAW,UAAU,MAAM,IAAI,QAAQ,IAAI,EAAE,CAAC,MAAM;CAC1D,MAAM,MAAM,UAAU,KAAK,QAAQ;CACnC,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI;EACF,OAAO,KAAK,MAAM,SAAS,MAAM,GAAG,IAAI,KAAK,CAAC;CAChD,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,SAAgB,2BAA2B,SAQvB;CAClB,MAAM,SAAS,QAAQ,mBAAmB;CAC1C,OAAO;EACL,IAAI,QAAQ;EACZ,gBAAgB,EAAE,SAAS,SAAS,UAAU;GAC5C,MAAM,MAAM,YAAY,SAAS,MAAM;GACvC,IAAI,CAAC,KAAK,OAAO;IAAE,OAAO;IAAO,QAAQ,WAAW;GAAS;GAC7D,OAAO,oBAAoB,SAAS,KAAK,QAAQ;IAC/C,WAAW,QAAQ,aAAa;IAChC,iBAAiB,QAAQ;GAC3B,CAAC,IACG,EAAE,OAAO,KAAK,IACd;IAAE,OAAO;IAAO,QAAQ;GAAoB;EAClD;EACA,OACE,QAAQ,WACN,EAAE,SAAS,SAAS,UAAU;GAC9B,MAAM,MAAM,SAAS,OAAO,KAAK;GACjC,OAAO,CAAC;IACN,UAAU,QAAQ;IAClB,WAAW,GAAG,QAAQ,GAAG;IACzB,YAAY,OAAO,KAAK,IAAI;IAC5B,SAAS;IACT,SAAS,iBAAiB,OAAO;GACnC,CAAC;EACH;CACJ;AACF;AAEA,SAAS,SAAS,GAAoB;CACpC,IAAI;EACF,OAAO,KAAK,MAAM,CAAC;CACrB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,iBAAiB,SAAiD;CACzE,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,OAAO,GAAG;EAC5C,IAAI,MAAM,KAAA,GAAW;EACrB,MAAM,QAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK;EACxC,IAAI,OAAO,UAAU,UAAU,IAAI,EAAE,YAAY,KAAK;CACxD;CACA,OAAO;AACT;;;;AC/pBA,SAAgB,4BAA4B,SAKxB;CAClB,IAAI,CAAC,CAAC,WAAW,QAAQ,CAAC,CAAC,SAAS,QAAQ,IAAI,GAAG,MAAM,IAAI,MAAM,uCAAuC;CAC1G,MAAM,QAAQ,EAAE,GAAG,QAAQ;CAC3B,MAAM,aAAa,MAAM,cAAc;CACvC,OAAO;EACL,IAAI;EACJ,gBAAgB,EAAE,SAAS,SAAS,UAAU;GAC5C,MAAM,MAAM,OAAO,KAAK,OAAO,CAAC,CAAC,MAAK,MAAK,EAAE,YAAY,MAAM,oBAAoB;GACnF,MAAM,YAAY,MAAM,QAAQ,OAAO,KAAA;GACvC,IAAI;IACF,uBAAuB;KAAE,KAAK,MAAM;KAAK;KAAS;KAAW,WAAW;KAAQ,YAAY,MAAM;IAAW,CAAC;IAC9G,OAAO,EAAE,OAAO,KAAK;GACvB,QAAQ;IAAE,OAAO;KAAE,OAAO;KAAO,QAAQ;IAAmC;GAAE;EAChF;EACA,MAAM,EAAE,SAAS,OAAO;GACtB,MAAM,OAAO,OAAO,YAAY,IAAI,gBAAgB,OAAO,CAAC;GAC5D,IAAI,CAAC,yBAAyB,KAAK,KAAK,cAAc,EAAE,GAAG,MAAM,IAAI,MAAM,4CAA4C;GACvH,MAAM,SAAS,MAAM,SAAS,WAAW,KAAK,gBAAgB;GAC9D,IAAI,CAAC,UAAU,CAAC,YAAY,KAAK,MAAM,GAAG,MAAM,IAAI,MAAM,mCAAmC;GAC7F,OAAO,CAAC;IAAE,UAAU;IAAY,WAAW,GAAG,WAAW,WAAW,MAAM,SAAS,WAAW,WAAW;IAEvG,iBAAiB,GAAG,MAAM,WAAW,GAAG,KAAK,WAAW,GAAG,MAAM,KAAK,GAAG;IACzE,YAAY,OAAO,KAAK,IAAI;IAAG,SAAS;KAAE,GAAG;KAAM,cAAc,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY;IAAE;IAAG,SAAS,CAAC;GAAE,CAAC;EAC/H;EACA,iBAAiB;GAAE,MAAM;GAAe,SAAS,EAAE,gBAAgB,0BAA0B;EAAE;EAC/F,cAAc;GAAE,WAAW,GAAG,WAAW;GAAI,QAAQ;GACnD,QAAQ,CAAC,EAAE,IAAI,GAAG,WAAW,mBAAmB,GAAG,EAAE,IAAI,GAAG,WAAW,iBAAiB,CAAC;EAAE;CAC/F;AACF;;AAGA,SAAgB,+BAA+B,SAI3B;CAClB,OAAO,4BAA4B;EAAE,GAAG;EAAS,YAAY;CAAa,CAAC;AAC7E;;;;ACzCA,SAAS,OAAO,SAAyB,MAA6B;CACpE,MAAM,UAAU,OAAO,QAAQ,OAAO,CAAC,CAAC,QAAQ,CAAC,SAAS,IAAI,YAAY,MAAM,IAAI;CACpF,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,MAAM,QAAQ,QAAQ,EAAE,GAAG;CAC3B,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS,KAAK,MAAM,UAAU,OAAO,QAAQ;AACzF;AAEA,SAAS,MAAM,WAA0B,KAAkC;CACzE,OAAO,cAAc,QAAQ,aAAa,KAAK,SAAS,KACnD,OAAO,cAAc,OAAO,SAAS,CAAC,KACtC,OAAO,SAAS,GAAG,KAAK,KAAK,IAAI,MAAM,OAAO,SAAS,CAAC,KAAK;AACpE;;AAGA,SAAgB,oBAAoB,SAAiB,SAAyB,QAAgB,MAAM,KAAK,IAAI,IAAI,KAAe;CAC9H,MAAM,KAAK,OAAO,SAAS,qBAAqB;CAChD,MAAM,YAAY,OAAO,SAAS,oBAAoB;CACtD,MAAM,YAAY,OAAO,SAAS,oBAAoB;CACtD,OAAO,QAAQ,UAAU,MAAM,aAAa,MAAM,WAAW,GAAG,KAC3D,oBAAoB,GAAG,GAAG,GAAG,UAAU,GAAG,WAAW,WAAW,QAAQ,EAAE,iBAAiB,UAAU,CAAC,CAAC;AAC9G;;AAGA,SAAS,sBAAsB,SAAiB,SAAyB,QAAgB,QAA4B,KAAsB;CACzI,MAAM,KAAK,OAAO,SAAS,GAAG,OAAO,IAAI;CACzC,MAAM,YAAY,OAAO,SAAS,GAAG,OAAO,WAAW;CACvD,MAAM,YAAY,OAAO,SAAS,GAAG,OAAO,WAAW;CACvD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,WAAW,QAAQ,GAAG,OAAO;CACxF,MAAM,UAAU,OAAO,MAAM,CAAC;CAC9B,IAAI,CAAC,yBAAyB,KAAK,OAAO,GAAG,OAAO;CACpD,MAAM,MAAM,OAAO,KAAK,SAAS,QAAQ;CACzC,IAAI,IAAI,SAAS,MAAM,IAAI,SAAS,QAAQ,MAAM,SAAS,OAAO;CAClE,MAAM,WAAW,WAAW,UAAU,GAAG,CAAC,CAAC,OAAO,GAAG,GAAG,GAAG,UAAU,GAAG,SAAS,CAAC,CAAC,OAAO,QAAQ;CAClG,OAAO,UAAU,MAAM,GAAG,CAAC,CAAC,MAAM,SAAS,KAAK,WAAW,KAAK,KAC3D,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,SAAS,UAClC,gBAAgB,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC;AACzE;;AAGA,SAAgB,kBAAkB,SAAiB,SAAyB,QAAgB,MAAM,KAAK,IAAI,IAAI,KAAe;CAC5H,OAAO,sBAAsB,SAAS,SAAS,QAAQ,WAAW,GAAG;AACvE;;AAGA,SAAgB,oBAAoB,SAAiB,SAAyB,QAAgB,MAAM,KAAK,IAAI,IAAI,KAAe;CAC9H,OAAO,sBAAsB,SAAS,SAAS,QAAQ,QAAQ,GAAG;AACpE;AAEA,SAAS,OAAO,OAAkD;CAChE,OAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,MAAM,UAAkB,WAAmB,SAAiB,OAAmE;CACtI,IAAI,OAAO,WAAW,MAAM,SAAS,MAAM,IAAI,SAAW,MAAM,IAAI,MAAM,uBAAuB;CACjG,MAAM,QAAiB,KAAK,MAAM,MAAM,OAAO;CAC/C,IAAI,CAAC,OAAO,KAAK,KAAK,OAAO,MAAM,eAAe,YAAY,OAAO,MAAM,aAAa,YACnF,CAAC,4BAA4B,KAAK,MAAM,UAAoB,KAC5D,CAAE,MAAM,QAAQ,CAAY,UAAW,MAAM,QAAQ,CAAY,SAAS,KAC7E,MAAM,IAAI,MAAM,4DAA4D;CAE9E,OAAO,CAAC;EAAE;EAAU,WAAW,GAAG,SAAS,GAAG,MAAM;EAAc,iBAAiB,MAAM;EACvF,YAAY,MAAM,OAAO,KAAK,IAAI;EAAG,SAAS;EAAO,SAAS,CAAC;CAAE,CAAC;AACtE;AAEA,MAAa,wBAAyC;CACpD,IAAI;CACJ,kBAAkB,EAAE,SAAS,SAAS,aAAa,oBAAoB,SAAS,SAAS,MAAM,IAC3F,EAAE,OAAO,KAAK,IAAI;EAAE,OAAO;EAAO,QAAQ;CAAoB;CAClE,QAAQ,UAAU,MAAM,UAAU,cAAc,MAAM,KAAK;CAC3D,cAAc;EAAE,WAAW;EAAW,QAAQ;EAAO,QAAQ;GAC3D,EAAE,IAAI,2BAA2B;GAAG,EAAE,IAAI,uBAAuB;GAAG,EAAE,IAAI,0BAA0B;EACtG;CAAE;AACJ;AAEA,MAAa,sBAAuC;CAClD,IAAI;CACJ,kBAAkB,EAAE,SAAS,SAAS,aAAa,kBAAkB,SAAS,SAAS,MAAM,IACzF,EAAE,OAAO,KAAK,IAAI;EAAE,OAAO;EAAO,QAAQ;CAAoB;CAClE,QAAQ,UAAU;EAChB,MAAM,SAAS,MAAM,QAAQ,cAAc,YAAY,KAAK;EAC5D,MAAM,KAAK,OAAO,MAAM,SAAS,YAAY;EAC7C,IAAI,CAAC,IAAI,MAAM,IAAI,MAAM,+BAA+B;EACxD,OAAO,OAAO,KAAK,WAAW;GAAE,GAAG;GAAO,iBAAiB;EAAG,EAAE;CAClE;CACA,cAAc;EAAE,WAAW;EAAS,QAAQ;EAAO,QAAQ,CAAC,EAAE,IAAI,wBAAwB,CAAC;CAAE;AAC/F;AAEA,MAAa,4BAA6C;CACxD,IAAI;CACJ,kBAAkB,EAAE,SAAS,SAAS,aAAa;EACjD,MAAM,YAAY,OAAO,SAAS,sBAAsB;EACxD,OAAO,UAAU,aAAa,sBAAsB,SAAS,WAAW,MAAM,IAC1E,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAoB;CACpE;CACA,QAAQ,UAAU,MAAM,cAAc,QAAQ,MAAM,KAAK;CACzD,cAAc;EAAE,WAAW;EAAe,QAAQ;EAAO,QAAQ;GAC/D,EAAE,IAAI,+BAA+B;GAAG,EAAE,IAAI,+BAA+B;GAC7E,EAAE,IAAI,+BAA+B;EACvC;CAAE;AACJ;AAEA,MAAa,8BAA+C;CAC1D,IAAI;CACJ,kBAAkB,EAAE,SAAS,SAAS,aAAa,kBAAkB,SAAS,SAAS,MAAM,IACzF,EAAE,OAAO,KAAK,IAAI;EAAE,OAAO;EAAO,QAAQ;CAAoB;CAClE,QAAQ,UAAU;EAChB,MAAM,SAAS,MAAM,iBAAiB,QAAQ,MAAM,KAAK;EACzD,IAAI,OAAO,EAAE,EAAE,oBAAoB,OAAO,MAAM,SAAS,YAAY,GAAG,MAAM,IAAI,MAAM,gCAAgC;EACxH,OAAO;CACT;CACA,cAAc;EAAE,WAAW;EAAkB,QAAQ;EAAO,QAAQ,CAAC,EAAE,IAAI,iCAAiC,CAAC;CAAE;AACjH;AAEA,MAAa,wBAAyC;CACpD,IAAI;CACJ,kBAAkB,EAAE,SAAS,SAAS,aAAa,oBAAoB,SAAS,SAAS,MAAM,IAC3F,EAAE,OAAO,KAAK,IAAI;EAAE,OAAO;EAAO,QAAQ;CAAoB;CAClE,QAAQ,UAAU;EAChB,IAAI,OAAO,WAAW,MAAM,SAAS,MAAM,IAAI,SAAW,MAAM,IAAI,MAAM,uBAAuB;EACjG,MAAM,QAAiB,KAAK,MAAM,MAAM,OAAO;EAC/C,IAAI,CAAC,OAAO,KAAK,KAAK,MAAM,SAAS,oBAAoB,CAAC,OAAO,MAAM,IAAI,KACtE,OAAO,MAAM,KAAK,aAAa,YAAY,CAAC,8CAA8C,KAAK,MAAM,KAAK,QAAQ,GACrH,MAAM,IAAI,MAAM,2CAA2C;EAE7D,MAAM,WAAW,OAAO,MAAM,SAAS,SAAS;EAChD,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,+BAA+B;EAC9D,OAAO,CAAC;GAAE,UAAU;GAAU,WAAW;GAAyB,iBAAiB;GACjF,YAAY,MAAM,OAAO,KAAK,IAAI;GAAG,SAAS;GAAO,SAAS,CAAC;EAAE,CAAC;CACtE;CACA,cAAc;EAAE,WAAW;EAAW,QAAQ;EAAM,QAAQ,CAAC,EAAE,IAAI,wBAAwB,CAAC;CAAE;AAChG;;AAGA,MAAa,0BAA2C;CACtD,IAAI;CACJ,kBAAkB,EAAE,SAAS,aAAa;EACxC,MAAM,WAAW,OAAO,SAAS,mBAAmB;EACpD,IAAI,CAAC,UAAU,CAAC,UAAU,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;EAC7E,MAAM,SAAS,OAAO,KAAK,QAAQ,GAAG,WAAW,OAAO,KAAK,MAAM;EACnE,IAAI,OAAO,WAAW,SAAS,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAoB;EAC1F,OAAO,gBAAgB,QAAQ,QAAQ,IACnC,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAoB;CACpE;CACA,QAAQ,UAAU;EAChB,IAAI,OAAO,WAAW,MAAM,SAAS,MAAM,IAAI,SAAW,MAAM,IAAI,MAAM,uBAAuB;EACjG,MAAM,QAAiB,KAAK,MAAM,MAAM,OAAO;EAC/C,IAAI,CAAC,OAAO,KAAK,GAAG,MAAM,IAAI,MAAM,qCAAqC;EACzE,MAAM,cAAc,OAAO,MAAM,iBAAiB,WAAW,MAAM,aAAa,YAAY,IAAI;EAChG,IAAI,gBAAgB,aAAa,gBAAgB,SAAS,OAAO,CAAC;EAClE,IAAI,OAAO,MAAM,mBAAmB,YAAY,CAAC,MAAM,kBAClD,MAAM,eAAe,SAAS,OAAO,OAAO,MAAM,gBAAgB,WACrE,MAAM,IAAI,MAAM,+DAA+D;EAEjF,IAAI,MAAM,aAAa,OAAO,CAAC;EAC/B,OAAO,CAAC;GAAE,UAAU;GAAY,WAAW;GACzC,iBAAiB,MAAM;GAAgB,YAAY,MAAM,OAAO,KAAK,IAAI;GAAG,SAAS;GAAO,SAAS,CAAC;EAAE,CAAC;CAC7G;CACA,cAAc;EAAE,WAAW;EAAa,QAAQ;EAAM,QAAQ,CAAC,EAAE,IAAI,4BAA4B,CAAC;CAAE;AACtG;;;AC/JA,MAAM,UAAU,MAA6C,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AAC7G,MAAM,WAAW,MAA4B,OAAO,MAAM,YAAY,gBAAgB,KAAK,CAAC;;;AAI5F,SAAgB,8BAA8B,SAI1B;CAClB,IAAI,CAAC,QAAQ,QAAQ,MAAM,KAAK,CAAC,QAAQ,QAAQ,aAAa,GAAG,MAAM,IAAI,MAAM,kDAAkD;CACnI,OAAO;EACL,IAAI,QAAQ;EACZ,gBAAgB,EAAE,SAAS,SAAS,UAAU;GAC5C,MAAM,YAAY,YAAY,SAAS,qBAAqB;GAC5D,OAAO,aAAa,wBAAwB,KAAK,SAAS,KACrD,oBAAoB,SAAS,WAAW,QAAQ;IAAE,WAAW;IAAU,iBAAiB;GAAU,CAAC,IACpG,EAAE,OAAO,KAAK,IAAI;IAAE,OAAO;IAAO,QAAQ;GAAoB;EACpE;EACA,MAAM,EAAE,SAAS,SAAS,OAAO;GAC/B,MAAM,OAAgB,KAAK,MAAM,OAAO;GACxC,IAAI,CAAC,OAAO,IAAI,KAAK,KAAK,WAAW,+BAA+B,CAAC,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,IAAI,MAAM,mCAAmC;GAC9K,MAAM,SAA4B,CAAC;GACnC,KAAK,MAAM,SAAS,KAAK,OAAO;IAC9B,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,MAAM,QAAQ,MAAM,OAAO,KAAK,MAAM,QAAQ,SAAS,KAAK,MAAM,IAAI,MAAM,wBAAwB;IAC3H,IAAI,MAAM,OAAO,QAAQ,QAAQ;IACjC,KAAK,MAAM,UAAU,MAAM,SAAS;KAClC,IAAI,CAAC,OAAO,MAAM,KAAK,OAAO,UAAU,cAAc,CAAC,OAAO,OAAO,KAAK,GAAG;KAC7E,MAAM,QAAQ,OAAO;KACrB,IAAI,MAAM,sBAAsB,cAAc,CAAC,OAAO,MAAM,QAAQ,KAC/D,MAAM,SAAS,oBAAoB,QAAQ,eAAe;KAC/D,IAAI,MAAM,aAAa,KAAA,GAAW;KAClC,IAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,SAAS,SAAS,KAAK,MAAM,IAAI,MAAM,2BAA2B;KAC9G,KAAK,MAAM,WAAW,MAAM,UAAU;MACpC,IAAI,CAAC,OAAO,OAAO,KAAK,OAAO,QAAQ,OAAO,YAAY,CAAC,QAAQ,MAAM,QAAQ,GAAG,SAAS,KAAK,MAAM,IAAI,MAAM,mCAAmC;MACrJ,OAAO,KAAK;OAAE,UAAU,QAAQ;OAAY,WAAW,GAAG,QAAQ,WAAW;OAC3E,iBAAiB,GAAG,QAAQ,cAAc,GAAG,QAAQ;OAAM,YAAY,OAAO,KAAK,IAAI;OACvF,SAAS;QAAE,QAAQ,QAAQ;QAAQ,eAAe,QAAQ;QAAe;OAAQ;OACjF,SAAS,OAAO,YAAY,OAAO,QAAQ,OAAO,CAAC,CAAC,SAAS,CAAC,GAAE,OAAO,OAAO,MAAM,WAAW,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;MAC7H,CAAC;KACH;IACF;GACF;GACA,OAAO;EACT;EACA,cAAc;GAAE,WAAW,GAAG,QAAQ,WAAW;GAAI,QAAQ;GAAM,QAAQ,CAAC,EAAE,IAAI,GAAG,QAAQ,WAAW,mBAAmB,CAAC;EAAE;CAChI;AACF;;;;;;;;;;;;;;;;;;AC9BA,MAAa,gCAAgC;AAE7C,MAAM,SAAS,IAAI,IAAY,yBAAyB,KAAK,EAAE,QAAQ,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC1G,MAAM,aAAa;AACnB,MAAM,iBAAiB;AAEvB,SAAS,OAAO,OAAgB,SAAgC;CAC9D,MAAM,OAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,IAAI,OAAO,KAAK,IAAI;CACxF,OAAO,OAAO,SAAS,YAAY,QAAQ,KAAK,IAAI,IAAI,OAAO;AACjE;AAEA,SAAS,UAAU,SAAiD;CAClE,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;EAClD,IAAI,UAAU,KAAA,KAAa,IAAI,YAAY,MAAA,0BAAqC;EAChF,IAAI,IAAI,YAAY,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI;CACrE;CACA,OAAO;AACT;AAEA,MAAa,2BAA4C;CACvD,IAAI;CACJ,gBAAgB,EAAE,SAAS,UAAiC;EAC1D,MAAM,QAAQ,YAAY,SAAS,6BAA6B;EAChE,IAAI,CAAC,OAAO,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAkC;EAC7E,IAAI,CAAC,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAiB;EAC7D,MAAM,SAAS,OAAO,KAAK,OAAO,OAAO;EACzC,MAAM,WAAW,OAAO,KAAK,QAAQ,OAAO;EAC5C,IAAI,OAAO,WAAW,SAAS,QAAQ,OAAO;GAAE,OAAO;GAAO,QAAQ;EAAwB;EAC9F,OAAO,gBAAgB,QAAQ,QAAQ,IAAI,EAAE,OAAO,KAAK,IAAI;GAAE,OAAO;GAAO,QAAQ;EAAwB;CAC/G;CACA,MAAM,EAAE,SAAS,SAAS,OAA0B;EAClD,IAAI;EACJ,IAAI;GACF,OAAO,KAAK,MAAM,OAAO;EAC3B,QAAQ;GACN,MAAM,IAAI,MAAM,oCAAoC;EACtD;EACA,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG,MAAM,IAAI,MAAM,yCAAyC;EACvH,MAAM,MAAM;EAEZ,IAAI,OAAO,IAAI,UAAU,YAAY,CAAC,OAAO,IAAI,IAAI,KAAK,GAAG,OAAO,CAAC;EAErE,MAAM,aAAa,OAAO,IAAI,YAAY,UAAU,KAAK,OAAO,IAAI,gBAAgB,UAAU,KAC5F,OAAO,IAAI,YAAY,UAAU,KAAK,OAAO,IAAI,gBAAgB,UAAU;EAC7E,MAAM,gBAAgB,OAAO,IAAI,eAAe,cAAc,KAAK,OAAO,IAAI,eAAe,cAAc;EAC3G,IAAI,CAAC,cAAc,CAAC,eAAe,MAAM,IAAI,MAAM,0DAA0D;EAC7G,MAAM,YAAY,OAAO,IAAI,cAAc,YAAY,OAAO,IAAI,cAAc,WAAW,OAAO,IAAI,SAAS,IAAI;EACnH,MAAM,CAAC,QAAQ,UAAU,IAAI,MAAM,MAAM,GAAG;EAC5C,OAAO,CAAC;GACN,UAAU;GACV,WAAW,aAAa,OAAO,GAAG;GAElC,iBAAiB,WAAW,QAAQ,CAAC,CAAC,OAAO,GAAG,IAAI,MAAM,GAAG,WAAW,GAAG,cAAc,GAAG,WAAW,CAAC,CAAC,OAAO,KAAK;GACrH,YAAY,OAAO,KAAK,IAAI;GAC5B,SAAS;IAAE,GAAG;IAAK;IAAY;GAAc;GAC7C,SAAS,UAAU,OAAO;EAC5B,CAAC;CACH;CACA,cAAc;EAEZ,WAAW;EACX,QAAQ;EACR,QAAQ,yBAAyB,KAAK,EAAE,QAAQ,cAAc,EAAE,IAAI,aAAa,OAAO,GAAG,SAAS,EAAE;CACxG;AACF"}
|
|
@@ -6,23 +6,79 @@ It does not call these provider APIs directly.
|
|
|
6
6
|
|
|
7
7
|
## Cloudbeds
|
|
8
8
|
|
|
9
|
-
Create an API key with `read:reservation`, `read:room`,
|
|
9
|
+
Create an API key for the target property with these scopes: `read:reservation`, `read:room`, `read:guest`, `read:housekeeping`, `write:housekeeping`, `read:rate`, and `write:item`.
|
|
10
10
|
Store it in the connection credential envelope.
|
|
11
11
|
Set nonsecret connection metadata to `{ "propertyId": "1234" }`.
|
|
12
|
-
Record
|
|
13
|
-
The connector sends this property ID on each request and refuses
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
Record the granted scopes on the Hub connection; each action is exposed only when the Hub grant holds its scopes.
|
|
13
|
+
The connector sends this property ID on each request and refuses rows that name another property.
|
|
14
|
+
`getGuest`, `getGuestNotes`, and `getHousekeepingStatus` return no property ID, so for those reads the pinned request parameter and the property-scoped key are the only bound.
|
|
15
|
+
|
|
16
|
+
| Action | Cloudbeds operation | Scopes |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `reservations.list` | `GET /getReservations` | `read:reservation` |
|
|
19
|
+
| `reservations.get` | `GET /getReservation` | `read:reservation` |
|
|
20
|
+
| `guests.get` | `GET /getGuest` | `read:guest` |
|
|
21
|
+
| `guests.notes` | `GET /getGuestNotes` | `read:guest` |
|
|
22
|
+
| `housekeeping.status` | `GET /getHousekeepingStatus` | `read:housekeeping` |
|
|
23
|
+
| `housekeeping.update` | `GET /getHousekeepingStatus`, then `POST /postHousekeepingStatus` | `read:housekeeping`, `write:housekeeping` |
|
|
24
|
+
| `rate-plans.list` | `GET /getRatePlans` | `read:rate` |
|
|
25
|
+
| `room-types.available` | `GET /getAvailableRoomTypes` | `read:room` |
|
|
26
|
+
| `folio-items.post` | `GET /getReservation`, then `POST /postCustomItem` | `read:reservation`, `write:item` |
|
|
27
|
+
| `webhooks.subscribe` | `GET /getWebhooks`, then `POST /postWebhook` | `read:reservation` |
|
|
28
|
+
| `webhooks.list` | `GET /getWebhooks` | `read:reservation` |
|
|
29
|
+
| `webhooks.delete` | `GET /getWebhooks`, then `DELETE /deleteWebhook` | `read:reservation` |
|
|
30
|
+
|
|
31
|
+
The webhook endpoints declare no scope in the spec; Cloudbeds applies the subscribed event's object scope, which is `read:reservation` for every allowed event.
|
|
32
|
+
|
|
33
|
+
### Reservations and payments
|
|
34
|
+
|
|
35
|
+
`reservations.list` requires an arrival or departure date pair, except that `status: "checked_in"` alone lists guests in house now.
|
|
16
36
|
Each range spans at most 31 days, and the caller pages with `pageNumber` and `pageSize` (at most 100).
|
|
17
|
-
The
|
|
18
|
-
|
|
37
|
+
The connector always sends `includeAllRooms=true`.
|
|
38
|
+
Each row has reservation identity, stay dates, status, main guest ID and name, balance, and deduplicated `roomTypeIds`, `roomIds`, and `roomNames` in provider order.
|
|
39
|
+
A row without a `rooms` field returns empty lists.
|
|
40
|
+
It does not request guest details, guest requirements, or custom fields.
|
|
41
|
+
|
|
42
|
+
`reservations.get` reads one reservation and checks that Cloudbeds returns the connected property and the requested reservation ID.
|
|
43
|
+
It returns status, stay dates, main guest ID and name, assigned rooms (room ID, room name, room type), unassigned room types, adult and child counts, source, `total`, `balance`, and `balanceDetailed` (`subTotal`, `additionalItems`, `taxesFees`, `grandTotal`, `paid`, `suggestedDeposit`).
|
|
44
|
+
Cloudbeds v1.3 has no payments or transactions read.
|
|
45
|
+
Use `balanceDetailed.paid` for the amount paid and `balance` for the amount owed.
|
|
46
|
+
Cards on file, custom fields, and guest documents are never returned.
|
|
47
|
+
|
|
48
|
+
### Guests
|
|
49
|
+
|
|
50
|
+
`guests.get` returns name, email, phone, cell phone, country, nationality, anonymization, and a merged-into guest ID.
|
|
51
|
+
The v1.3 guest schema has no language field.
|
|
52
|
+
Address, birth date, and identity documents are never returned.
|
|
53
|
+
`guests.notes` returns each staff note with its ID, author, timestamps, and text up to 4,000 characters.
|
|
54
|
+
|
|
55
|
+
### Housekeeping
|
|
56
|
+
|
|
57
|
+
`housekeeping.status` returns today's room condition, occupancy, do-not-disturb, blocked flag, front-desk status, and assigned housekeeper.
|
|
58
|
+
It filters by `roomId` or `roomCondition` and pages up to 500 rooms.
|
|
59
|
+
Conditions are `clean` or `dirty`; `inspected` is also reported on properties with that feature.
|
|
60
|
+
|
|
61
|
+
`housekeeping.update` first reads the room's status to prove the room belongs to the connected property.
|
|
62
|
+
It then sets `roomCondition` to `clean` or `dirty` and, when given, `doNotDisturb`.
|
|
63
|
+
Cloudbeds toggles the condition when the request omits it, so the connector always sends an explicit condition.
|
|
64
|
+
Setting an explicit condition is idempotent, so retries are safe.
|
|
65
|
+
A receipt that does not echo the requested room and condition is reported as indeterminate.
|
|
66
|
+
|
|
67
|
+
### Rates and availability
|
|
68
|
+
|
|
69
|
+
`rate-plans.list` reads publicly sellable rate plans for a stay of 1 to 31 nights, optionally for one room type.
|
|
70
|
+
It returns each rate's ID, plan ID and public name, room type, room rate, total rate, rooms available, and whether it is derived.
|
|
71
|
+
Allotment-block rates are not returned by Cloudbeds.
|
|
72
|
+
It refuses rows that name another property or another requested room type.
|
|
19
73
|
|
|
20
74
|
`room-types.available` reads Cloudbeds room-type availability for a requested stay of 1 to 31 nights.
|
|
21
75
|
It sends one pinned property ID and explicit guest counts to `getAvailableRoomTypes`.
|
|
22
76
|
The result contains each room type's reported available count and rate, plus property page fields.
|
|
23
77
|
Cloudbeds pages properties, not room types; `roomCount` may exceed `pageSize` for one property.
|
|
24
78
|
The connector accepts only the one pinned property row and rejects unexpected additional property rows.
|
|
25
|
-
|
|
79
|
+
Rates and availability are provider snapshots at fetch time, not a held room or confirmed booking.
|
|
80
|
+
|
|
81
|
+
### Folio items
|
|
26
82
|
|
|
27
83
|
`folio-items.post` writes one unpaid custom item to a reservation.
|
|
28
84
|
It first reads the reservation by ID and checks that Cloudbeds returns the connected property and requested reservation ID.
|
|
@@ -33,10 +89,34 @@ Cloudbeds records custom tax labels as free text and does not calculate the tax
|
|
|
33
89
|
The caller must confirm the tax and price before granting the write.
|
|
34
90
|
Cloudbeds can return a `notice` on duplicate `referenceID`; the connector reports recognized duplicate notices without claiming it created a second item.
|
|
35
91
|
Other receipts without a sold product ID remain indeterminate and require operator reconciliation.
|
|
36
|
-
The connection test proves reservation read access; it does not prove room read or write access.
|
|
37
|
-
Only a real approved write can prove the write scope.
|
|
38
92
|
|
|
39
|
-
|
|
93
|
+
### Webhooks
|
|
94
|
+
|
|
95
|
+
`webhooks.subscribe` accepts only these reservation events: `created`, `status_changed`, `dates_changed`, `accommodation_changed`, and `deleted`.
|
|
96
|
+
`status_changed` carries confirmation, cancellation, check-in, check-out, and no-show transitions.
|
|
97
|
+
The endpoint must be an https URL without credentials or a fragment.
|
|
98
|
+
The connector first lists subscriptions and reuses one with the same event and endpoint.
|
|
99
|
+
Pass `authHeaderName` and `authHeaderValue` together to have Cloudbeds send that header on every delivery.
|
|
100
|
+
Cloudbeds never returns the value from `getWebhooks`, and a re-post with a new value rotates it.
|
|
101
|
+
The connector never returns the value and redacts it from errors.
|
|
102
|
+
`webhooks.delete` deletes a subscription only after it appears in the property's list, and reports a missing one as already absent.
|
|
103
|
+
|
|
104
|
+
Cloudbeds does not sign webhook bodies.
|
|
105
|
+
`cloudbedsWebhookProvider` from `@tangle-network/agent-integrations/webhooks` authenticates a delivery by comparing the `CLOUDBEDS_WEBHOOK_AUTH_HEADER` (`x-tangle-webhook-token`) header with the per-connection secret in constant time.
|
|
106
|
+
Subscribe with `authHeaderName: CLOUDBEDS_WEBHOOK_AUTH_HEADER` and that secret as `authHeaderValue`.
|
|
107
|
+
The header proves the sender holds the secret; it does not bind the body or prevent a replay.
|
|
108
|
+
Treat every event as a hint: re-read the reservation with `reservations.get` before acting on it.
|
|
109
|
+
|
|
110
|
+
The provider emits `cloudbeds.<object>.<action>` events, such as `cloudbeds.reservation.status_changed`, from a closed catalog of the five subscribed events.
|
|
111
|
+
Its payload is the delivered JSON plus string `propertyId` and `reservationId` fields normalized from the inconsistent spellings Cloudbeds sends (`propertyID`, `propertyID_str`, `propertyId`; `reservationID`, `reservationId`).
|
|
112
|
+
`providerEventId` is the hex SHA-256 of `event|propertyId|reservationId|timestamp`; Cloudbeds keeps the original timestamp across its five retries, so a retry deduplicates.
|
|
113
|
+
Other events are acknowledged as no-ops.
|
|
114
|
+
Cloudbeds retries a delivery that takes more than 2 seconds, so the receiver must enqueue the event and return 2xx before doing any work.
|
|
115
|
+
|
|
116
|
+
The connection test proves reservation read access only.
|
|
117
|
+
Only a real approved write can prove a write scope.
|
|
118
|
+
|
|
119
|
+
Source: [Cloudbeds PMS OpenAPI](https://github.com/cloudbeds/openapi-specs/blob/main/src/pms-v1.3-openapi.yaml), [Cloudbeds webhooks guide](https://developers.cloudbeds.com/docs/webhooks-1), and [Cloudbeds point of sale guide](https://developers.cloudbeds.com/docs/point-of-sale).
|
|
40
120
|
|
|
41
121
|
## PriceLabs
|
|
42
122
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-integrations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.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": {
|