@norbix.ai/ts 1.1.0 → 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
package/dist/webhooks/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CodeMashHub2 } from '../types/hub2.dtos.js';
|
|
2
|
+
|
|
1
3
|
declare class NorbixWebhookError extends Error {
|
|
2
4
|
readonly code: string;
|
|
3
5
|
constructor(message: string, code: string);
|
|
@@ -15,6 +17,38 @@ declare class NorbixWebhookParseError extends NorbixWebhookError {
|
|
|
15
17
|
*/
|
|
16
18
|
declare const NORBIX_WEBHOOK_EVENT_NAMES: readonly ["database.record.inserted", "database.record.updated", "database.record.deleted", "database.record.replaced", "database.record.responsibilityChanged", "database.records.inserted", "database.records.updated", "database.records.deleted", "membership.user.registered", "membership.user.invited", "membership.user.verified", "membership.user.updated", "membership.user.deleted", "membership.user.blocked", "membership.user.reactivated", "files.file.uploaded", "files.file.deleted"];
|
|
17
19
|
type NorbixWebhookEventName = (typeof NORBIX_WEBHOOK_EVENT_NAMES)[number];
|
|
20
|
+
/**
|
|
21
|
+
* Named event constants — use these instead of raw strings so app code gets
|
|
22
|
+
* autocomplete and is typo-safe.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* receiver.on(NorbixWebhookEvents.Membership.UserRegistered, (user, event) => {});
|
|
26
|
+
*/
|
|
27
|
+
declare const NorbixWebhookEvents: {
|
|
28
|
+
readonly Database: {
|
|
29
|
+
readonly RecordInserted: "database.record.inserted";
|
|
30
|
+
readonly RecordUpdated: "database.record.updated";
|
|
31
|
+
readonly RecordDeleted: "database.record.deleted";
|
|
32
|
+
readonly RecordReplaced: "database.record.replaced";
|
|
33
|
+
readonly RecordResponsibilityChanged: "database.record.responsibilityChanged";
|
|
34
|
+
readonly RecordsInserted: "database.records.inserted";
|
|
35
|
+
readonly RecordsUpdated: "database.records.updated";
|
|
36
|
+
readonly RecordsDeleted: "database.records.deleted";
|
|
37
|
+
};
|
|
38
|
+
readonly Membership: {
|
|
39
|
+
readonly UserRegistered: "membership.user.registered";
|
|
40
|
+
readonly UserInvited: "membership.user.invited";
|
|
41
|
+
readonly UserVerified: "membership.user.verified";
|
|
42
|
+
readonly UserUpdated: "membership.user.updated";
|
|
43
|
+
readonly UserDeleted: "membership.user.deleted";
|
|
44
|
+
readonly UserBlocked: "membership.user.blocked";
|
|
45
|
+
readonly UserReactivated: "membership.user.reactivated";
|
|
46
|
+
};
|
|
47
|
+
readonly Files: {
|
|
48
|
+
readonly FileUploaded: "files.file.uploaded";
|
|
49
|
+
readonly FileDeleted: "files.file.deleted";
|
|
50
|
+
};
|
|
51
|
+
};
|
|
18
52
|
interface NorbixWebhookEventGroup {
|
|
19
53
|
group: string;
|
|
20
54
|
label: string;
|
|
@@ -44,6 +78,177 @@ declare const NORBIX_WEBHOOK_HEADERS: {
|
|
|
44
78
|
};
|
|
45
79
|
type NorbixWebhookHeaderName = (typeof NORBIX_WEBHOOK_HEADERS)[keyof typeof NORBIX_WEBHOOK_HEADERS];
|
|
46
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Webhook payload shapes, organised by the three trigger kinds:
|
|
83
|
+
*
|
|
84
|
+
* - **Entity** — create / delete / single-property state flip. The payload
|
|
85
|
+
* IS the entity; the event name already says what changed.
|
|
86
|
+
* - **Mutation** — an arbitrary edit you must diff. The payload is `{ from, to }`.
|
|
87
|
+
* - **Batch** — many records at once. The payload is an array.
|
|
88
|
+
*
|
|
89
|
+
* Wrapper identifiers (record id, schema name, …) are NOT part of the payload —
|
|
90
|
+
* they are lifted onto `event.metadata` by the receiver. See `event-data.ts`.
|
|
91
|
+
*/
|
|
92
|
+
/** A before/after pair for a mutation event. */
|
|
93
|
+
interface NorbixWebhookMutation<T> {
|
|
94
|
+
from: T;
|
|
95
|
+
to: T;
|
|
96
|
+
}
|
|
97
|
+
/** `membership.user.updated` payload — a user mutation. */
|
|
98
|
+
type NorbixWebhookUserUpdated = NorbixWebhookMutation<CodeMashHub2.UserDto>;
|
|
99
|
+
/** `database.record.updated` / `replaced` payload — a document mutation. */
|
|
100
|
+
type NorbixWebhookRecordUpdated<TDocument = Record<string, unknown>> = NorbixWebhookMutation<TDocument>;
|
|
101
|
+
/** `membership.user.invited` payload (no full entity yet — just an email). */
|
|
102
|
+
interface NorbixWebhookUserInvited {
|
|
103
|
+
email: string;
|
|
104
|
+
}
|
|
105
|
+
/** `files.file.deleted` payload (no entity — just the path). */
|
|
106
|
+
interface NorbixWebhookFileDeleted {
|
|
107
|
+
path: string;
|
|
108
|
+
}
|
|
109
|
+
/** Wire payload for single-record database events (insert / delete). */
|
|
110
|
+
interface NorbixWebhookWireDatabaseRecord<TDocument = Record<string, unknown>> {
|
|
111
|
+
schemaName: string;
|
|
112
|
+
integrationId: string;
|
|
113
|
+
id: string;
|
|
114
|
+
document: TDocument;
|
|
115
|
+
schema?: CodeMashHub2.DataSchemaDto | null;
|
|
116
|
+
}
|
|
117
|
+
/** Wire payload for database record updated / replaced. */
|
|
118
|
+
interface NorbixWebhookWireDatabaseRecordUpdated<TDocument = Record<string, unknown>> {
|
|
119
|
+
schemaName: string;
|
|
120
|
+
integrationId: string;
|
|
121
|
+
id: string;
|
|
122
|
+
from: TDocument;
|
|
123
|
+
to: TDocument;
|
|
124
|
+
schema?: CodeMashHub2.DataSchemaDto | null;
|
|
125
|
+
}
|
|
126
|
+
/** Wire payload for database.records.inserted (batch). */
|
|
127
|
+
interface NorbixWebhookWireDatabaseRecordsInserted<TDocument = Record<string, unknown>> {
|
|
128
|
+
schemaName: string;
|
|
129
|
+
integrationId: string;
|
|
130
|
+
ids: string[];
|
|
131
|
+
documents: TDocument[];
|
|
132
|
+
schema?: CodeMashHub2.DataSchemaDto | null;
|
|
133
|
+
}
|
|
134
|
+
/** Wire payload for database.records.updated. */
|
|
135
|
+
interface NorbixWebhookWireDatabaseRecordsUpdated {
|
|
136
|
+
schemaName: string;
|
|
137
|
+
integrationId: string;
|
|
138
|
+
matchedCount: number;
|
|
139
|
+
modifiedCount: number;
|
|
140
|
+
update: Record<string, unknown>;
|
|
141
|
+
schema?: CodeMashHub2.DataSchemaDto | null;
|
|
142
|
+
}
|
|
143
|
+
/** Wire payload for database.records.deleted. */
|
|
144
|
+
interface NorbixWebhookWireDatabaseRecordsDeleted {
|
|
145
|
+
schemaName: string;
|
|
146
|
+
integrationId: string;
|
|
147
|
+
deletedCount: number;
|
|
148
|
+
filter: Record<string, unknown>;
|
|
149
|
+
schema?: CodeMashHub2.DataSchemaDto | null;
|
|
150
|
+
}
|
|
151
|
+
/** Wire payload for database.record.responsibilityChanged. */
|
|
152
|
+
interface NorbixWebhookWireDatabaseRecordResponsibilityChanged {
|
|
153
|
+
schemaName: string;
|
|
154
|
+
integrationId: string;
|
|
155
|
+
id: string;
|
|
156
|
+
fromOwnerId: string;
|
|
157
|
+
toOwnerId: string;
|
|
158
|
+
schema?: CodeMashHub2.DataSchemaDto | null;
|
|
159
|
+
}
|
|
160
|
+
/** Wire payload for membership.user.registered. */
|
|
161
|
+
interface NorbixWebhookWireUserRegistered {
|
|
162
|
+
id: string;
|
|
163
|
+
to: CodeMashHub2.UserDto;
|
|
164
|
+
}
|
|
165
|
+
/** Wire payload for membership.user.verified | blocked | reactivated | updated. */
|
|
166
|
+
interface NorbixWebhookWireUserTransition {
|
|
167
|
+
id: string;
|
|
168
|
+
from?: CodeMashHub2.UserDto | null;
|
|
169
|
+
to: CodeMashHub2.UserDto;
|
|
170
|
+
}
|
|
171
|
+
/** Wire payload for membership.user.deleted. */
|
|
172
|
+
interface NorbixWebhookWireUserDeleted {
|
|
173
|
+
id: string;
|
|
174
|
+
from: CodeMashHub2.UserDto;
|
|
175
|
+
}
|
|
176
|
+
/** Wire payload for files.file.uploaded. */
|
|
177
|
+
interface NorbixWebhookWireFileUploaded {
|
|
178
|
+
integrationId: string;
|
|
179
|
+
file: CodeMashHub2.FileResourceRefDto;
|
|
180
|
+
}
|
|
181
|
+
/** Wire payload for files.file.deleted. */
|
|
182
|
+
interface NorbixWebhookWireFileDeleted {
|
|
183
|
+
integrationId: string;
|
|
184
|
+
path: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The normalised payload the receiver hands to a typed handler, per event name.
|
|
189
|
+
*
|
|
190
|
+
* - Entity events resolve to the entity itself (`UserDto`, `FileResourceRefDto`).
|
|
191
|
+
* - Mutation events resolve to `{ from, to }`.
|
|
192
|
+
* - Batch events resolve to an array.
|
|
193
|
+
*
|
|
194
|
+
* For database events the document type is unknown at the catalog level — pass
|
|
195
|
+
* it as the generic on `on<TDocument>(...)` (see `NorbixWebhookPayload`).
|
|
196
|
+
*/
|
|
197
|
+
interface NorbixWebhookPayloadMap<TDocument = Record<string, unknown>> {
|
|
198
|
+
'membership.user.registered': CodeMashHub2.UserDto;
|
|
199
|
+
'membership.user.verified': CodeMashHub2.UserDto;
|
|
200
|
+
'membership.user.blocked': CodeMashHub2.UserDto;
|
|
201
|
+
'membership.user.reactivated': CodeMashHub2.UserDto;
|
|
202
|
+
'membership.user.deleted': CodeMashHub2.UserDto;
|
|
203
|
+
'membership.user.invited': NorbixWebhookUserInvited;
|
|
204
|
+
'membership.user.updated': NorbixWebhookMutation<CodeMashHub2.UserDto>;
|
|
205
|
+
'database.record.inserted': TDocument;
|
|
206
|
+
'database.record.deleted': TDocument;
|
|
207
|
+
'database.record.updated': NorbixWebhookMutation<TDocument>;
|
|
208
|
+
'database.record.replaced': NorbixWebhookMutation<TDocument>;
|
|
209
|
+
'database.records.inserted': TDocument[];
|
|
210
|
+
'database.records.updated': {
|
|
211
|
+
matchedCount: number;
|
|
212
|
+
modifiedCount: number;
|
|
213
|
+
};
|
|
214
|
+
'database.records.deleted': {
|
|
215
|
+
deletedCount: number;
|
|
216
|
+
};
|
|
217
|
+
'database.record.responsibilityChanged': {
|
|
218
|
+
fromOwnerId: string;
|
|
219
|
+
toOwnerId: string;
|
|
220
|
+
};
|
|
221
|
+
'files.file.uploaded': CodeMashHub2.FileResourceRefDto;
|
|
222
|
+
'files.file.deleted': NorbixWebhookFileDeleted;
|
|
223
|
+
}
|
|
224
|
+
/** Resolve the typed payload for a known event (with optional document type). */
|
|
225
|
+
type NorbixWebhookPayload<E extends NorbixWebhookEventName, TDocument = Record<string, unknown>> = NorbixWebhookPayloadMap<TDocument>[E];
|
|
226
|
+
/**
|
|
227
|
+
* Identifiers lifted off the wire payload onto `event.metadata`.
|
|
228
|
+
* Fields are present only for events that carry them.
|
|
229
|
+
*/
|
|
230
|
+
interface NorbixWebhookEventMetadata {
|
|
231
|
+
/** The mutated / created entity id (membership events). */
|
|
232
|
+
user?: {
|
|
233
|
+
id: string;
|
|
234
|
+
};
|
|
235
|
+
/** Schema info for database.* events. `id` is null until the gateway sends it. */
|
|
236
|
+
schema?: {
|
|
237
|
+
id: string | null;
|
|
238
|
+
name: string;
|
|
239
|
+
};
|
|
240
|
+
/** Single record id for single-record database events. */
|
|
241
|
+
record?: {
|
|
242
|
+
id: string;
|
|
243
|
+
};
|
|
244
|
+
/** Record ids for batch database events. */
|
|
245
|
+
records?: {
|
|
246
|
+
ids: string[];
|
|
247
|
+
};
|
|
248
|
+
/** Integration id for files.* events (and database events that carry it). */
|
|
249
|
+
integrationId?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
47
252
|
/** JSON envelope POSTed to every webhook destination. */
|
|
48
253
|
interface NorbixWebhookEnvelope<TData = unknown> {
|
|
49
254
|
/** Stable delivery id — dedupe retries on this (also X-Norbix-Delivery). */
|
|
@@ -88,6 +293,32 @@ interface NorbixWebhookHandleInput {
|
|
|
88
293
|
verify?: boolean;
|
|
89
294
|
}
|
|
90
295
|
type NorbixWebhookHeaderBag = Record<string, string | string[] | undefined> | Headers;
|
|
296
|
+
/**
|
|
297
|
+
* Metadata object passed as the 2nd arg to a typed handler. Carries the
|
|
298
|
+
* delivery facts at the top level and payload identifiers under `metadata`.
|
|
299
|
+
*/
|
|
300
|
+
interface NorbixWebhookEvent {
|
|
301
|
+
/** Logical event name, e.g. "membership.user.registered". */
|
|
302
|
+
name: string;
|
|
303
|
+
/** Stable delivery id — dedupe retries on this. */
|
|
304
|
+
deliveryId: string;
|
|
305
|
+
/** ISO-8601 UTC emit time. */
|
|
306
|
+
createdOn: string;
|
|
307
|
+
triggerId: string | null;
|
|
308
|
+
/** Present once the gateway sends a correlation header/field; else null. */
|
|
309
|
+
correlationId: string | null;
|
|
310
|
+
accountId: string;
|
|
311
|
+
projectId: string;
|
|
312
|
+
integrationId: string | null;
|
|
313
|
+
destinationId: string | null;
|
|
314
|
+
/** true when signature verified; null when verification was skipped. */
|
|
315
|
+
verified: boolean | null;
|
|
316
|
+
/** Identifiers lifted off the wire payload (entity id, schema, record ids). */
|
|
317
|
+
metadata: NorbixWebhookEventMetadata;
|
|
318
|
+
/** Escape hatch: the raw envelope, if a handler needs an unmapped field. */
|
|
319
|
+
raw: NorbixWebhookEnvelope;
|
|
320
|
+
}
|
|
321
|
+
/** Context passed to raw (`onRaw`) handlers alongside the envelope. */
|
|
91
322
|
interface NorbixWebhookContext {
|
|
92
323
|
path?: string;
|
|
93
324
|
headers: NorbixWebhookDeliveryHeaders;
|
|
@@ -103,15 +334,52 @@ interface NorbixWebhookHandleResult {
|
|
|
103
334
|
handled: boolean;
|
|
104
335
|
triggerId?: string | null;
|
|
105
336
|
}
|
|
106
|
-
|
|
107
|
-
type
|
|
337
|
+
/** Typed handler — first arg is the normalised payload, second is metadata. */
|
|
338
|
+
type NorbixWebhookHandler<TPayload = unknown> = (payload: TPayload, event: NorbixWebhookEvent) => void | Promise<void>;
|
|
339
|
+
/** Raw handler — first arg is the envelope, second is the delivery context. */
|
|
340
|
+
type NorbixWebhookRawHandler<TData = unknown> = (envelope: NorbixWebhookEnvelope<TData>, ctx: NorbixWebhookContext) => void | Promise<void>;
|
|
108
341
|
interface NorbixWebhookReceiverOptions {
|
|
109
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Signing secret. When set, verification runs on every delivery.
|
|
344
|
+
* Defaults to `process.env.NORBIX_WEBHOOK_SIGNING_SECRET`. Omit to skip verify.
|
|
345
|
+
*/
|
|
110
346
|
secret?: string;
|
|
111
|
-
/**
|
|
347
|
+
/**
|
|
348
|
+
* Reject timestamps outside this window (seconds).
|
|
349
|
+
* Defaults to `process.env.NORBIX_WEBHOOK_TOLERANCE_SECONDS`, else 300.
|
|
350
|
+
*/
|
|
112
351
|
toleranceSeconds?: number;
|
|
352
|
+
/**
|
|
353
|
+
* Optional guard — when set, deliveries whose envelope projectId does not
|
|
354
|
+
* match are rejected. Defaults to `process.env.NORBIX_PROJECT_ID`.
|
|
355
|
+
*/
|
|
356
|
+
projectId?: string;
|
|
357
|
+
/**
|
|
358
|
+
* Optional guard — when set, deliveries whose envelope accountId does not
|
|
359
|
+
* match are rejected. Defaults to `process.env.NORBIX_ACCOUNT_ID`.
|
|
360
|
+
*/
|
|
361
|
+
accountId?: string;
|
|
113
362
|
}
|
|
114
363
|
|
|
364
|
+
/** Result of normalising a wire envelope for a typed handler. */
|
|
365
|
+
interface NorbixWebhookNormalized {
|
|
366
|
+
/** The payload-first value handed to a typed handler. */
|
|
367
|
+
payload: unknown;
|
|
368
|
+
/** Identifiers lifted off the wire payload. */
|
|
369
|
+
metadata: NorbixWebhookEventMetadata;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Turn a raw wire envelope into `{ payload, metadata }`.
|
|
373
|
+
*
|
|
374
|
+
* - Entity events → `payload` is the entity (user / document / file).
|
|
375
|
+
* - Mutation events → `payload` is `{ from, to }`.
|
|
376
|
+
* - Batch events → `payload` is the array.
|
|
377
|
+
*
|
|
378
|
+
* Wrapper ids (record id, schema, user id, …) are moved onto `metadata`.
|
|
379
|
+
* Unknown events fall back to `payload = envelope.data`, `metadata = {}`.
|
|
380
|
+
*/
|
|
381
|
+
declare function normalizeNorbixWebhook(envelope: NorbixWebhookEnvelope): NorbixWebhookNormalized;
|
|
382
|
+
|
|
115
383
|
/** Read Norbix delivery headers from an incoming HTTP request. */
|
|
116
384
|
declare function parseNorbixWebhookHeaders(headers: NorbixWebhookHeaderBag): NorbixWebhookDeliveryHeaders;
|
|
117
385
|
/** Parse the JSON envelope from the raw POST body. */
|
|
@@ -135,41 +403,73 @@ declare function computeNorbixWebhookSignature(secret: string, timestamp: string
|
|
|
135
403
|
/**
|
|
136
404
|
* Register handlers for inbound Norbix webhook deliveries (trigger → destination POST).
|
|
137
405
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* to per-event handlers (similar to Stripe's webhook pattern).
|
|
406
|
+
* Verifies the HMAC signature, parses the envelope, normalises the payload, and
|
|
407
|
+
* dispatches to per-event handlers.
|
|
141
408
|
*
|
|
142
409
|
* @example
|
|
143
410
|
* ```ts
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* });
|
|
411
|
+
* import { NorbixWebhookReceiver, NorbixWebhookEvents } from '@norbix.ai/ts/webhooks';
|
|
412
|
+
* import type { CodeMashHub2 } from '@norbix.ai/ts/types/hub';
|
|
147
413
|
*
|
|
148
|
-
* receiver
|
|
149
|
-
* console.log('inserted', event.data);
|
|
150
|
-
* });
|
|
414
|
+
* const receiver = new NorbixWebhookReceiver(); // reads env
|
|
151
415
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
416
|
+
* // Typed: first arg IS the payload, second is metadata.
|
|
417
|
+
* receiver.on<CodeMashHub2.UserDto>(
|
|
418
|
+
* NorbixWebhookEvents.Membership.UserRegistered,
|
|
419
|
+
* (user, event) => {
|
|
420
|
+
* user.email ?? user.userName;
|
|
421
|
+
* event.metadata.user?.id;
|
|
422
|
+
* },
|
|
423
|
+
* );
|
|
424
|
+
*
|
|
425
|
+
* // Mutation: payload is { from, to }.
|
|
426
|
+
* receiver.on(NorbixWebhookEvents.Membership.UserUpdated, (user, event) => {
|
|
427
|
+
* if (user.from.email !== user.to.email) { ... }
|
|
154
428
|
* });
|
|
155
429
|
*
|
|
430
|
+
* // Raw escape hatch: (envelope, ctx).
|
|
431
|
+
* receiver.onRaw(NorbixWebhookEvents.Files.FileUploaded, (envelope, ctx) => {});
|
|
432
|
+
*
|
|
156
433
|
* await receiver.handle({ rawBody, headers: req.headers });
|
|
157
434
|
* ```
|
|
158
435
|
*/
|
|
159
436
|
declare class NorbixWebhookReceiver {
|
|
160
|
-
private readonly options;
|
|
161
437
|
private readonly handlers;
|
|
162
438
|
private defaultHandler?;
|
|
439
|
+
private readonly config;
|
|
163
440
|
constructor(options?: NorbixWebhookReceiverOptions);
|
|
164
|
-
/**
|
|
441
|
+
/**
|
|
442
|
+
* Membership user entity events (create / delete / state flip) — `payload`
|
|
443
|
+
* is the user directly. Pass the entity type as the generic, e.g.
|
|
444
|
+
* `on<CodeMashHub2.UserDto>(NorbixWebhookEvents.Membership.UserRegistered, ...)`.
|
|
445
|
+
*/
|
|
446
|
+
on<TUser = CodeMashHub2.UserDto>(event: 'membership.user.registered' | 'membership.user.verified' | 'membership.user.blocked' | 'membership.user.reactivated' | 'membership.user.deleted', handler: NorbixWebhookHandler<TUser>): this;
|
|
447
|
+
/** Membership user-updated mutation — `payload` is `{ from, to }`. */
|
|
448
|
+
on<TUser = CodeMashHub2.UserDto>(event: 'membership.user.updated', handler: NorbixWebhookHandler<NorbixWebhookMutation<TUser>>): this;
|
|
449
|
+
/** Database record mutation — pass the document type; `payload` is `{ from, to }`. */
|
|
450
|
+
on<TDocument>(event: 'database.record.updated' | 'database.record.replaced', handler: NorbixWebhookHandler<NorbixWebhookMutation<TDocument>>): this;
|
|
451
|
+
/** Database single-record entity — pass the document type; `payload` is the document. */
|
|
452
|
+
on<TDocument>(event: 'database.record.inserted' | 'database.record.deleted', handler: NorbixWebhookHandler<TDocument>): this;
|
|
453
|
+
/** Database batch insert — pass the document type; `payload` is the array. */
|
|
454
|
+
on<TDocument>(event: 'database.records.inserted', handler: NorbixWebhookHandler<TDocument[]>): this;
|
|
455
|
+
/** Any known catalog event — `payload` typed from the payload map. */
|
|
456
|
+
on<E extends NorbixWebhookEventName>(event: E, handler: NorbixWebhookHandler<NorbixWebhookPayload<E>>): this;
|
|
457
|
+
/** Any event name — untyped payload. */
|
|
165
458
|
on(event: string, handler: NorbixWebhookHandler): this;
|
|
166
|
-
/**
|
|
167
|
-
|
|
459
|
+
/** Register the typed handler for many events at once (skips already-registered). */
|
|
460
|
+
onEach(events: readonly string[], handler: NorbixWebhookHandler): this;
|
|
461
|
+
/** Raw handler for one event — receives the envelope and delivery context. */
|
|
462
|
+
onRaw<TData = unknown>(event: string, handler: NorbixWebhookRawHandler<TData>): this;
|
|
463
|
+
/** Register a raw handler for many events at once (skips already-registered). */
|
|
464
|
+
onEachRaw(events: readonly string[], handler: NorbixWebhookRawHandler): this;
|
|
465
|
+
/** Fallback (raw) when no event-specific handler is registered. */
|
|
466
|
+
onDefault(handler: NorbixWebhookRawHandler): this;
|
|
168
467
|
/**
|
|
169
|
-
* Verify (when secret configured), parse, and dispatch the delivery.
|
|
170
|
-
*
|
|
468
|
+
* Verify (when secret configured), parse, normalise, and dispatch the delivery.
|
|
469
|
+
* Throws NorbixWebhookSignatureError (401-worthy) on bad signature or guard
|
|
470
|
+
* mismatch; otherwise returns a 200-worthy result.
|
|
171
471
|
*/
|
|
172
472
|
handle(input: NorbixWebhookHandleInput): Promise<NorbixWebhookHandleResult>;
|
|
173
473
|
}
|
|
174
474
|
|
|
175
|
-
export { NORBIX_WEBHOOK_EVENT_GROUPS, NORBIX_WEBHOOK_EVENT_NAMES, NORBIX_WEBHOOK_HEADERS, type NorbixWebhookContext, type NorbixWebhookDeliveryHeaders, type NorbixWebhookEnvelope, NorbixWebhookError, type NorbixWebhookEventGroup, type NorbixWebhookEventName, type NorbixWebhookHandleInput, type NorbixWebhookHandleResult, type NorbixWebhookHandler, type
|
|
475
|
+
export { NORBIX_WEBHOOK_EVENT_GROUPS, NORBIX_WEBHOOK_EVENT_NAMES, NORBIX_WEBHOOK_HEADERS, type NorbixWebhookContext, type NorbixWebhookDeliveryHeaders, type NorbixWebhookEnvelope, NorbixWebhookError, type NorbixWebhookEvent, type NorbixWebhookEventGroup, type NorbixWebhookEventMetadata, type NorbixWebhookEventName, NorbixWebhookEvents, type NorbixWebhookFileDeleted, type NorbixWebhookHandleInput, type NorbixWebhookHandleResult, type NorbixWebhookHandler, type NorbixWebhookHeaderBag, type NorbixWebhookHeaderName, type NorbixWebhookMutation, type NorbixWebhookNormalized, NorbixWebhookParseError, type NorbixWebhookPayload, type NorbixWebhookPayloadMap, type NorbixWebhookRawHandler, NorbixWebhookReceiver, type NorbixWebhookReceiverOptions, type NorbixWebhookRecordUpdated, NorbixWebhookSignatureError, type NorbixWebhookSignatureVerification, type NorbixWebhookUserInvited, type NorbixWebhookUserUpdated, type NorbixWebhookVerifyOptions, type NorbixWebhookWireDatabaseRecord, type NorbixWebhookWireDatabaseRecordResponsibilityChanged, type NorbixWebhookWireDatabaseRecordUpdated, type NorbixWebhookWireDatabaseRecordsDeleted, type NorbixWebhookWireDatabaseRecordsInserted, type NorbixWebhookWireDatabaseRecordsUpdated, type NorbixWebhookWireFileDeleted, type NorbixWebhookWireFileUploaded, type NorbixWebhookWireUserDeleted, type NorbixWebhookWireUserRegistered, type NorbixWebhookWireUserTransition, computeNorbixWebhookSignature, normalizeNorbixWebhook, parseNorbixWebhookEnvelope, parseNorbixWebhookHeaders, verifyNorbixWebhookSignature };
|
package/dist/webhooks/index.js
CHANGED
|
@@ -42,6 +42,31 @@ var NORBIX_WEBHOOK_EVENT_NAMES = [
|
|
|
42
42
|
"files.file.uploaded",
|
|
43
43
|
"files.file.deleted"
|
|
44
44
|
];
|
|
45
|
+
var NorbixWebhookEvents = {
|
|
46
|
+
Database: {
|
|
47
|
+
RecordInserted: "database.record.inserted",
|
|
48
|
+
RecordUpdated: "database.record.updated",
|
|
49
|
+
RecordDeleted: "database.record.deleted",
|
|
50
|
+
RecordReplaced: "database.record.replaced",
|
|
51
|
+
RecordResponsibilityChanged: "database.record.responsibilityChanged",
|
|
52
|
+
RecordsInserted: "database.records.inserted",
|
|
53
|
+
RecordsUpdated: "database.records.updated",
|
|
54
|
+
RecordsDeleted: "database.records.deleted"
|
|
55
|
+
},
|
|
56
|
+
Membership: {
|
|
57
|
+
UserRegistered: "membership.user.registered",
|
|
58
|
+
UserInvited: "membership.user.invited",
|
|
59
|
+
UserVerified: "membership.user.verified",
|
|
60
|
+
UserUpdated: "membership.user.updated",
|
|
61
|
+
UserDeleted: "membership.user.deleted",
|
|
62
|
+
UserBlocked: "membership.user.blocked",
|
|
63
|
+
UserReactivated: "membership.user.reactivated"
|
|
64
|
+
},
|
|
65
|
+
Files: {
|
|
66
|
+
FileUploaded: "files.file.uploaded",
|
|
67
|
+
FileDeleted: "files.file.deleted"
|
|
68
|
+
}
|
|
69
|
+
};
|
|
45
70
|
var NORBIX_WEBHOOK_EVENT_GROUPS = [
|
|
46
71
|
{
|
|
47
72
|
group: "database",
|
|
@@ -89,6 +114,71 @@ var NORBIX_WEBHOOK_HEADERS = {
|
|
|
89
114
|
signature: "X-Norbix-Signature",
|
|
90
115
|
timestamp: "X-Norbix-Timestamp"
|
|
91
116
|
};
|
|
117
|
+
|
|
118
|
+
// src/webhooks/normalize.ts
|
|
119
|
+
function isObject(value) {
|
|
120
|
+
return typeof value === "object" && value !== null;
|
|
121
|
+
}
|
|
122
|
+
function normalizeNorbixWebhook(envelope) {
|
|
123
|
+
const event = envelope.event;
|
|
124
|
+
const data = envelope.data;
|
|
125
|
+
const d = isObject(data) ? data : {};
|
|
126
|
+
if (event.startsWith("database.")) {
|
|
127
|
+
const metadata = {};
|
|
128
|
+
if (typeof d.schemaName === "string") {
|
|
129
|
+
const schema = isObject(d.schema) ? d.schema : null;
|
|
130
|
+
const schemaId = schema && typeof schema.id === "string" ? schema.id : null;
|
|
131
|
+
metadata.schema = { id: schemaId, name: d.schemaName };
|
|
132
|
+
}
|
|
133
|
+
if (typeof d.integrationId === "string") metadata.integrationId = d.integrationId;
|
|
134
|
+
if (typeof d.id === "string") metadata.record = { id: d.id };
|
|
135
|
+
if (Array.isArray(d.ids)) metadata.records = { ids: d.ids };
|
|
136
|
+
switch (event) {
|
|
137
|
+
case "database.record.inserted":
|
|
138
|
+
case "database.record.deleted":
|
|
139
|
+
return { payload: d.document, metadata };
|
|
140
|
+
case "database.record.updated":
|
|
141
|
+
case "database.record.replaced":
|
|
142
|
+
return { payload: { from: d.from, to: d.to }, metadata };
|
|
143
|
+
case "database.records.inserted":
|
|
144
|
+
return { payload: d.documents ?? [], metadata };
|
|
145
|
+
default:
|
|
146
|
+
return { payload: data, metadata };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (event.startsWith("membership.")) {
|
|
150
|
+
const metadata = {};
|
|
151
|
+
if (typeof d.id === "string") metadata.user = { id: d.id };
|
|
152
|
+
switch (event) {
|
|
153
|
+
case "membership.user.registered":
|
|
154
|
+
case "membership.user.verified":
|
|
155
|
+
case "membership.user.blocked":
|
|
156
|
+
case "membership.user.reactivated":
|
|
157
|
+
return { payload: d.to, metadata };
|
|
158
|
+
case "membership.user.deleted":
|
|
159
|
+
return { payload: d.from, metadata };
|
|
160
|
+
case "membership.user.updated":
|
|
161
|
+
return { payload: { from: d.from, to: d.to }, metadata };
|
|
162
|
+
case "membership.user.invited":
|
|
163
|
+
return { payload: { email: d.email }, metadata };
|
|
164
|
+
default:
|
|
165
|
+
return { payload: data, metadata };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (event.startsWith("files.")) {
|
|
169
|
+
const metadata = {};
|
|
170
|
+
if (typeof d.integrationId === "string") metadata.integrationId = d.integrationId;
|
|
171
|
+
switch (event) {
|
|
172
|
+
case "files.file.uploaded":
|
|
173
|
+
return { payload: d.file, metadata };
|
|
174
|
+
case "files.file.deleted":
|
|
175
|
+
return { payload: { path: d.path }, metadata };
|
|
176
|
+
default:
|
|
177
|
+
return { payload: data, metadata };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { payload: data, metadata: {} };
|
|
181
|
+
}
|
|
92
182
|
function headerValue(headers, name) {
|
|
93
183
|
if (headers instanceof Headers) {
|
|
94
184
|
return headers.get(name);
|
|
@@ -183,38 +273,77 @@ function timingSafeEqualUtf8(a, b) {
|
|
|
183
273
|
}
|
|
184
274
|
|
|
185
275
|
// src/webhooks/receiver.ts
|
|
276
|
+
function readEnv() {
|
|
277
|
+
return typeof process !== "undefined" && process.env ? process.env : {};
|
|
278
|
+
}
|
|
279
|
+
function toNumber(value) {
|
|
280
|
+
if (value == null || value === "") return void 0;
|
|
281
|
+
const n = Number(value);
|
|
282
|
+
return Number.isFinite(n) ? n : void 0;
|
|
283
|
+
}
|
|
284
|
+
function resolveConfig(options) {
|
|
285
|
+
const env = readEnv();
|
|
286
|
+
const tolerance = options.toleranceSeconds ?? toNumber(env.NORBIX_WEBHOOK_TOLERANCE_SECONDS);
|
|
287
|
+
return {
|
|
288
|
+
secret: options.secret ?? env.NORBIX_WEBHOOK_SIGNING_SECRET,
|
|
289
|
+
projectId: options.projectId ?? env.NORBIX_PROJECT_ID,
|
|
290
|
+
accountId: options.accountId ?? env.NORBIX_ACCOUNT_ID,
|
|
291
|
+
toleranceSeconds: Number.isFinite(tolerance) ? tolerance : 300
|
|
292
|
+
};
|
|
293
|
+
}
|
|
186
294
|
var NorbixWebhookReceiver = class {
|
|
187
|
-
constructor(options = {}) {
|
|
188
|
-
this.options = options;
|
|
189
|
-
}
|
|
190
|
-
options;
|
|
191
295
|
handlers = /* @__PURE__ */ new Map();
|
|
192
296
|
defaultHandler;
|
|
193
|
-
|
|
297
|
+
config;
|
|
298
|
+
constructor(options = {}) {
|
|
299
|
+
this.config = resolveConfig(options);
|
|
300
|
+
}
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- overloads pass narrower handlers
|
|
194
302
|
on(event, handler) {
|
|
195
|
-
this.handlers.set(event, handler);
|
|
303
|
+
this.handlers.set(event, { raw: false, fn: handler });
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
/** Register the typed handler for many events at once (skips already-registered). */
|
|
307
|
+
onEach(events, handler) {
|
|
308
|
+
for (const event of events) {
|
|
309
|
+
if (!this.handlers.has(event)) this.handlers.set(event, { raw: false, fn: handler });
|
|
310
|
+
}
|
|
196
311
|
return this;
|
|
197
312
|
}
|
|
198
|
-
|
|
313
|
+
/* ---- Raw handlers: (envelope, ctx) ---- */
|
|
314
|
+
/** Raw handler for one event — receives the envelope and delivery context. */
|
|
315
|
+
onRaw(event, handler) {
|
|
316
|
+
this.handlers.set(event, { raw: true, fn: handler });
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
/** Register a raw handler for many events at once (skips already-registered). */
|
|
320
|
+
onEachRaw(events, handler) {
|
|
321
|
+
for (const event of events) {
|
|
322
|
+
if (!this.handlers.has(event)) this.handlers.set(event, { raw: true, fn: handler });
|
|
323
|
+
}
|
|
324
|
+
return this;
|
|
325
|
+
}
|
|
326
|
+
/** Fallback (raw) when no event-specific handler is registered. */
|
|
199
327
|
onDefault(handler) {
|
|
200
|
-
this.defaultHandler = handler;
|
|
328
|
+
this.defaultHandler = { raw: true, fn: handler };
|
|
201
329
|
return this;
|
|
202
330
|
}
|
|
203
331
|
/**
|
|
204
|
-
* Verify (when secret configured), parse, and dispatch the delivery.
|
|
205
|
-
*
|
|
332
|
+
* Verify (when secret configured), parse, normalise, and dispatch the delivery.
|
|
333
|
+
* Throws NorbixWebhookSignatureError (401-worthy) on bad signature or guard
|
|
334
|
+
* mismatch; otherwise returns a 200-worthy result.
|
|
206
335
|
*/
|
|
207
336
|
async handle(input) {
|
|
208
337
|
const deliveryHeaders = parseNorbixWebhookHeaders(input.headers);
|
|
209
|
-
const shouldVerify = input.verify !== false && !!this.
|
|
338
|
+
const shouldVerify = input.verify !== false && !!this.config.secret;
|
|
210
339
|
let verified = null;
|
|
211
|
-
if (shouldVerify && this.
|
|
340
|
+
if (shouldVerify && this.config.secret) {
|
|
212
341
|
const result = verifyNorbixWebhookSignature({
|
|
213
|
-
secret: this.
|
|
342
|
+
secret: this.config.secret,
|
|
214
343
|
rawBody: input.rawBody,
|
|
215
344
|
signature: deliveryHeaders.signature,
|
|
216
345
|
timestamp: deliveryHeaders.timestamp,
|
|
217
|
-
toleranceSeconds: this.
|
|
346
|
+
toleranceSeconds: this.config.toleranceSeconds
|
|
218
347
|
});
|
|
219
348
|
if (!result.ok) {
|
|
220
349
|
throw new NorbixWebhookSignatureError(result.reason ?? "Invalid signature");
|
|
@@ -222,6 +351,16 @@ var NorbixWebhookReceiver = class {
|
|
|
222
351
|
verified = true;
|
|
223
352
|
}
|
|
224
353
|
const envelope = parseNorbixWebhookEnvelope(input.rawBody);
|
|
354
|
+
if (this.config.projectId && envelope.projectId !== this.config.projectId) {
|
|
355
|
+
throw new NorbixWebhookSignatureError(
|
|
356
|
+
`delivery projectId ${envelope.projectId} does not match configured ${this.config.projectId}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (this.config.accountId && envelope.accountId !== this.config.accountId) {
|
|
360
|
+
throw new NorbixWebhookSignatureError(
|
|
361
|
+
`delivery accountId ${envelope.accountId} does not match configured ${this.config.accountId}`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
225
364
|
const ctx = {
|
|
226
365
|
path: input.path,
|
|
227
366
|
headers: {
|
|
@@ -233,10 +372,29 @@ var NorbixWebhookReceiver = class {
|
|
|
233
372
|
},
|
|
234
373
|
verified
|
|
235
374
|
};
|
|
236
|
-
const
|
|
375
|
+
const registration = this.handlers.get(envelope.event) ?? this.defaultHandler;
|
|
237
376
|
let handled = false;
|
|
238
|
-
if (
|
|
239
|
-
|
|
377
|
+
if (registration) {
|
|
378
|
+
if (registration.raw) {
|
|
379
|
+
await registration.fn(envelope, ctx);
|
|
380
|
+
} else {
|
|
381
|
+
const { payload, metadata } = normalizeNorbixWebhook(envelope);
|
|
382
|
+
const event = {
|
|
383
|
+
name: envelope.event,
|
|
384
|
+
deliveryId: envelope.id,
|
|
385
|
+
createdOn: envelope.createdOn,
|
|
386
|
+
triggerId: envelope.triggerId ?? null,
|
|
387
|
+
correlationId: null,
|
|
388
|
+
accountId: ctx.headers.accountId ?? envelope.accountId,
|
|
389
|
+
projectId: ctx.headers.projectId ?? envelope.projectId,
|
|
390
|
+
integrationId: ctx.headers.integrationId,
|
|
391
|
+
destinationId: ctx.headers.destinationId,
|
|
392
|
+
verified,
|
|
393
|
+
metadata,
|
|
394
|
+
raw: envelope
|
|
395
|
+
};
|
|
396
|
+
await registration.fn(payload, event);
|
|
397
|
+
}
|
|
240
398
|
handled = true;
|
|
241
399
|
}
|
|
242
400
|
return {
|
|
@@ -250,6 +408,6 @@ var NorbixWebhookReceiver = class {
|
|
|
250
408
|
}
|
|
251
409
|
};
|
|
252
410
|
|
|
253
|
-
export { NORBIX_WEBHOOK_EVENT_GROUPS, NORBIX_WEBHOOK_EVENT_NAMES, NORBIX_WEBHOOK_HEADERS, NorbixWebhookError, NorbixWebhookParseError, NorbixWebhookReceiver, NorbixWebhookSignatureError, computeNorbixWebhookSignature, parseNorbixWebhookEnvelope, parseNorbixWebhookHeaders, verifyNorbixWebhookSignature };
|
|
411
|
+
export { NORBIX_WEBHOOK_EVENT_GROUPS, NORBIX_WEBHOOK_EVENT_NAMES, NORBIX_WEBHOOK_HEADERS, NorbixWebhookError, NorbixWebhookEvents, NorbixWebhookParseError, NorbixWebhookReceiver, NorbixWebhookSignatureError, computeNorbixWebhookSignature, normalizeNorbixWebhook, parseNorbixWebhookEnvelope, parseNorbixWebhookHeaders, verifyNorbixWebhookSignature };
|
|
254
412
|
//# sourceMappingURL=index.js.map
|
|
255
413
|
//# sourceMappingURL=index.js.map
|