@norbix.ai/ts 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/webhooks/index.cjs +177 -17
- package/dist/webhooks/index.cjs.map +1 -1
- package/dist/webhooks/index.d.cts +322 -22
- package/dist/webhooks/index.d.ts +322 -22
- package/dist/webhooks/index.js +176 -18
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +11 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/webhooks/errors.ts","../../src/webhooks/events.ts","../../src/webhooks/headers.ts","../../src/webhooks/parse.ts","../../src/webhooks/receiver.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export class NorbixWebhookError extends Error {\n readonly code: string;\n\n constructor(message: string, code: string) {\n super(message);\n this.name = 'NorbixWebhookError';\n this.code = code;\n }\n}\n\nexport class NorbixWebhookSignatureError extends NorbixWebhookError {\n constructor(message: string) {\n super(message, 'WEBHOOK_SIGNATURE_INVALID');\n this.name = 'NorbixWebhookSignatureError';\n }\n}\n\nexport class NorbixWebhookParseError extends NorbixWebhookError {\n constructor(message: string) {\n super(message, 'WEBHOOK_PARSE_INVALID');\n this.name = 'NorbixWebhookParseError';\n }\n}\n","/**\n * Closed catalog of event names a destination may subscribe to.\n * Source: gateway Domain trigger event name value objects.\n */\nexport const NORBIX_WEBHOOK_EVENT_NAMES = [\n 'database.record.inserted',\n 'database.record.updated',\n 'database.record.deleted',\n 'database.record.replaced',\n 'database.record.responsibilityChanged',\n 'database.records.inserted',\n 'database.records.updated',\n 'database.records.deleted',\n 'membership.user.registered',\n 'membership.user.invited',\n 'membership.user.verified',\n 'membership.user.updated',\n 'membership.user.deleted',\n 'membership.user.blocked',\n 'membership.user.reactivated',\n 'files.file.uploaded',\n 'files.file.deleted',\n] as const;\n\nexport type NorbixWebhookEventName = (typeof NORBIX_WEBHOOK_EVENT_NAMES)[number];\n\nexport interface NorbixWebhookEventGroup {\n group: string;\n label: string;\n events: readonly string[];\n}\n\nexport const NORBIX_WEBHOOK_EVENT_GROUPS: NorbixWebhookEventGroup[] = [\n {\n group: 'database',\n label: 'Database',\n events: [\n 'database.record.inserted',\n 'database.record.updated',\n 'database.record.deleted',\n 'database.record.replaced',\n 'database.record.responsibilityChanged',\n 'database.records.inserted',\n 'database.records.updated',\n 'database.records.deleted',\n ],\n },\n {\n group: 'membership',\n label: 'Membership',\n events: [\n 'membership.user.registered',\n 'membership.user.invited',\n 'membership.user.verified',\n 'membership.user.updated',\n 'membership.user.deleted',\n 'membership.user.blocked',\n 'membership.user.reactivated',\n ],\n },\n {\n group: 'files',\n label: 'Files',\n events: ['files.file.uploaded', 'files.file.deleted'],\n },\n];\n","/**\n * Outbound Norbix webhook delivery headers.\n *\n * Source of truth: gateway `WebhookDeliveryClient` (NOT `AuthStatics` /\n * `ConfigureCors`, which define **inbound API** headers like\n * `norbix-account-id` / `norbix-project-id` for studio → gateway calls).\n *\n * @see gateway/src/Isidos.CodeMash.Services.Webhook/Dispatching/WebhookDeliveryClient.cs\n */\nexport const NORBIX_WEBHOOK_HEADERS = {\n event: 'X-Norbix-Event',\n delivery: 'X-Norbix-Delivery',\n idempotencyKey: 'Idempotency-Key',\n account: 'X-Norbix-Account',\n project: 'X-Norbix-Project',\n integration: 'X-Norbix-Integration',\n destination: 'X-Norbix-Destination',\n signature: 'X-Norbix-Signature',\n timestamp: 'X-Norbix-Timestamp',\n} as const;\n\nexport type NorbixWebhookHeaderName =\n (typeof NORBIX_WEBHOOK_HEADERS)[keyof typeof NORBIX_WEBHOOK_HEADERS];\n","import { createHmac, timingSafeEqual } from 'node:crypto';\n\nimport { NorbixWebhookParseError } from './errors.js';\nimport { NORBIX_WEBHOOK_HEADERS } from './headers.js';\nimport type {\n NorbixWebhookDeliveryHeaders,\n NorbixWebhookEnvelope,\n NorbixWebhookHeaderBag,\n NorbixWebhookVerifyOptions,\n} from './types.js';\n\nfunction headerValue(headers: NorbixWebhookHeaderBag, name: string): string | null {\n if (headers instanceof Headers) {\n return headers.get(name);\n }\n\n const direct = headers[name];\n if (direct !== undefined) {\n return Array.isArray(direct) ? (direct[0] ?? null) : direct;\n }\n\n const lower = name.toLowerCase();\n const lowerDirect = headers[lower];\n if (lowerDirect !== undefined) {\n return Array.isArray(lowerDirect) ? (lowerDirect[0] ?? null) : lowerDirect;\n }\n\n // Some frameworks preserve original casing on header keys.\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === lower) {\n return Array.isArray(value) ? (value[0] ?? null) : (value ?? null);\n }\n }\n\n return null;\n}\n\n/** Read Norbix delivery headers from an incoming HTTP request. */\nexport function parseNorbixWebhookHeaders(\n headers: NorbixWebhookHeaderBag,\n): NorbixWebhookDeliveryHeaders {\n const deliveryId =\n headerValue(headers, NORBIX_WEBHOOK_HEADERS.delivery) ??\n headerValue(headers, NORBIX_WEBHOOK_HEADERS.idempotencyKey);\n\n return {\n event: headerValue(headers, NORBIX_WEBHOOK_HEADERS.event),\n deliveryId,\n idempotencyKey: headerValue(headers, NORBIX_WEBHOOK_HEADERS.idempotencyKey),\n accountId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.account),\n projectId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.project),\n integrationId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.integration),\n destinationId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.destination),\n signature: headerValue(headers, NORBIX_WEBHOOK_HEADERS.signature),\n timestamp: headerValue(headers, NORBIX_WEBHOOK_HEADERS.timestamp),\n };\n}\n\n/** Parse the JSON envelope from the raw POST body. */\nexport function parseNorbixWebhookEnvelope<TData = unknown>(\n rawBody: string,\n): NorbixWebhookEnvelope<TData> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawBody);\n } catch {\n throw new NorbixWebhookParseError('Webhook body is not valid JSON');\n }\n\n if (!parsed || typeof parsed !== 'object') {\n throw new NorbixWebhookParseError('Webhook body must be a JSON object');\n }\n\n const envelope = parsed as Partial<NorbixWebhookEnvelope<TData>>;\n if (typeof envelope.id !== 'string' || !envelope.id) {\n throw new NorbixWebhookParseError('Webhook envelope missing id');\n }\n if (typeof envelope.event !== 'string' || !envelope.event) {\n throw new NorbixWebhookParseError('Webhook envelope missing event');\n }\n\n return envelope as NorbixWebhookEnvelope<TData>;\n}\n\nexport interface NorbixWebhookSignatureVerification {\n ok: boolean;\n reason?: string;\n}\n\n/**\n * Verify X-Norbix-Signature against the raw body.\n * Algorithm (gateway WebhookDeliveryClient.Sign):\n * sha256=<hex> HMAC-SHA256(secret, \"<timestamp>.<rawBody>\")\n */\nexport function verifyNorbixWebhookSignature(\n input: {\n rawBody: string;\n signature?: string | null;\n timestamp?: string | null;\n } & NorbixWebhookVerifyOptions,\n): NorbixWebhookSignatureVerification {\n const { secret, rawBody, signature, timestamp, toleranceSeconds = 300 } = input;\n\n if (!signature) {\n return { ok: false, reason: 'missing X-Norbix-Signature header' };\n }\n if (!timestamp) {\n return { ok: false, reason: 'missing X-Norbix-Timestamp header' };\n }\n\n if (toleranceSeconds > 0) {\n const sent = Number(timestamp);\n if (!Number.isFinite(sent)) {\n return { ok: false, reason: 'X-Norbix-Timestamp is not a number' };\n }\n const ageSeconds = Math.abs(Date.now() / 1000 - sent);\n if (ageSeconds > toleranceSeconds) {\n return {\n ok: false,\n reason: `timestamp outside ${toleranceSeconds}s tolerance (age ${Math.round(ageSeconds)}s)`,\n };\n }\n }\n\n const expected = computeNorbixWebhookSignature(secret, timestamp, rawBody);\n if (!timingSafeEqualUtf8(expected, signature)) {\n return { ok: false, reason: 'signature mismatch' };\n }\n\n return { ok: true };\n}\n\nexport function computeNorbixWebhookSignature(\n secret: string,\n timestamp: string,\n rawBody: string,\n): string {\n return `sha256=${hmacSha256Hex(secret, `${timestamp}.${rawBody}`)}`;\n}\n\nfunction hmacSha256Hex(secret: string, payload: string): string {\n return createHmac('sha256', secret).update(payload, 'utf8').digest('hex');\n}\n\nfunction timingSafeEqualUtf8(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'utf8');\n const bufB = Buffer.from(b, 'utf8');\n if (bufA.length !== bufB.length) return false;\n return timingSafeEqual(bufA, bufB);\n}\n","import { NorbixWebhookSignatureError } from './errors.js';\nimport {\n parseNorbixWebhookEnvelope,\n parseNorbixWebhookHeaders,\n verifyNorbixWebhookSignature,\n} from './parse.js';\nimport type {\n NorbixWebhookContext,\n NorbixWebhookHandleInput,\n NorbixWebhookHandleResult,\n NorbixWebhookHandler,\n NorbixWebhookReceiverOptions,\n} from './types.js';\n\n/**\n * Register handlers for inbound Norbix webhook deliveries (trigger → destination POST).\n *\n * Triggers with a `WebhookCall` action publish events to configured destinations.\n * This receiver verifies the HMAC signature, parses the envelope, and dispatches\n * to per-event handlers (similar to Stripe's webhook pattern).\n *\n * @example\n * ```ts\n * const receiver = new NorbixWebhookReceiver({\n * secret: process.env.NORBIX_WEBHOOK_SECRET,\n * });\n *\n * receiver.on('database.record.inserted', async (event) => {\n * console.log('inserted', event.data);\n * });\n *\n * receiver.onDefault(async (event) => {\n * console.log('unhandled', event.event);\n * });\n *\n * await receiver.handle({ rawBody, headers: req.headers });\n * ```\n */\nexport class NorbixWebhookReceiver {\n private readonly handlers = new Map<string, NorbixWebhookHandler>();\n private defaultHandler?: NorbixWebhookHandler;\n\n constructor(private readonly options: NorbixWebhookReceiverOptions = {}) {}\n\n /** Handle a specific event name (e.g. membership.user.registered). */\n on(event: string, handler: NorbixWebhookHandler): this {\n this.handlers.set(event, handler);\n return this;\n }\n\n /** Fallback when no event-specific handler is registered. */\n onDefault(handler: NorbixWebhookHandler): this {\n this.defaultHandler = handler;\n return this;\n }\n\n /**\n * Verify (when secret configured), parse, and dispatch the delivery.\n * Returns 200-worthy result — throw NorbixWebhookSignatureError for 401.\n */\n async handle(input: NorbixWebhookHandleInput): Promise<NorbixWebhookHandleResult> {\n const deliveryHeaders = parseNorbixWebhookHeaders(input.headers);\n const shouldVerify = input.verify !== false && !!this.options.secret;\n let verified: boolean | null = null;\n\n if (shouldVerify && this.options.secret) {\n const result = verifyNorbixWebhookSignature({\n secret: this.options.secret,\n rawBody: input.rawBody,\n signature: deliveryHeaders.signature,\n timestamp: deliveryHeaders.timestamp,\n toleranceSeconds: this.options.toleranceSeconds ?? 300,\n });\n if (!result.ok) {\n throw new NorbixWebhookSignatureError(result.reason ?? 'Invalid signature');\n }\n verified = true;\n }\n\n const envelope = parseNorbixWebhookEnvelope(input.rawBody);\n const ctx: NorbixWebhookContext = {\n path: input.path,\n headers: {\n ...deliveryHeaders,\n event: deliveryHeaders.event ?? envelope.event,\n deliveryId: deliveryHeaders.deliveryId ?? envelope.id,\n accountId: deliveryHeaders.accountId ?? envelope.accountId,\n projectId: deliveryHeaders.projectId ?? envelope.projectId,\n },\n verified,\n };\n\n const handler = this.handlers.get(envelope.event) ?? this.defaultHandler;\n let handled = false;\n if (handler) {\n await handler(envelope, ctx);\n handled = true;\n }\n\n return {\n received: true,\n event: envelope.event,\n deliveryId: envelope.id,\n verified,\n handled,\n triggerId: envelope.triggerId,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/webhooks/errors.ts","../../src/webhooks/events.ts","../../src/webhooks/headers.ts","../../src/webhooks/normalize.ts","../../src/webhooks/parse.ts","../../src/webhooks/receiver.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export class NorbixWebhookError extends Error {\n readonly code: string;\n\n constructor(message: string, code: string) {\n super(message);\n this.name = 'NorbixWebhookError';\n this.code = code;\n }\n}\n\nexport class NorbixWebhookSignatureError extends NorbixWebhookError {\n constructor(message: string) {\n super(message, 'WEBHOOK_SIGNATURE_INVALID');\n this.name = 'NorbixWebhookSignatureError';\n }\n}\n\nexport class NorbixWebhookParseError extends NorbixWebhookError {\n constructor(message: string) {\n super(message, 'WEBHOOK_PARSE_INVALID');\n this.name = 'NorbixWebhookParseError';\n }\n}\n","/**\n * Closed catalog of event names a destination may subscribe to.\n * Source: gateway Domain trigger event name value objects.\n */\nexport const NORBIX_WEBHOOK_EVENT_NAMES = [\n 'database.record.inserted',\n 'database.record.updated',\n 'database.record.deleted',\n 'database.record.replaced',\n 'database.record.responsibilityChanged',\n 'database.records.inserted',\n 'database.records.updated',\n 'database.records.deleted',\n 'membership.user.registered',\n 'membership.user.invited',\n 'membership.user.verified',\n 'membership.user.updated',\n 'membership.user.deleted',\n 'membership.user.blocked',\n 'membership.user.reactivated',\n 'files.file.uploaded',\n 'files.file.deleted',\n] as const;\n\nexport type NorbixWebhookEventName = (typeof NORBIX_WEBHOOK_EVENT_NAMES)[number];\n\n/**\n * Named event constants — use these instead of raw strings so app code gets\n * autocomplete and is typo-safe.\n *\n * @example\n * receiver.on(NorbixWebhookEvents.Membership.UserRegistered, (user, event) => {});\n */\nexport const NorbixWebhookEvents = {\n Database: {\n RecordInserted: 'database.record.inserted',\n RecordUpdated: 'database.record.updated',\n RecordDeleted: 'database.record.deleted',\n RecordReplaced: 'database.record.replaced',\n RecordResponsibilityChanged: 'database.record.responsibilityChanged',\n RecordsInserted: 'database.records.inserted',\n RecordsUpdated: 'database.records.updated',\n RecordsDeleted: 'database.records.deleted',\n },\n Membership: {\n UserRegistered: 'membership.user.registered',\n UserInvited: 'membership.user.invited',\n UserVerified: 'membership.user.verified',\n UserUpdated: 'membership.user.updated',\n UserDeleted: 'membership.user.deleted',\n UserBlocked: 'membership.user.blocked',\n UserReactivated: 'membership.user.reactivated',\n },\n Files: {\n FileUploaded: 'files.file.uploaded',\n FileDeleted: 'files.file.deleted',\n },\n} as const satisfies Record<string, Record<string, NorbixWebhookEventName>>;\n\nexport interface NorbixWebhookEventGroup {\n group: string;\n label: string;\n events: readonly string[];\n}\n\nexport const NORBIX_WEBHOOK_EVENT_GROUPS: NorbixWebhookEventGroup[] = [\n {\n group: 'database',\n label: 'Database',\n events: [\n 'database.record.inserted',\n 'database.record.updated',\n 'database.record.deleted',\n 'database.record.replaced',\n 'database.record.responsibilityChanged',\n 'database.records.inserted',\n 'database.records.updated',\n 'database.records.deleted',\n ],\n },\n {\n group: 'membership',\n label: 'Membership',\n events: [\n 'membership.user.registered',\n 'membership.user.invited',\n 'membership.user.verified',\n 'membership.user.updated',\n 'membership.user.deleted',\n 'membership.user.blocked',\n 'membership.user.reactivated',\n ],\n },\n {\n group: 'files',\n label: 'Files',\n events: ['files.file.uploaded', 'files.file.deleted'],\n },\n];\n","/**\n * Outbound Norbix webhook delivery headers.\n *\n * Source of truth: gateway `WebhookDeliveryClient` (NOT `AuthStatics` /\n * `ConfigureCors`, which define **inbound API** headers like\n * `norbix-account-id` / `norbix-project-id` for studio → gateway calls).\n *\n * @see gateway/src/Isidos.CodeMash.Services.Webhook/Dispatching/WebhookDeliveryClient.cs\n */\nexport const NORBIX_WEBHOOK_HEADERS = {\n event: 'X-Norbix-Event',\n delivery: 'X-Norbix-Delivery',\n idempotencyKey: 'Idempotency-Key',\n account: 'X-Norbix-Account',\n project: 'X-Norbix-Project',\n integration: 'X-Norbix-Integration',\n destination: 'X-Norbix-Destination',\n signature: 'X-Norbix-Signature',\n timestamp: 'X-Norbix-Timestamp',\n} as const;\n\nexport type NorbixWebhookHeaderName =\n (typeof NORBIX_WEBHOOK_HEADERS)[keyof typeof NORBIX_WEBHOOK_HEADERS];\n","import type { NorbixWebhookEventMetadata } from './event-data.js';\nimport type { NorbixWebhookEnvelope } from './types.js';\n\n/** Result of normalising a wire envelope for a typed handler. */\nexport interface NorbixWebhookNormalized {\n /** The payload-first value handed to a typed handler. */\n payload: unknown;\n /** Identifiers lifted off the wire payload. */\n metadata: NorbixWebhookEventMetadata;\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Turn a raw wire envelope into `{ payload, metadata }`.\n *\n * - Entity events → `payload` is the entity (user / document / file).\n * - Mutation events → `payload` is `{ from, to }`.\n * - Batch events → `payload` is the array.\n *\n * Wrapper ids (record id, schema, user id, …) are moved onto `metadata`.\n * Unknown events fall back to `payload = envelope.data`, `metadata = {}`.\n */\nexport function normalizeNorbixWebhook(envelope: NorbixWebhookEnvelope): NorbixWebhookNormalized {\n const event = envelope.event;\n const data: unknown = envelope.data;\n const d = isObject(data) ? data : {};\n\n // Database events share schema/record metadata.\n if (event.startsWith('database.')) {\n const metadata: NorbixWebhookEventMetadata = {};\n if (typeof d.schemaName === 'string') {\n const schema = isObject(d.schema) ? d.schema : null;\n const schemaId = schema && typeof schema.id === 'string' ? schema.id : null;\n metadata.schema = { id: schemaId, name: d.schemaName };\n }\n if (typeof d.integrationId === 'string') metadata.integrationId = d.integrationId;\n if (typeof d.id === 'string') metadata.record = { id: d.id };\n if (Array.isArray(d.ids)) metadata.records = { ids: d.ids as string[] };\n\n switch (event) {\n case 'database.record.inserted':\n case 'database.record.deleted':\n return { payload: d.document, metadata };\n case 'database.record.updated':\n case 'database.record.replaced':\n return { payload: { from: d.from, to: d.to }, metadata };\n case 'database.records.inserted':\n return { payload: d.documents ?? [], metadata };\n default:\n // records.updated / records.deleted / responsibilityChanged — pass data through.\n return { payload: data, metadata };\n }\n }\n\n // Membership events: entity directly, except updated (mutation).\n if (event.startsWith('membership.')) {\n const metadata: NorbixWebhookEventMetadata = {};\n if (typeof d.id === 'string') metadata.user = { id: d.id };\n\n switch (event) {\n case 'membership.user.registered':\n case 'membership.user.verified':\n case 'membership.user.blocked':\n case 'membership.user.reactivated':\n // wire sends { id, to } / { id, from?, to } — the entity is `to`.\n return { payload: d.to, metadata };\n case 'membership.user.deleted':\n // wire sends { id, from } — the entity is `from`.\n return { payload: d.from, metadata };\n case 'membership.user.updated':\n return { payload: { from: d.from, to: d.to }, metadata };\n case 'membership.user.invited':\n return { payload: { email: d.email }, metadata };\n default:\n return { payload: data, metadata };\n }\n }\n\n // Files events.\n if (event.startsWith('files.')) {\n const metadata: NorbixWebhookEventMetadata = {};\n if (typeof d.integrationId === 'string') metadata.integrationId = d.integrationId;\n\n switch (event) {\n case 'files.file.uploaded':\n return { payload: d.file, metadata };\n case 'files.file.deleted':\n return { payload: { path: d.path }, metadata };\n default:\n return { payload: data, metadata };\n }\n }\n\n return { payload: data, metadata: {} };\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\n\nimport { NorbixWebhookParseError } from './errors.js';\nimport { NORBIX_WEBHOOK_HEADERS } from './headers.js';\nimport type {\n NorbixWebhookDeliveryHeaders,\n NorbixWebhookEnvelope,\n NorbixWebhookHeaderBag,\n NorbixWebhookVerifyOptions,\n} from './types.js';\n\nfunction headerValue(headers: NorbixWebhookHeaderBag, name: string): string | null {\n if (headers instanceof Headers) {\n return headers.get(name);\n }\n\n const direct = headers[name];\n if (direct !== undefined) {\n return Array.isArray(direct) ? (direct[0] ?? null) : direct;\n }\n\n const lower = name.toLowerCase();\n const lowerDirect = headers[lower];\n if (lowerDirect !== undefined) {\n return Array.isArray(lowerDirect) ? (lowerDirect[0] ?? null) : lowerDirect;\n }\n\n // Some frameworks preserve original casing on header keys.\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === lower) {\n return Array.isArray(value) ? (value[0] ?? null) : (value ?? null);\n }\n }\n\n return null;\n}\n\n/** Read Norbix delivery headers from an incoming HTTP request. */\nexport function parseNorbixWebhookHeaders(\n headers: NorbixWebhookHeaderBag,\n): NorbixWebhookDeliveryHeaders {\n const deliveryId =\n headerValue(headers, NORBIX_WEBHOOK_HEADERS.delivery) ??\n headerValue(headers, NORBIX_WEBHOOK_HEADERS.idempotencyKey);\n\n return {\n event: headerValue(headers, NORBIX_WEBHOOK_HEADERS.event),\n deliveryId,\n idempotencyKey: headerValue(headers, NORBIX_WEBHOOK_HEADERS.idempotencyKey),\n accountId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.account),\n projectId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.project),\n integrationId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.integration),\n destinationId: headerValue(headers, NORBIX_WEBHOOK_HEADERS.destination),\n signature: headerValue(headers, NORBIX_WEBHOOK_HEADERS.signature),\n timestamp: headerValue(headers, NORBIX_WEBHOOK_HEADERS.timestamp),\n };\n}\n\n/** Parse the JSON envelope from the raw POST body. */\nexport function parseNorbixWebhookEnvelope<TData = unknown>(\n rawBody: string,\n): NorbixWebhookEnvelope<TData> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(rawBody);\n } catch {\n throw new NorbixWebhookParseError('Webhook body is not valid JSON');\n }\n\n if (!parsed || typeof parsed !== 'object') {\n throw new NorbixWebhookParseError('Webhook body must be a JSON object');\n }\n\n const envelope = parsed as Partial<NorbixWebhookEnvelope<TData>>;\n if (typeof envelope.id !== 'string' || !envelope.id) {\n throw new NorbixWebhookParseError('Webhook envelope missing id');\n }\n if (typeof envelope.event !== 'string' || !envelope.event) {\n throw new NorbixWebhookParseError('Webhook envelope missing event');\n }\n\n return envelope as NorbixWebhookEnvelope<TData>;\n}\n\nexport interface NorbixWebhookSignatureVerification {\n ok: boolean;\n reason?: string;\n}\n\n/**\n * Verify X-Norbix-Signature against the raw body.\n * Algorithm (gateway WebhookDeliveryClient.Sign):\n * sha256=<hex> HMAC-SHA256(secret, \"<timestamp>.<rawBody>\")\n */\nexport function verifyNorbixWebhookSignature(\n input: {\n rawBody: string;\n signature?: string | null;\n timestamp?: string | null;\n } & NorbixWebhookVerifyOptions,\n): NorbixWebhookSignatureVerification {\n const { secret, rawBody, signature, timestamp, toleranceSeconds = 300 } = input;\n\n if (!signature) {\n return { ok: false, reason: 'missing X-Norbix-Signature header' };\n }\n if (!timestamp) {\n return { ok: false, reason: 'missing X-Norbix-Timestamp header' };\n }\n\n if (toleranceSeconds > 0) {\n const sent = Number(timestamp);\n if (!Number.isFinite(sent)) {\n return { ok: false, reason: 'X-Norbix-Timestamp is not a number' };\n }\n const ageSeconds = Math.abs(Date.now() / 1000 - sent);\n if (ageSeconds > toleranceSeconds) {\n return {\n ok: false,\n reason: `timestamp outside ${toleranceSeconds}s tolerance (age ${Math.round(ageSeconds)}s)`,\n };\n }\n }\n\n const expected = computeNorbixWebhookSignature(secret, timestamp, rawBody);\n if (!timingSafeEqualUtf8(expected, signature)) {\n return { ok: false, reason: 'signature mismatch' };\n }\n\n return { ok: true };\n}\n\nexport function computeNorbixWebhookSignature(\n secret: string,\n timestamp: string,\n rawBody: string,\n): string {\n return `sha256=${hmacSha256Hex(secret, `${timestamp}.${rawBody}`)}`;\n}\n\nfunction hmacSha256Hex(secret: string, payload: string): string {\n return createHmac('sha256', secret).update(payload, 'utf8').digest('hex');\n}\n\nfunction timingSafeEqualUtf8(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'utf8');\n const bufB = Buffer.from(b, 'utf8');\n if (bufA.length !== bufB.length) return false;\n return timingSafeEqual(bufA, bufB);\n}\n","import type { CodeMashHub2 } from '../types/hub2.dtos.js';\n\nimport { NorbixWebhookSignatureError } from './errors.js';\nimport type { NorbixWebhookPayload } from './event-data.js';\nimport type { NorbixWebhookEventName } from './events.js';\nimport { normalizeNorbixWebhook } from './normalize.js';\nimport {\n parseNorbixWebhookEnvelope,\n parseNorbixWebhookHeaders,\n verifyNorbixWebhookSignature,\n} from './parse.js';\nimport type { NorbixWebhookMutation } from './payloads.js';\nimport type {\n NorbixWebhookContext,\n NorbixWebhookEvent,\n NorbixWebhookHandleInput,\n NorbixWebhookHandleResult,\n NorbixWebhookHandler,\n NorbixWebhookRawHandler,\n NorbixWebhookReceiverOptions,\n} from './types.js';\n\ninterface ResolvedConfig {\n secret?: string;\n toleranceSeconds: number;\n projectId?: string;\n accountId?: string;\n}\n\nfunction readEnv(): Record<string, string | undefined> {\n return typeof process !== 'undefined' && process.env ? process.env : {};\n}\n\nfunction toNumber(value: string | undefined): number | undefined {\n if (value == null || value === '') return undefined;\n const n = Number(value);\n return Number.isFinite(n) ? n : undefined;\n}\n\nfunction resolveConfig(options: NorbixWebhookReceiverOptions): ResolvedConfig {\n const env = readEnv();\n const tolerance = options.toleranceSeconds ?? toNumber(env.NORBIX_WEBHOOK_TOLERANCE_SECONDS);\n return {\n secret: options.secret ?? env.NORBIX_WEBHOOK_SIGNING_SECRET,\n projectId: options.projectId ?? env.NORBIX_PROJECT_ID,\n accountId: options.accountId ?? env.NORBIX_ACCOUNT_ID,\n toleranceSeconds: Number.isFinite(tolerance) ? (tolerance as number) : 300,\n };\n}\n\n/** Internal record of a registered handler and how to invoke it. */\ninterface Registration {\n raw: boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- holds either handler shape\n fn: (...args: any[]) => void | Promise<void>;\n}\n\n/**\n * Register handlers for inbound Norbix webhook deliveries (trigger → destination POST).\n *\n * Verifies the HMAC signature, parses the envelope, normalises the payload, and\n * dispatches to per-event handlers.\n *\n * @example\n * ```ts\n * import { NorbixWebhookReceiver, NorbixWebhookEvents } from '@norbix.ai/ts/webhooks';\n * import type { CodeMashHub2 } from '@norbix.ai/ts/types/hub';\n *\n * const receiver = new NorbixWebhookReceiver(); // reads env\n *\n * // Typed: first arg IS the payload, second is metadata.\n * receiver.on<CodeMashHub2.UserDto>(\n * NorbixWebhookEvents.Membership.UserRegistered,\n * (user, event) => {\n * user.email ?? user.userName;\n * event.metadata.user?.id;\n * },\n * );\n *\n * // Mutation: payload is { from, to }.\n * receiver.on(NorbixWebhookEvents.Membership.UserUpdated, (user, event) => {\n * if (user.from.email !== user.to.email) { ... }\n * });\n *\n * // Raw escape hatch: (envelope, ctx).\n * receiver.onRaw(NorbixWebhookEvents.Files.FileUploaded, (envelope, ctx) => {});\n *\n * await receiver.handle({ rawBody, headers: req.headers });\n * ```\n */\nexport class NorbixWebhookReceiver {\n private readonly handlers = new Map<string, Registration>();\n private defaultHandler?: Registration;\n private readonly config: ResolvedConfig;\n\n constructor(options: NorbixWebhookReceiverOptions = {}) {\n this.config = resolveConfig(options);\n }\n\n /* ---- Typed handlers: (payload, event) ---- */\n\n /**\n * Membership user entity events (create / delete / state flip) — `payload`\n * is the user directly. Pass the entity type as the generic, e.g.\n * `on<CodeMashHub2.UserDto>(NorbixWebhookEvents.Membership.UserRegistered, ...)`.\n */\n on<TUser = CodeMashHub2.UserDto>(\n event:\n | 'membership.user.registered'\n | 'membership.user.verified'\n | 'membership.user.blocked'\n | 'membership.user.reactivated'\n | 'membership.user.deleted',\n handler: NorbixWebhookHandler<TUser>,\n ): this;\n /** Membership user-updated mutation — `payload` is `{ from, to }`. */\n on<TUser = CodeMashHub2.UserDto>(\n event: 'membership.user.updated',\n handler: NorbixWebhookHandler<NorbixWebhookMutation<TUser>>,\n ): this;\n /** Database record mutation — pass the document type; `payload` is `{ from, to }`. */\n on<TDocument>(\n event: 'database.record.updated' | 'database.record.replaced',\n handler: NorbixWebhookHandler<NorbixWebhookMutation<TDocument>>,\n ): this;\n /** Database single-record entity — pass the document type; `payload` is the document. */\n on<TDocument>(\n event: 'database.record.inserted' | 'database.record.deleted',\n handler: NorbixWebhookHandler<TDocument>,\n ): this;\n /** Database batch insert — pass the document type; `payload` is the array. */\n on<TDocument>(\n event: 'database.records.inserted',\n handler: NorbixWebhookHandler<TDocument[]>,\n ): this;\n /** Any known catalog event — `payload` typed from the payload map. */\n on<E extends NorbixWebhookEventName>(\n event: E,\n handler: NorbixWebhookHandler<NorbixWebhookPayload<E>>,\n ): this;\n /** Any event name — untyped payload. */\n on(event: string, handler: NorbixWebhookHandler): this;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- overloads pass narrower handlers\n on(event: string, handler: NorbixWebhookHandler<any>): this {\n this.handlers.set(event, { raw: false, fn: handler });\n return this;\n }\n\n /** Register the typed handler for many events at once (skips already-registered). */\n onEach(events: readonly string[], handler: NorbixWebhookHandler): this {\n for (const event of events) {\n if (!this.handlers.has(event)) this.handlers.set(event, { raw: false, fn: handler });\n }\n return this;\n }\n\n /* ---- Raw handlers: (envelope, ctx) ---- */\n\n /** Raw handler for one event — receives the envelope and delivery context. */\n onRaw<TData = unknown>(event: string, handler: NorbixWebhookRawHandler<TData>): this {\n this.handlers.set(event, { raw: true, fn: handler as NorbixWebhookRawHandler });\n return this;\n }\n\n /** Register a raw handler for many events at once (skips already-registered). */\n onEachRaw(events: readonly string[], handler: NorbixWebhookRawHandler): this {\n for (const event of events) {\n if (!this.handlers.has(event)) this.handlers.set(event, { raw: true, fn: handler });\n }\n return this;\n }\n\n /** Fallback (raw) when no event-specific handler is registered. */\n onDefault(handler: NorbixWebhookRawHandler): this {\n this.defaultHandler = { raw: true, fn: handler };\n return this;\n }\n\n /**\n * Verify (when secret configured), parse, normalise, and dispatch the delivery.\n * Throws NorbixWebhookSignatureError (401-worthy) on bad signature or guard\n * mismatch; otherwise returns a 200-worthy result.\n */\n async handle(input: NorbixWebhookHandleInput): Promise<NorbixWebhookHandleResult> {\n const deliveryHeaders = parseNorbixWebhookHeaders(input.headers);\n const shouldVerify = input.verify !== false && !!this.config.secret;\n let verified: boolean | null = null;\n\n if (shouldVerify && this.config.secret) {\n const result = verifyNorbixWebhookSignature({\n secret: this.config.secret,\n rawBody: input.rawBody,\n signature: deliveryHeaders.signature,\n timestamp: deliveryHeaders.timestamp,\n toleranceSeconds: this.config.toleranceSeconds,\n });\n if (!result.ok) {\n throw new NorbixWebhookSignatureError(result.reason ?? 'Invalid signature');\n }\n verified = true;\n }\n\n const envelope = parseNorbixWebhookEnvelope(input.rawBody);\n\n if (this.config.projectId && envelope.projectId !== this.config.projectId) {\n throw new NorbixWebhookSignatureError(\n `delivery projectId ${envelope.projectId} does not match configured ${this.config.projectId}`,\n );\n }\n if (this.config.accountId && envelope.accountId !== this.config.accountId) {\n throw new NorbixWebhookSignatureError(\n `delivery accountId ${envelope.accountId} does not match configured ${this.config.accountId}`,\n );\n }\n\n const ctx: NorbixWebhookContext = {\n path: input.path,\n headers: {\n ...deliveryHeaders,\n event: deliveryHeaders.event ?? envelope.event,\n deliveryId: deliveryHeaders.deliveryId ?? envelope.id,\n accountId: deliveryHeaders.accountId ?? envelope.accountId,\n projectId: deliveryHeaders.projectId ?? envelope.projectId,\n },\n verified,\n };\n\n const registration = this.handlers.get(envelope.event) ?? this.defaultHandler;\n let handled = false;\n\n if (registration) {\n if (registration.raw) {\n await registration.fn(envelope, ctx);\n } else {\n const { payload, metadata } = normalizeNorbixWebhook(envelope);\n const event: NorbixWebhookEvent = {\n name: envelope.event,\n deliveryId: envelope.id,\n createdOn: envelope.createdOn,\n triggerId: envelope.triggerId ?? null,\n correlationId: null,\n accountId: ctx.headers.accountId ?? envelope.accountId,\n projectId: ctx.headers.projectId ?? envelope.projectId,\n integrationId: ctx.headers.integrationId,\n destinationId: ctx.headers.destinationId,\n verified,\n metadata,\n raw: envelope,\n };\n await registration.fn(payload, event);\n }\n handled = true;\n }\n\n return {\n received: true,\n event: envelope.event,\n deliveryId: envelope.id,\n verified,\n handled,\n triggerId: envelope.triggerId,\n };\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norbix.ai/ts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Official TypeScript SDK for Norbix — one client for both API and Hub. Works in Node and the browser.",
|
|
5
5
|
"author": "UAB Isidos",
|
|
6
6
|
"license": "MIT",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@types/node": "^22.7.0",
|
|
99
99
|
"@typescript-eslint/eslint-plugin": "^8.10.0",
|
|
100
100
|
"@typescript-eslint/parser": "^8.10.0",
|
|
101
|
-
"@vitest/coverage-v8": "^
|
|
101
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
102
102
|
"eslint": "^9.13.0",
|
|
103
103
|
"eslint-config-prettier": "^9.1.0",
|
|
104
104
|
"eslint-plugin-import": "^2.31.0",
|
|
@@ -106,9 +106,16 @@
|
|
|
106
106
|
"husky": "^9.1.6",
|
|
107
107
|
"prettier": "^3.3.3",
|
|
108
108
|
"semantic-release": "^24.1.2",
|
|
109
|
-
"tsup": "^8.
|
|
109
|
+
"tsup": "^8.5.1",
|
|
110
110
|
"typescript": "^5.6.3",
|
|
111
|
-
"vitest": "^
|
|
111
|
+
"vitest": "^4.1.8"
|
|
112
|
+
},
|
|
113
|
+
"overrides": {
|
|
114
|
+
"brace-expansion": "^2.0.3",
|
|
115
|
+
"esbuild": "^0.28.1",
|
|
116
|
+
"ip-address": "^10.2.0",
|
|
117
|
+
"npm": "^11.17.0",
|
|
118
|
+
"picomatch": "^4.0.4"
|
|
112
119
|
},
|
|
113
120
|
"publishConfig": {
|
|
114
121
|
"access": "public",
|