@hostwebhook/node 1.3.0 → 1.4.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/index.d.mts +355 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +138 -96
- package/dist/index.mjs +138 -96
- package/package.json +1 -1
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when signature verification fails.
|
|
3
|
+
*/
|
|
4
|
+
declare class VerificationError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
interface VerifyOptions {
|
|
8
|
+
/** Max allowed age of the signature timestamp in seconds. Default: 300 (5 min). Set 0 to disable. */
|
|
9
|
+
maxAgeSec?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Verify a HostWebhook signature.
|
|
13
|
+
*
|
|
14
|
+
* Matches the format produced by HostWebhook's delivery processor:
|
|
15
|
+
* `X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>`
|
|
16
|
+
*
|
|
17
|
+
* The HMAC is computed as: HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
18
|
+
*
|
|
19
|
+
* @param payload - The raw request body (string or Buffer)
|
|
20
|
+
* @param headers - The request headers object
|
|
21
|
+
* @param secret - Your signing secret (e.g. `whsec_...`)
|
|
22
|
+
* @param options - Optional configuration
|
|
23
|
+
* @throws {VerificationError} If the signature is missing, malformed, expired, or invalid
|
|
24
|
+
*/
|
|
25
|
+
declare function verify(payload: string | Buffer, headers: Record<string, string | string[] | undefined>, secret: string, options?: VerifyOptions): void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Typed representation of HostWebhook delivery headers.
|
|
29
|
+
*/
|
|
30
|
+
interface WebhookHeaders {
|
|
31
|
+
/** Unique event ID (`X-HostWebhook-Event-Id`) */
|
|
32
|
+
eventId: string;
|
|
33
|
+
/** Delivery attempt number (`X-HostWebhook-Attempt`) */
|
|
34
|
+
attempt: number;
|
|
35
|
+
/** Signature timestamp in milliseconds */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
/** Raw HMAC-SHA256 hex signature */
|
|
38
|
+
signature: string;
|
|
39
|
+
/** Chain step number, if this is a chained delivery */
|
|
40
|
+
chainStep?: number;
|
|
41
|
+
/** Original chain payload (base64-decoded JSON) */
|
|
42
|
+
chainOriginal?: unknown;
|
|
43
|
+
/** Previous chain step response (base64-decoded JSON) */
|
|
44
|
+
chainPrevious?: unknown;
|
|
45
|
+
/** Chain response metadata (base64-decoded JSON) */
|
|
46
|
+
chainResponse?: {
|
|
47
|
+
status: number;
|
|
48
|
+
body: unknown;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
};
|
|
51
|
+
/** Whether the chain response was truncated */
|
|
52
|
+
chainResponseTruncated?: boolean;
|
|
53
|
+
/** Original size of the chain response before truncation */
|
|
54
|
+
chainResponseOriginalSize?: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse all `X-HostWebhook-*` headers into a typed object.
|
|
58
|
+
*
|
|
59
|
+
* @param headers - The raw request headers
|
|
60
|
+
* @returns Parsed webhook headers
|
|
61
|
+
*/
|
|
62
|
+
declare function parseHeaders(headers: Record<string, string | string[] | undefined>): WebhookHeaders;
|
|
63
|
+
|
|
64
|
+
type NextFn = (err?: unknown) => void;
|
|
65
|
+
interface ExpressRequest {
|
|
66
|
+
body: string | Buffer;
|
|
67
|
+
headers: Record<string, string | string[] | undefined>;
|
|
68
|
+
}
|
|
69
|
+
interface ExpressResponse {
|
|
70
|
+
status(code: number): ExpressResponse;
|
|
71
|
+
json(body: unknown): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Express middleware that verifies HostWebhook signatures.
|
|
75
|
+
*
|
|
76
|
+
* IMPORTANT: You must use `express.raw({ type: 'application/json' })` before
|
|
77
|
+
* this middleware so that `req.body` is a Buffer (not parsed JSON).
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import express from 'express';
|
|
82
|
+
* import { expressMiddleware } from '@hostwebhook/node';
|
|
83
|
+
*
|
|
84
|
+
* const app = express();
|
|
85
|
+
* app.post('/webhooks',
|
|
86
|
+
* express.raw({ type: 'application/json' }),
|
|
87
|
+
* expressMiddleware(process.env.SIGNING_SECRET!),
|
|
88
|
+
* (req, res) => {
|
|
89
|
+
* const payload = JSON.parse(req.body.toString());
|
|
90
|
+
* res.status(200).json({ received: true });
|
|
91
|
+
* },
|
|
92
|
+
* );
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
declare function expressMiddleware(secret: string, options?: VerifyOptions): (req: ExpressRequest, res: ExpressResponse, next: NextFn) => void;
|
|
96
|
+
/**
|
|
97
|
+
* NestJS guard factory. Returns a class that can be used with @UseGuards().
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { Controller, Post, UseGuards, Req } from '@nestjs/common';
|
|
102
|
+
* import { createNestGuard } from '@hostwebhook/node';
|
|
103
|
+
*
|
|
104
|
+
* const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);
|
|
105
|
+
*
|
|
106
|
+
* @Controller('webhooks')
|
|
107
|
+
* export class WebhooksController {
|
|
108
|
+
* @Post()
|
|
109
|
+
* @UseGuards(WebhookGuard)
|
|
110
|
+
* handle(@Req() req: Request) {
|
|
111
|
+
* return { received: true };
|
|
112
|
+
* }
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function createNestGuard(secret: string, options?: VerifyOptions): {
|
|
117
|
+
new (): {
|
|
118
|
+
canActivate(context: {
|
|
119
|
+
switchToHttp(): {
|
|
120
|
+
getRequest(): ExpressRequest;
|
|
121
|
+
};
|
|
122
|
+
}): boolean;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Fastify preHandler hook that verifies HostWebhook signatures.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* import Fastify from 'fastify';
|
|
131
|
+
* import { fastifyHook } from '@hostwebhook/node';
|
|
132
|
+
*
|
|
133
|
+
* const app = Fastify({ bodyLimit: 1_048_576 });
|
|
134
|
+
* app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));
|
|
135
|
+
*
|
|
136
|
+
* app.post('/webhooks', { preHandler: fastifyHook(process.env.SIGNING_SECRET!) }, (req, reply) => {
|
|
137
|
+
* reply.send({ received: true });
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare function fastifyHook(secret: string, options?: VerifyOptions): (req: ExpressRequest, reply: ExpressResponse, done: NextFn) => void;
|
|
142
|
+
|
|
143
|
+
/** Static secret signature — sends the secret as-is in a header (no HMAC). */
|
|
144
|
+
interface StaticSignature {
|
|
145
|
+
type: 'static';
|
|
146
|
+
/** The secret/token value to send. */
|
|
147
|
+
secret: string;
|
|
148
|
+
/**
|
|
149
|
+
* Header name to send the secret in.
|
|
150
|
+
* Default: `Authorization` with `Bearer <secret>` format.
|
|
151
|
+
* @example 'X-My-Custom-Key'
|
|
152
|
+
*/
|
|
153
|
+
header?: string;
|
|
154
|
+
}
|
|
155
|
+
/** Configuration for the HostWebhook client. */
|
|
156
|
+
interface HostWebhookConfig {
|
|
157
|
+
/**
|
|
158
|
+
* API key (`hwk_...`) for authentication. Required for file uploads
|
|
159
|
+
* and recommended for all operations. Must have appropriate scopes.
|
|
160
|
+
*/
|
|
161
|
+
apiKey?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Default ingress token. When set, `send(payload)` uses this token.
|
|
164
|
+
* For multi-endpoint usage, pass the token per-call: `send(token, payload)`.
|
|
165
|
+
*/
|
|
166
|
+
token?: string;
|
|
167
|
+
/** Base URL of the HostWebhook API. Default: `https://api.hostwebhook.com` */
|
|
168
|
+
baseUrl?: string;
|
|
169
|
+
/**
|
|
170
|
+
* HMAC-SHA256 signing secret. When set, every request is signed with the
|
|
171
|
+
* HostWebhook signature format (`X-HostWebhook-Signature: t=<ts>,v1=<hmac>`).
|
|
172
|
+
* Pair with `incomingSignatureType: 'hostwebhook'` on the endpoint.
|
|
173
|
+
* @deprecated Prefer `apiKey` for authentication.
|
|
174
|
+
*/
|
|
175
|
+
signingSecret?: string;
|
|
176
|
+
/**
|
|
177
|
+
* Signature configuration for incoming webhook verification.
|
|
178
|
+
* Cannot be used together with `signingSecret`.
|
|
179
|
+
*
|
|
180
|
+
* @example Static with default header (Authorization: Bearer)
|
|
181
|
+
* ```ts
|
|
182
|
+
* { type: 'static', secret: 'my-secret' }
|
|
183
|
+
* ```
|
|
184
|
+
* @example Static with custom header
|
|
185
|
+
* ```ts
|
|
186
|
+
* { type: 'static', secret: 'my-secret', header: 'X-My-Custom-Key' }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
signature?: StaticSignature;
|
|
190
|
+
/** Custom headers to include in every request. */
|
|
191
|
+
defaultHeaders?: Record<string, string>;
|
|
192
|
+
/** Request timeout in milliseconds. Default: 10000 (10s). */
|
|
193
|
+
timeoutMs?: number;
|
|
194
|
+
}
|
|
195
|
+
/** A single pipeline execution step received via SSE stream. */
|
|
196
|
+
interface PipelineStep {
|
|
197
|
+
eventId?: string;
|
|
198
|
+
nodeType: string;
|
|
199
|
+
nodeId: string;
|
|
200
|
+
nodeName?: string;
|
|
201
|
+
status: 'running' | 'success' | 'error' | 'blocked';
|
|
202
|
+
statusCode?: number;
|
|
203
|
+
durationMs?: number;
|
|
204
|
+
error?: string;
|
|
205
|
+
lastPayload?: Record<string, unknown>;
|
|
206
|
+
}
|
|
207
|
+
/** Options for a single send call. */
|
|
208
|
+
interface SendOptions {
|
|
209
|
+
/** Custom headers for this specific request (merged with defaultHeaders). */
|
|
210
|
+
headers?: Record<string, string>;
|
|
211
|
+
/** Override the Content-Type. Default: `application/json`. */
|
|
212
|
+
contentType?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Send raw string/Buffer body instead of JSON-stringifying a payload object.
|
|
215
|
+
* When set, the `payload` parameter to `send()` is ignored.
|
|
216
|
+
*/
|
|
217
|
+
rawBody?: string | Buffer;
|
|
218
|
+
/**
|
|
219
|
+
* Wait for the full pipeline result via SSE streaming (blocking).
|
|
220
|
+
* The SDK will POST the payload, then open an SSE stream to receive
|
|
221
|
+
* pipeline steps and the final result. Returns when the pipeline completes.
|
|
222
|
+
* Timeout: ~125s (slightly above the server's 120s SSE timeout).
|
|
223
|
+
*/
|
|
224
|
+
waitForResult?: boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Non-blocking pipeline result callback. `send()` returns immediately with
|
|
227
|
+
* the eventId, and the callback fires in the background when the pipeline
|
|
228
|
+
* completes (via SSE stream). Perfect for UIs that shouldn't block.
|
|
229
|
+
*
|
|
230
|
+
* Requires the endpoint to be in `none` response mode (async).
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* const { eventId } = await hw.send(payload, {
|
|
235
|
+
* onResult: (result) => console.log('Pipeline done:', result.syncStatus),
|
|
236
|
+
* });
|
|
237
|
+
* // UI shows "Sent!" immediately here
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
onResult?: (result: SendResult) => void;
|
|
241
|
+
/**
|
|
242
|
+
* Called for each pipeline step during SSE streaming.
|
|
243
|
+
* Only fires when `onResult` or `waitForResult` is used.
|
|
244
|
+
*/
|
|
245
|
+
onStep?: (step: PipelineStep) => void;
|
|
246
|
+
}
|
|
247
|
+
/** Result returned after a successful send. */
|
|
248
|
+
interface SendResult {
|
|
249
|
+
/** The event ID assigned by HostWebhook. */
|
|
250
|
+
eventId: string;
|
|
251
|
+
/** Short-lived token for SSE stream auth (returned by ingress, used internally by waitForResult). */
|
|
252
|
+
streamToken?: string;
|
|
253
|
+
/** Sync/stream: pipeline result status (200=delivered, 422=validation failed, 502=delivery failed, 408=timeout) */
|
|
254
|
+
syncStatus?: number;
|
|
255
|
+
/** Sync/stream: delivery status ('delivered') */
|
|
256
|
+
deliveryStatus?: string;
|
|
257
|
+
/** Sync/stream: HTTP status code from the target URL */
|
|
258
|
+
statusCode?: number;
|
|
259
|
+
/** Sync/stream: response body from the target URL (truncated to 4KB) */
|
|
260
|
+
response?: string;
|
|
261
|
+
/** Sync/stream: delivery latency in ms */
|
|
262
|
+
latencyMs?: number;
|
|
263
|
+
/** Sync/stream: error message if pipeline/delivery failed */
|
|
264
|
+
error?: string;
|
|
265
|
+
/** Sync/stream: validation error messages (schema validator) */
|
|
266
|
+
message?: string[];
|
|
267
|
+
/** SSE stream: pipeline execution steps (only when waitForResult is used) */
|
|
268
|
+
steps?: PipelineStep[];
|
|
269
|
+
/** Any additional fields from the sync response */
|
|
270
|
+
[key: string]: unknown;
|
|
271
|
+
}
|
|
272
|
+
/** Thrown when the HostWebhook API returns a non-2xx response. */
|
|
273
|
+
declare class SendError extends Error {
|
|
274
|
+
readonly statusCode: number;
|
|
275
|
+
readonly responseBody: string;
|
|
276
|
+
constructor(message: string, statusCode: number, responseBody: string);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* HostWebhook client for sending webhooks through the platform.
|
|
281
|
+
*
|
|
282
|
+
* @example Single-endpoint
|
|
283
|
+
* ```ts
|
|
284
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
285
|
+
* await hw.send({ event: 'order.created' });
|
|
286
|
+
* ```
|
|
287
|
+
*
|
|
288
|
+
* @example Multi-endpoint
|
|
289
|
+
* ```ts
|
|
290
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...' });
|
|
291
|
+
* await hw.send('token_a', { event: 'order.created' });
|
|
292
|
+
* await hw.send('token_b', { event: 'user.updated' });
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
declare class HostWebhook {
|
|
296
|
+
#private;
|
|
297
|
+
constructor(config: HostWebhookConfig);
|
|
298
|
+
/**
|
|
299
|
+
* Send a webhook payload through HostWebhook.
|
|
300
|
+
*
|
|
301
|
+
* @overload send(payload, options?) — uses default token from constructor
|
|
302
|
+
* @overload send(token, payload, options?) — specify token per call
|
|
303
|
+
*
|
|
304
|
+
* @throws {SendError} If the API returns a non-2xx status
|
|
305
|
+
* @throws {Error} If no token is available
|
|
306
|
+
*/
|
|
307
|
+
send(tokenOrPayload: string | Record<string, unknown>, payloadOrOptions?: Record<string, unknown> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
308
|
+
/**
|
|
309
|
+
* Convenience: send multiple payloads in parallel.
|
|
310
|
+
*
|
|
311
|
+
* @param payloads - Array of payloads to send
|
|
312
|
+
* @param options - Optional per-request configuration (shared for all)
|
|
313
|
+
* @returns Array of results (in same order as payloads)
|
|
314
|
+
*/
|
|
315
|
+
sendBatch(payloads: Record<string, unknown>[], options?: SendOptions): Promise<SendResult[]>;
|
|
316
|
+
/**
|
|
317
|
+
* Send a webhook with file attachments.
|
|
318
|
+
*
|
|
319
|
+
* Requires `apiKey` with `canvas:write` scope for file operations.
|
|
320
|
+
* Files are uploaded via presigned URLs, then references are injected into the payload.
|
|
321
|
+
*
|
|
322
|
+
* @overload sendWithFiles(payload, files, options?) — uses default token
|
|
323
|
+
* @overload sendWithFiles(token, payload, files, options?) — specify token per call
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
328
|
+
* await hw.sendWithFiles(
|
|
329
|
+
* { orderId: '123' },
|
|
330
|
+
* { invoice: { data: pdfBuffer, fileName: 'inv.pdf', mimeType: 'application/pdf' } },
|
|
331
|
+
* );
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
sendWithFiles(tokenOrPayload: string | Record<string, unknown>, payloadOrFiles: Record<string, unknown> | Record<string, {
|
|
335
|
+
data: Buffer | Blob | Uint8Array;
|
|
336
|
+
fileName: string;
|
|
337
|
+
mimeType: string;
|
|
338
|
+
}>, filesOrOptions?: Record<string, {
|
|
339
|
+
data: Buffer | Blob | Uint8Array;
|
|
340
|
+
fileName: string;
|
|
341
|
+
mimeType: string;
|
|
342
|
+
}> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Standalone send function (no class instantiation needed).
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* import { send } from '@hostwebhook/node';
|
|
350
|
+
* await send({ token: 'your_token' }, { event: 'test' });
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
declare function send(config: HostWebhookConfig, payload: Record<string, unknown>, options?: SendOptions): Promise<SendResult>;
|
|
354
|
+
|
|
355
|
+
export { HostWebhook, type HostWebhookConfig, type PipelineStep, SendError, type SendOptions, type SendResult, type StaticSignature, VerificationError, type VerifyOptions, type WebhookHeaders, createNestGuard, expressMiddleware, fastifyHook, parseHeaders, send, verify };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when signature verification fails.
|
|
3
|
+
*/
|
|
4
|
+
declare class VerificationError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
interface VerifyOptions {
|
|
8
|
+
/** Max allowed age of the signature timestamp in seconds. Default: 300 (5 min). Set 0 to disable. */
|
|
9
|
+
maxAgeSec?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Verify a HostWebhook signature.
|
|
13
|
+
*
|
|
14
|
+
* Matches the format produced by HostWebhook's delivery processor:
|
|
15
|
+
* `X-HostWebhook-Signature: t=<timestamp_ms>,v1=<hmac_hex>`
|
|
16
|
+
*
|
|
17
|
+
* The HMAC is computed as: HMAC-SHA256(secret, `${timestamp}.${rawBody}`)
|
|
18
|
+
*
|
|
19
|
+
* @param payload - The raw request body (string or Buffer)
|
|
20
|
+
* @param headers - The request headers object
|
|
21
|
+
* @param secret - Your signing secret (e.g. `whsec_...`)
|
|
22
|
+
* @param options - Optional configuration
|
|
23
|
+
* @throws {VerificationError} If the signature is missing, malformed, expired, or invalid
|
|
24
|
+
*/
|
|
25
|
+
declare function verify(payload: string | Buffer, headers: Record<string, string | string[] | undefined>, secret: string, options?: VerifyOptions): void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Typed representation of HostWebhook delivery headers.
|
|
29
|
+
*/
|
|
30
|
+
interface WebhookHeaders {
|
|
31
|
+
/** Unique event ID (`X-HostWebhook-Event-Id`) */
|
|
32
|
+
eventId: string;
|
|
33
|
+
/** Delivery attempt number (`X-HostWebhook-Attempt`) */
|
|
34
|
+
attempt: number;
|
|
35
|
+
/** Signature timestamp in milliseconds */
|
|
36
|
+
timestamp: number;
|
|
37
|
+
/** Raw HMAC-SHA256 hex signature */
|
|
38
|
+
signature: string;
|
|
39
|
+
/** Chain step number, if this is a chained delivery */
|
|
40
|
+
chainStep?: number;
|
|
41
|
+
/** Original chain payload (base64-decoded JSON) */
|
|
42
|
+
chainOriginal?: unknown;
|
|
43
|
+
/** Previous chain step response (base64-decoded JSON) */
|
|
44
|
+
chainPrevious?: unknown;
|
|
45
|
+
/** Chain response metadata (base64-decoded JSON) */
|
|
46
|
+
chainResponse?: {
|
|
47
|
+
status: number;
|
|
48
|
+
body: unknown;
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
};
|
|
51
|
+
/** Whether the chain response was truncated */
|
|
52
|
+
chainResponseTruncated?: boolean;
|
|
53
|
+
/** Original size of the chain response before truncation */
|
|
54
|
+
chainResponseOriginalSize?: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse all `X-HostWebhook-*` headers into a typed object.
|
|
58
|
+
*
|
|
59
|
+
* @param headers - The raw request headers
|
|
60
|
+
* @returns Parsed webhook headers
|
|
61
|
+
*/
|
|
62
|
+
declare function parseHeaders(headers: Record<string, string | string[] | undefined>): WebhookHeaders;
|
|
63
|
+
|
|
64
|
+
type NextFn = (err?: unknown) => void;
|
|
65
|
+
interface ExpressRequest {
|
|
66
|
+
body: string | Buffer;
|
|
67
|
+
headers: Record<string, string | string[] | undefined>;
|
|
68
|
+
}
|
|
69
|
+
interface ExpressResponse {
|
|
70
|
+
status(code: number): ExpressResponse;
|
|
71
|
+
json(body: unknown): void;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Express middleware that verifies HostWebhook signatures.
|
|
75
|
+
*
|
|
76
|
+
* IMPORTANT: You must use `express.raw({ type: 'application/json' })` before
|
|
77
|
+
* this middleware so that `req.body` is a Buffer (not parsed JSON).
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import express from 'express';
|
|
82
|
+
* import { expressMiddleware } from '@hostwebhook/node';
|
|
83
|
+
*
|
|
84
|
+
* const app = express();
|
|
85
|
+
* app.post('/webhooks',
|
|
86
|
+
* express.raw({ type: 'application/json' }),
|
|
87
|
+
* expressMiddleware(process.env.SIGNING_SECRET!),
|
|
88
|
+
* (req, res) => {
|
|
89
|
+
* const payload = JSON.parse(req.body.toString());
|
|
90
|
+
* res.status(200).json({ received: true });
|
|
91
|
+
* },
|
|
92
|
+
* );
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
declare function expressMiddleware(secret: string, options?: VerifyOptions): (req: ExpressRequest, res: ExpressResponse, next: NextFn) => void;
|
|
96
|
+
/**
|
|
97
|
+
* NestJS guard factory. Returns a class that can be used with @UseGuards().
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { Controller, Post, UseGuards, Req } from '@nestjs/common';
|
|
102
|
+
* import { createNestGuard } from '@hostwebhook/node';
|
|
103
|
+
*
|
|
104
|
+
* const WebhookGuard = createNestGuard(process.env.SIGNING_SECRET!);
|
|
105
|
+
*
|
|
106
|
+
* @Controller('webhooks')
|
|
107
|
+
* export class WebhooksController {
|
|
108
|
+
* @Post()
|
|
109
|
+
* @UseGuards(WebhookGuard)
|
|
110
|
+
* handle(@Req() req: Request) {
|
|
111
|
+
* return { received: true };
|
|
112
|
+
* }
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function createNestGuard(secret: string, options?: VerifyOptions): {
|
|
117
|
+
new (): {
|
|
118
|
+
canActivate(context: {
|
|
119
|
+
switchToHttp(): {
|
|
120
|
+
getRequest(): ExpressRequest;
|
|
121
|
+
};
|
|
122
|
+
}): boolean;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Fastify preHandler hook that verifies HostWebhook signatures.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* import Fastify from 'fastify';
|
|
131
|
+
* import { fastifyHook } from '@hostwebhook/node';
|
|
132
|
+
*
|
|
133
|
+
* const app = Fastify({ bodyLimit: 1_048_576 });
|
|
134
|
+
* app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (req, body, done) => done(null, body));
|
|
135
|
+
*
|
|
136
|
+
* app.post('/webhooks', { preHandler: fastifyHook(process.env.SIGNING_SECRET!) }, (req, reply) => {
|
|
137
|
+
* reply.send({ received: true });
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare function fastifyHook(secret: string, options?: VerifyOptions): (req: ExpressRequest, reply: ExpressResponse, done: NextFn) => void;
|
|
142
|
+
|
|
143
|
+
/** Static secret signature — sends the secret as-is in a header (no HMAC). */
|
|
144
|
+
interface StaticSignature {
|
|
145
|
+
type: 'static';
|
|
146
|
+
/** The secret/token value to send. */
|
|
147
|
+
secret: string;
|
|
148
|
+
/**
|
|
149
|
+
* Header name to send the secret in.
|
|
150
|
+
* Default: `Authorization` with `Bearer <secret>` format.
|
|
151
|
+
* @example 'X-My-Custom-Key'
|
|
152
|
+
*/
|
|
153
|
+
header?: string;
|
|
154
|
+
}
|
|
155
|
+
/** Configuration for the HostWebhook client. */
|
|
156
|
+
interface HostWebhookConfig {
|
|
157
|
+
/**
|
|
158
|
+
* API key (`hwk_...`) for authentication. Required for file uploads
|
|
159
|
+
* and recommended for all operations. Must have appropriate scopes.
|
|
160
|
+
*/
|
|
161
|
+
apiKey?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Default ingress token. When set, `send(payload)` uses this token.
|
|
164
|
+
* For multi-endpoint usage, pass the token per-call: `send(token, payload)`.
|
|
165
|
+
*/
|
|
166
|
+
token?: string;
|
|
167
|
+
/** Base URL of the HostWebhook API. Default: `https://api.hostwebhook.com` */
|
|
168
|
+
baseUrl?: string;
|
|
169
|
+
/**
|
|
170
|
+
* HMAC-SHA256 signing secret. When set, every request is signed with the
|
|
171
|
+
* HostWebhook signature format (`X-HostWebhook-Signature: t=<ts>,v1=<hmac>`).
|
|
172
|
+
* Pair with `incomingSignatureType: 'hostwebhook'` on the endpoint.
|
|
173
|
+
* @deprecated Prefer `apiKey` for authentication.
|
|
174
|
+
*/
|
|
175
|
+
signingSecret?: string;
|
|
176
|
+
/**
|
|
177
|
+
* Signature configuration for incoming webhook verification.
|
|
178
|
+
* Cannot be used together with `signingSecret`.
|
|
179
|
+
*
|
|
180
|
+
* @example Static with default header (Authorization: Bearer)
|
|
181
|
+
* ```ts
|
|
182
|
+
* { type: 'static', secret: 'my-secret' }
|
|
183
|
+
* ```
|
|
184
|
+
* @example Static with custom header
|
|
185
|
+
* ```ts
|
|
186
|
+
* { type: 'static', secret: 'my-secret', header: 'X-My-Custom-Key' }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
signature?: StaticSignature;
|
|
190
|
+
/** Custom headers to include in every request. */
|
|
191
|
+
defaultHeaders?: Record<string, string>;
|
|
192
|
+
/** Request timeout in milliseconds. Default: 10000 (10s). */
|
|
193
|
+
timeoutMs?: number;
|
|
194
|
+
}
|
|
195
|
+
/** A single pipeline execution step received via SSE stream. */
|
|
196
|
+
interface PipelineStep {
|
|
197
|
+
eventId?: string;
|
|
198
|
+
nodeType: string;
|
|
199
|
+
nodeId: string;
|
|
200
|
+
nodeName?: string;
|
|
201
|
+
status: 'running' | 'success' | 'error' | 'blocked';
|
|
202
|
+
statusCode?: number;
|
|
203
|
+
durationMs?: number;
|
|
204
|
+
error?: string;
|
|
205
|
+
lastPayload?: Record<string, unknown>;
|
|
206
|
+
}
|
|
207
|
+
/** Options for a single send call. */
|
|
208
|
+
interface SendOptions {
|
|
209
|
+
/** Custom headers for this specific request (merged with defaultHeaders). */
|
|
210
|
+
headers?: Record<string, string>;
|
|
211
|
+
/** Override the Content-Type. Default: `application/json`. */
|
|
212
|
+
contentType?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Send raw string/Buffer body instead of JSON-stringifying a payload object.
|
|
215
|
+
* When set, the `payload` parameter to `send()` is ignored.
|
|
216
|
+
*/
|
|
217
|
+
rawBody?: string | Buffer;
|
|
218
|
+
/**
|
|
219
|
+
* Wait for the full pipeline result via SSE streaming (blocking).
|
|
220
|
+
* The SDK will POST the payload, then open an SSE stream to receive
|
|
221
|
+
* pipeline steps and the final result. Returns when the pipeline completes.
|
|
222
|
+
* Timeout: ~125s (slightly above the server's 120s SSE timeout).
|
|
223
|
+
*/
|
|
224
|
+
waitForResult?: boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Non-blocking pipeline result callback. `send()` returns immediately with
|
|
227
|
+
* the eventId, and the callback fires in the background when the pipeline
|
|
228
|
+
* completes (via SSE stream). Perfect for UIs that shouldn't block.
|
|
229
|
+
*
|
|
230
|
+
* Requires the endpoint to be in `none` response mode (async).
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* const { eventId } = await hw.send(payload, {
|
|
235
|
+
* onResult: (result) => console.log('Pipeline done:', result.syncStatus),
|
|
236
|
+
* });
|
|
237
|
+
* // UI shows "Sent!" immediately here
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
onResult?: (result: SendResult) => void;
|
|
241
|
+
/**
|
|
242
|
+
* Called for each pipeline step during SSE streaming.
|
|
243
|
+
* Only fires when `onResult` or `waitForResult` is used.
|
|
244
|
+
*/
|
|
245
|
+
onStep?: (step: PipelineStep) => void;
|
|
246
|
+
}
|
|
247
|
+
/** Result returned after a successful send. */
|
|
248
|
+
interface SendResult {
|
|
249
|
+
/** The event ID assigned by HostWebhook. */
|
|
250
|
+
eventId: string;
|
|
251
|
+
/** Short-lived token for SSE stream auth (returned by ingress, used internally by waitForResult). */
|
|
252
|
+
streamToken?: string;
|
|
253
|
+
/** Sync/stream: pipeline result status (200=delivered, 422=validation failed, 502=delivery failed, 408=timeout) */
|
|
254
|
+
syncStatus?: number;
|
|
255
|
+
/** Sync/stream: delivery status ('delivered') */
|
|
256
|
+
deliveryStatus?: string;
|
|
257
|
+
/** Sync/stream: HTTP status code from the target URL */
|
|
258
|
+
statusCode?: number;
|
|
259
|
+
/** Sync/stream: response body from the target URL (truncated to 4KB) */
|
|
260
|
+
response?: string;
|
|
261
|
+
/** Sync/stream: delivery latency in ms */
|
|
262
|
+
latencyMs?: number;
|
|
263
|
+
/** Sync/stream: error message if pipeline/delivery failed */
|
|
264
|
+
error?: string;
|
|
265
|
+
/** Sync/stream: validation error messages (schema validator) */
|
|
266
|
+
message?: string[];
|
|
267
|
+
/** SSE stream: pipeline execution steps (only when waitForResult is used) */
|
|
268
|
+
steps?: PipelineStep[];
|
|
269
|
+
/** Any additional fields from the sync response */
|
|
270
|
+
[key: string]: unknown;
|
|
271
|
+
}
|
|
272
|
+
/** Thrown when the HostWebhook API returns a non-2xx response. */
|
|
273
|
+
declare class SendError extends Error {
|
|
274
|
+
readonly statusCode: number;
|
|
275
|
+
readonly responseBody: string;
|
|
276
|
+
constructor(message: string, statusCode: number, responseBody: string);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* HostWebhook client for sending webhooks through the platform.
|
|
281
|
+
*
|
|
282
|
+
* @example Single-endpoint
|
|
283
|
+
* ```ts
|
|
284
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
285
|
+
* await hw.send({ event: 'order.created' });
|
|
286
|
+
* ```
|
|
287
|
+
*
|
|
288
|
+
* @example Multi-endpoint
|
|
289
|
+
* ```ts
|
|
290
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...' });
|
|
291
|
+
* await hw.send('token_a', { event: 'order.created' });
|
|
292
|
+
* await hw.send('token_b', { event: 'user.updated' });
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
declare class HostWebhook {
|
|
296
|
+
#private;
|
|
297
|
+
constructor(config: HostWebhookConfig);
|
|
298
|
+
/**
|
|
299
|
+
* Send a webhook payload through HostWebhook.
|
|
300
|
+
*
|
|
301
|
+
* @overload send(payload, options?) — uses default token from constructor
|
|
302
|
+
* @overload send(token, payload, options?) — specify token per call
|
|
303
|
+
*
|
|
304
|
+
* @throws {SendError} If the API returns a non-2xx status
|
|
305
|
+
* @throws {Error} If no token is available
|
|
306
|
+
*/
|
|
307
|
+
send(tokenOrPayload: string | Record<string, unknown>, payloadOrOptions?: Record<string, unknown> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
308
|
+
/**
|
|
309
|
+
* Convenience: send multiple payloads in parallel.
|
|
310
|
+
*
|
|
311
|
+
* @param payloads - Array of payloads to send
|
|
312
|
+
* @param options - Optional per-request configuration (shared for all)
|
|
313
|
+
* @returns Array of results (in same order as payloads)
|
|
314
|
+
*/
|
|
315
|
+
sendBatch(payloads: Record<string, unknown>[], options?: SendOptions): Promise<SendResult[]>;
|
|
316
|
+
/**
|
|
317
|
+
* Send a webhook with file attachments.
|
|
318
|
+
*
|
|
319
|
+
* Requires `apiKey` with `canvas:write` scope for file operations.
|
|
320
|
+
* Files are uploaded via presigned URLs, then references are injected into the payload.
|
|
321
|
+
*
|
|
322
|
+
* @overload sendWithFiles(payload, files, options?) — uses default token
|
|
323
|
+
* @overload sendWithFiles(token, payload, files, options?) — specify token per call
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
328
|
+
* await hw.sendWithFiles(
|
|
329
|
+
* { orderId: '123' },
|
|
330
|
+
* { invoice: { data: pdfBuffer, fileName: 'inv.pdf', mimeType: 'application/pdf' } },
|
|
331
|
+
* );
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
sendWithFiles(tokenOrPayload: string | Record<string, unknown>, payloadOrFiles: Record<string, unknown> | Record<string, {
|
|
335
|
+
data: Buffer | Blob | Uint8Array;
|
|
336
|
+
fileName: string;
|
|
337
|
+
mimeType: string;
|
|
338
|
+
}>, filesOrOptions?: Record<string, {
|
|
339
|
+
data: Buffer | Blob | Uint8Array;
|
|
340
|
+
fileName: string;
|
|
341
|
+
mimeType: string;
|
|
342
|
+
}> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Standalone send function (no class instantiation needed).
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* import { send } from '@hostwebhook/node';
|
|
350
|
+
* await send({ token: 'your_token' }, { event: 'test' });
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
declare function send(config: HostWebhookConfig, payload: Record<string, unknown>, options?: SendOptions): Promise<SendResult>;
|
|
354
|
+
|
|
355
|
+
export { HostWebhook, type HostWebhookConfig, type PipelineStep, SendError, type SendOptions, type SendResult, type StaticSignature, VerificationError, type VerifyOptions, type WebhookHeaders, createNestGuard, expressMiddleware, fastifyHook, parseHeaders, send, verify };
|
package/dist/index.js
CHANGED
|
@@ -207,24 +207,26 @@ var SendError = class extends Error {
|
|
|
207
207
|
// src/client.ts
|
|
208
208
|
var DEFAULT_BASE_URL = "https://api.hostwebhook.com";
|
|
209
209
|
var DEFAULT_TIMEOUT = 1e4;
|
|
210
|
-
var _token, _baseUrl, _signingSecret, _signature, _defaultHeaders, _timeoutMs, _HostWebhook_instances, waitForStream_fn;
|
|
210
|
+
var _apiKey, _token, _baseUrl, _signingSecret, _signature, _defaultHeaders, _timeoutMs, _HostWebhook_instances, doSend_fn, waitForStream_fn;
|
|
211
211
|
var HostWebhook = class {
|
|
212
212
|
constructor(config) {
|
|
213
213
|
__privateAdd(this, _HostWebhook_instances);
|
|
214
|
+
__privateAdd(this, _apiKey);
|
|
214
215
|
__privateAdd(this, _token);
|
|
215
216
|
__privateAdd(this, _baseUrl);
|
|
216
217
|
__privateAdd(this, _signingSecret);
|
|
217
218
|
__privateAdd(this, _signature);
|
|
218
219
|
__privateAdd(this, _defaultHeaders);
|
|
219
220
|
__privateAdd(this, _timeoutMs);
|
|
220
|
-
if (!config.token) {
|
|
221
|
-
throw new Error("HostWebhook: token is required");
|
|
221
|
+
if (!config.apiKey && !config.token) {
|
|
222
|
+
throw new Error("HostWebhook: apiKey or token is required");
|
|
222
223
|
}
|
|
223
224
|
if (config.signingSecret && config.signature) {
|
|
224
225
|
throw new Error(
|
|
225
226
|
"HostWebhook: use either signingSecret or signature, not both"
|
|
226
227
|
);
|
|
227
228
|
}
|
|
229
|
+
__privateSet(this, _apiKey, config.apiKey);
|
|
228
230
|
__privateSet(this, _token, config.token);
|
|
229
231
|
__privateSet(this, _baseUrl, (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""));
|
|
230
232
|
__privateSet(this, _signingSecret, config.signingSecret);
|
|
@@ -235,91 +237,29 @@ var HostWebhook = class {
|
|
|
235
237
|
/**
|
|
236
238
|
* Send a webhook payload through HostWebhook.
|
|
237
239
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* monitoring, transforms, and signing.
|
|
240
|
+
* @overload send(payload, options?) — uses default token from constructor
|
|
241
|
+
* @overload send(token, payload, options?) — specify token per call
|
|
241
242
|
*
|
|
242
|
-
* @param payload - The webhook payload (will be JSON.stringify'd)
|
|
243
|
-
* @param options - Optional per-request configuration
|
|
244
|
-
* @returns The event ID assigned by HostWebhook
|
|
245
243
|
* @throws {SendError} If the API returns a non-2xx status
|
|
244
|
+
* @throws {Error} If no token is available
|
|
246
245
|
*/
|
|
247
|
-
async send(
|
|
248
|
-
|
|
249
|
-
let
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
259
|
-
...__privateGet(this, _defaultHeaders),
|
|
260
|
-
...options?.headers
|
|
261
|
-
};
|
|
262
|
-
if (__privateGet(this, _signature)?.type === "static") {
|
|
263
|
-
const { secret, header } = __privateGet(this, _signature);
|
|
264
|
-
if (header) {
|
|
265
|
-
headers[header] = secret;
|
|
266
|
-
} else {
|
|
267
|
-
headers["Authorization"] = `Bearer ${secret}`;
|
|
246
|
+
async send(tokenOrPayload, payloadOrOptions, maybeOptions) {
|
|
247
|
+
let token;
|
|
248
|
+
let payload;
|
|
249
|
+
let options;
|
|
250
|
+
if (typeof tokenOrPayload === "string") {
|
|
251
|
+
token = tokenOrPayload;
|
|
252
|
+
payload = payloadOrOptions ?? {};
|
|
253
|
+
options = maybeOptions;
|
|
254
|
+
} else {
|
|
255
|
+
if (!__privateGet(this, _token)) {
|
|
256
|
+
throw new Error("HostWebhook: no default token set \u2014 pass token as first argument: send(token, payload)");
|
|
268
257
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
headers["X-HostWebhook-Signature"] = `t=${timestamp},v1=${sig}`;
|
|
258
|
+
token = __privateGet(this, _token);
|
|
259
|
+
payload = tokenOrPayload;
|
|
260
|
+
options = payloadOrOptions;
|
|
273
261
|
}
|
|
274
|
-
|
|
275
|
-
const controller = new AbortController();
|
|
276
|
-
const timer = setTimeout(() => controller.abort(), __privateGet(this, _timeoutMs));
|
|
277
|
-
let ingressResult;
|
|
278
|
-
try {
|
|
279
|
-
const res = await fetch(url, {
|
|
280
|
-
method: "POST",
|
|
281
|
-
headers,
|
|
282
|
-
body,
|
|
283
|
-
signal: controller.signal
|
|
284
|
-
});
|
|
285
|
-
const text = await res.text();
|
|
286
|
-
if (!res.ok) {
|
|
287
|
-
throw new SendError(
|
|
288
|
-
`HostWebhook ingress error (${res.status}): ${text}`,
|
|
289
|
-
res.status,
|
|
290
|
-
text
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
try {
|
|
294
|
-
ingressResult = JSON.parse(text);
|
|
295
|
-
} catch {
|
|
296
|
-
ingressResult = { eventId: text };
|
|
297
|
-
}
|
|
298
|
-
} catch (err) {
|
|
299
|
-
if (err instanceof SendError) throw err;
|
|
300
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
301
|
-
throw new SendError(
|
|
302
|
-
`HostWebhook request timed out after ${__privateGet(this, _timeoutMs)}ms`,
|
|
303
|
-
0,
|
|
304
|
-
""
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
throw err;
|
|
308
|
-
} finally {
|
|
309
|
-
clearTimeout(timer);
|
|
310
|
-
}
|
|
311
|
-
if (ingressResult.syncStatus !== void 0) {
|
|
312
|
-
if (options?.onResult) options.onResult(ingressResult);
|
|
313
|
-
return ingressResult;
|
|
314
|
-
}
|
|
315
|
-
if (options?.waitForResult && ingressResult.streamToken) {
|
|
316
|
-
return __privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep);
|
|
317
|
-
}
|
|
318
|
-
if (options?.onResult && ingressResult.streamToken) {
|
|
319
|
-
__privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep).then((result) => options.onResult(result)).catch(() => {
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
return ingressResult;
|
|
262
|
+
return __privateMethod(this, _HostWebhook_instances, doSend_fn).call(this, token, payload, options);
|
|
323
263
|
}
|
|
324
264
|
/**
|
|
325
265
|
* Convenience: send multiple payloads in parallel.
|
|
@@ -334,41 +274,60 @@ var HostWebhook = class {
|
|
|
334
274
|
/**
|
|
335
275
|
* Send a webhook with file attachments.
|
|
336
276
|
*
|
|
337
|
-
*
|
|
338
|
-
* then
|
|
277
|
+
* Requires `apiKey` with `canvas:write` scope for file operations.
|
|
278
|
+
* Files are uploaded via presigned URLs, then references are injected into the payload.
|
|
339
279
|
*
|
|
340
|
-
* @
|
|
341
|
-
* @
|
|
342
|
-
* @param options - Optional per-request configuration
|
|
280
|
+
* @overload sendWithFiles(payload, files, options?) — uses default token
|
|
281
|
+
* @overload sendWithFiles(token, payload, files, options?) — specify token per call
|
|
343
282
|
*
|
|
344
283
|
* @example
|
|
345
284
|
* ```ts
|
|
346
|
-
* const hw = new HostWebhook({ token: '
|
|
285
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
347
286
|
* await hw.sendWithFiles(
|
|
348
|
-
* { orderId: '123'
|
|
287
|
+
* { orderId: '123' },
|
|
349
288
|
* { invoice: { data: pdfBuffer, fileName: 'inv.pdf', mimeType: 'application/pdf' } },
|
|
350
289
|
* );
|
|
351
290
|
* ```
|
|
352
291
|
*/
|
|
353
|
-
async sendWithFiles(
|
|
292
|
+
async sendWithFiles(tokenOrPayload, payloadOrFiles, filesOrOptions, maybeOptions) {
|
|
293
|
+
let token;
|
|
294
|
+
let payload;
|
|
295
|
+
let files;
|
|
296
|
+
let options;
|
|
297
|
+
if (typeof tokenOrPayload === "string") {
|
|
298
|
+
token = tokenOrPayload;
|
|
299
|
+
payload = payloadOrFiles;
|
|
300
|
+
files = filesOrOptions ?? {};
|
|
301
|
+
options = maybeOptions;
|
|
302
|
+
} else {
|
|
303
|
+
if (!__privateGet(this, _token)) {
|
|
304
|
+
throw new Error("HostWebhook: no default token set \u2014 pass token as first argument");
|
|
305
|
+
}
|
|
306
|
+
token = __privateGet(this, _token);
|
|
307
|
+
payload = tokenOrPayload;
|
|
308
|
+
files = payloadOrFiles ?? {};
|
|
309
|
+
options = filesOrOptions;
|
|
310
|
+
}
|
|
311
|
+
if (!__privateGet(this, _apiKey)) {
|
|
312
|
+
throw new Error("HostWebhook: apiKey is required for sendWithFiles (needs canvas:write scope)");
|
|
313
|
+
}
|
|
354
314
|
const enrichedPayload = { ...payload };
|
|
355
315
|
for (const [fieldName, file] of Object.entries(files)) {
|
|
356
316
|
const uploadRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/upload-url`, {
|
|
357
317
|
method: "POST",
|
|
358
318
|
headers: {
|
|
359
319
|
"Content-Type": "application/json",
|
|
360
|
-
"Authorization": `Bearer ${__privateGet(this,
|
|
320
|
+
"Authorization": `Bearer ${__privateGet(this, _apiKey)}`
|
|
361
321
|
},
|
|
362
322
|
body: JSON.stringify({ fileName: file.fileName, mimeType: file.mimeType })
|
|
363
323
|
});
|
|
364
324
|
if (!uploadRes.ok) throw new SendError(`Failed to get upload URL for ${fieldName}`, uploadRes.status, await uploadRes.text());
|
|
365
325
|
const { fileId, uploadUrl, key } = await uploadRes.json();
|
|
366
|
-
const
|
|
367
|
-
const putRes = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": file.mimeType }, body: uploadBody });
|
|
326
|
+
const putRes = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": file.mimeType }, body: file.data });
|
|
368
327
|
if (!putRes.ok) throw new SendError(`Failed to upload ${fieldName} to storage`, putRes.status, await putRes.text());
|
|
369
328
|
const confirmRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/${fileId}/confirm`, {
|
|
370
329
|
method: "POST",
|
|
371
|
-
headers: { "Authorization": `Bearer ${__privateGet(this,
|
|
330
|
+
headers: { "Authorization": `Bearer ${__privateGet(this, _apiKey)}` }
|
|
372
331
|
});
|
|
373
332
|
if (!confirmRes.ok) throw new SendError(`Failed to confirm upload for ${fieldName}`, confirmRes.status, await confirmRes.text());
|
|
374
333
|
const confirmed = await confirmRes.json();
|
|
@@ -376,9 +335,10 @@ var HostWebhook = class {
|
|
|
376
335
|
_file: { id: confirmed.id, key, originalName: confirmed.originalName, mimeType: confirmed.mimeType, size: confirmed.size }
|
|
377
336
|
};
|
|
378
337
|
}
|
|
379
|
-
return this.
|
|
338
|
+
return __privateMethod(this, _HostWebhook_instances, doSend_fn).call(this, token, enrichedPayload, options);
|
|
380
339
|
}
|
|
381
340
|
};
|
|
341
|
+
_apiKey = new WeakMap();
|
|
382
342
|
_token = new WeakMap();
|
|
383
343
|
_baseUrl = new WeakMap();
|
|
384
344
|
_signingSecret = new WeakMap();
|
|
@@ -386,6 +346,88 @@ _signature = new WeakMap();
|
|
|
386
346
|
_defaultHeaders = new WeakMap();
|
|
387
347
|
_timeoutMs = new WeakMap();
|
|
388
348
|
_HostWebhook_instances = new WeakSet();
|
|
349
|
+
doSend_fn = async function(token, payload, options) {
|
|
350
|
+
const body = options?.rawBody !== void 0 ? typeof options.rawBody === "string" ? options.rawBody : options.rawBody.toString("utf8") : JSON.stringify(payload);
|
|
351
|
+
let tz = "";
|
|
352
|
+
try {
|
|
353
|
+
tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
const headers = {
|
|
357
|
+
"Content-Type": options?.contentType ?? "application/json",
|
|
358
|
+
"User-Agent": `HostWebhook-SDK/${"1.4.0"}`,
|
|
359
|
+
"X-HostWebhook-Sent-At": (/* @__PURE__ */ new Date()).toISOString(),
|
|
360
|
+
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
361
|
+
...__privateGet(this, _defaultHeaders),
|
|
362
|
+
...options?.headers
|
|
363
|
+
};
|
|
364
|
+
if (__privateGet(this, _apiKey)) {
|
|
365
|
+
headers["Authorization"] = `Bearer ${__privateGet(this, _apiKey)}`;
|
|
366
|
+
}
|
|
367
|
+
if (!__privateGet(this, _apiKey)) {
|
|
368
|
+
if (__privateGet(this, _signature)?.type === "static") {
|
|
369
|
+
const { secret, header } = __privateGet(this, _signature);
|
|
370
|
+
if (header) {
|
|
371
|
+
headers[header] = secret;
|
|
372
|
+
} else {
|
|
373
|
+
headers["Authorization"] = `Bearer ${secret}`;
|
|
374
|
+
}
|
|
375
|
+
} else if (__privateGet(this, _signingSecret)) {
|
|
376
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
377
|
+
const sig = (0, import_crypto2.createHmac)("sha256", __privateGet(this, _signingSecret)).update(`${timestamp}.${body}`).digest("hex");
|
|
378
|
+
headers["X-HostWebhook-Signature"] = `t=${timestamp},v1=${sig}`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const url = `${__privateGet(this, _baseUrl)}/api/in/${token}`;
|
|
382
|
+
const controller = new AbortController();
|
|
383
|
+
const timer = setTimeout(() => controller.abort(), __privateGet(this, _timeoutMs));
|
|
384
|
+
let ingressResult;
|
|
385
|
+
try {
|
|
386
|
+
const res = await fetch(url, {
|
|
387
|
+
method: "POST",
|
|
388
|
+
headers,
|
|
389
|
+
body,
|
|
390
|
+
signal: controller.signal
|
|
391
|
+
});
|
|
392
|
+
const text = await res.text();
|
|
393
|
+
if (!res.ok) {
|
|
394
|
+
throw new SendError(
|
|
395
|
+
`HostWebhook ingress error (${res.status}): ${text}`,
|
|
396
|
+
res.status,
|
|
397
|
+
text
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
ingressResult = JSON.parse(text);
|
|
402
|
+
} catch {
|
|
403
|
+
ingressResult = { eventId: text };
|
|
404
|
+
}
|
|
405
|
+
} catch (err) {
|
|
406
|
+
if (err instanceof SendError) throw err;
|
|
407
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
408
|
+
throw new SendError(
|
|
409
|
+
`HostWebhook request timed out after ${__privateGet(this, _timeoutMs)}ms`,
|
|
410
|
+
0,
|
|
411
|
+
""
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
throw err;
|
|
415
|
+
} finally {
|
|
416
|
+
clearTimeout(timer);
|
|
417
|
+
}
|
|
418
|
+
if (ingressResult.syncStatus !== void 0) {
|
|
419
|
+
if (options?.onResult) options.onResult(ingressResult);
|
|
420
|
+
return ingressResult;
|
|
421
|
+
}
|
|
422
|
+
if (options?.waitForResult && ingressResult.streamToken) {
|
|
423
|
+
return __privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep);
|
|
424
|
+
}
|
|
425
|
+
if (options?.onResult && ingressResult.streamToken) {
|
|
426
|
+
__privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep).then((result) => options.onResult(result)).catch(() => {
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return ingressResult;
|
|
430
|
+
};
|
|
389
431
|
waitForStream_fn = async function(ingressResult, onStep) {
|
|
390
432
|
const { eventId, streamToken } = ingressResult;
|
|
391
433
|
const streamUrl = `${__privateGet(this, _baseUrl)}/api/events/${eventId}/stream?token=${streamToken}`;
|
package/dist/index.mjs
CHANGED
|
@@ -174,24 +174,26 @@ var SendError = class extends Error {
|
|
|
174
174
|
// src/client.ts
|
|
175
175
|
var DEFAULT_BASE_URL = "https://api.hostwebhook.com";
|
|
176
176
|
var DEFAULT_TIMEOUT = 1e4;
|
|
177
|
-
var _token, _baseUrl, _signingSecret, _signature, _defaultHeaders, _timeoutMs, _HostWebhook_instances, waitForStream_fn;
|
|
177
|
+
var _apiKey, _token, _baseUrl, _signingSecret, _signature, _defaultHeaders, _timeoutMs, _HostWebhook_instances, doSend_fn, waitForStream_fn;
|
|
178
178
|
var HostWebhook = class {
|
|
179
179
|
constructor(config) {
|
|
180
180
|
__privateAdd(this, _HostWebhook_instances);
|
|
181
|
+
__privateAdd(this, _apiKey);
|
|
181
182
|
__privateAdd(this, _token);
|
|
182
183
|
__privateAdd(this, _baseUrl);
|
|
183
184
|
__privateAdd(this, _signingSecret);
|
|
184
185
|
__privateAdd(this, _signature);
|
|
185
186
|
__privateAdd(this, _defaultHeaders);
|
|
186
187
|
__privateAdd(this, _timeoutMs);
|
|
187
|
-
if (!config.token) {
|
|
188
|
-
throw new Error("HostWebhook: token is required");
|
|
188
|
+
if (!config.apiKey && !config.token) {
|
|
189
|
+
throw new Error("HostWebhook: apiKey or token is required");
|
|
189
190
|
}
|
|
190
191
|
if (config.signingSecret && config.signature) {
|
|
191
192
|
throw new Error(
|
|
192
193
|
"HostWebhook: use either signingSecret or signature, not both"
|
|
193
194
|
);
|
|
194
195
|
}
|
|
196
|
+
__privateSet(this, _apiKey, config.apiKey);
|
|
195
197
|
__privateSet(this, _token, config.token);
|
|
196
198
|
__privateSet(this, _baseUrl, (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""));
|
|
197
199
|
__privateSet(this, _signingSecret, config.signingSecret);
|
|
@@ -202,91 +204,29 @@ var HostWebhook = class {
|
|
|
202
204
|
/**
|
|
203
205
|
* Send a webhook payload through HostWebhook.
|
|
204
206
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* monitoring, transforms, and signing.
|
|
207
|
+
* @overload send(payload, options?) — uses default token from constructor
|
|
208
|
+
* @overload send(token, payload, options?) — specify token per call
|
|
208
209
|
*
|
|
209
|
-
* @param payload - The webhook payload (will be JSON.stringify'd)
|
|
210
|
-
* @param options - Optional per-request configuration
|
|
211
|
-
* @returns The event ID assigned by HostWebhook
|
|
212
210
|
* @throws {SendError} If the API returns a non-2xx status
|
|
211
|
+
* @throws {Error} If no token is available
|
|
213
212
|
*/
|
|
214
|
-
async send(
|
|
215
|
-
|
|
216
|
-
let
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
226
|
-
...__privateGet(this, _defaultHeaders),
|
|
227
|
-
...options?.headers
|
|
228
|
-
};
|
|
229
|
-
if (__privateGet(this, _signature)?.type === "static") {
|
|
230
|
-
const { secret, header } = __privateGet(this, _signature);
|
|
231
|
-
if (header) {
|
|
232
|
-
headers[header] = secret;
|
|
233
|
-
} else {
|
|
234
|
-
headers["Authorization"] = `Bearer ${secret}`;
|
|
213
|
+
async send(tokenOrPayload, payloadOrOptions, maybeOptions) {
|
|
214
|
+
let token;
|
|
215
|
+
let payload;
|
|
216
|
+
let options;
|
|
217
|
+
if (typeof tokenOrPayload === "string") {
|
|
218
|
+
token = tokenOrPayload;
|
|
219
|
+
payload = payloadOrOptions ?? {};
|
|
220
|
+
options = maybeOptions;
|
|
221
|
+
} else {
|
|
222
|
+
if (!__privateGet(this, _token)) {
|
|
223
|
+
throw new Error("HostWebhook: no default token set \u2014 pass token as first argument: send(token, payload)");
|
|
235
224
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
headers["X-HostWebhook-Signature"] = `t=${timestamp},v1=${sig}`;
|
|
225
|
+
token = __privateGet(this, _token);
|
|
226
|
+
payload = tokenOrPayload;
|
|
227
|
+
options = payloadOrOptions;
|
|
240
228
|
}
|
|
241
|
-
|
|
242
|
-
const controller = new AbortController();
|
|
243
|
-
const timer = setTimeout(() => controller.abort(), __privateGet(this, _timeoutMs));
|
|
244
|
-
let ingressResult;
|
|
245
|
-
try {
|
|
246
|
-
const res = await fetch(url, {
|
|
247
|
-
method: "POST",
|
|
248
|
-
headers,
|
|
249
|
-
body,
|
|
250
|
-
signal: controller.signal
|
|
251
|
-
});
|
|
252
|
-
const text = await res.text();
|
|
253
|
-
if (!res.ok) {
|
|
254
|
-
throw new SendError(
|
|
255
|
-
`HostWebhook ingress error (${res.status}): ${text}`,
|
|
256
|
-
res.status,
|
|
257
|
-
text
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
try {
|
|
261
|
-
ingressResult = JSON.parse(text);
|
|
262
|
-
} catch {
|
|
263
|
-
ingressResult = { eventId: text };
|
|
264
|
-
}
|
|
265
|
-
} catch (err) {
|
|
266
|
-
if (err instanceof SendError) throw err;
|
|
267
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
268
|
-
throw new SendError(
|
|
269
|
-
`HostWebhook request timed out after ${__privateGet(this, _timeoutMs)}ms`,
|
|
270
|
-
0,
|
|
271
|
-
""
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
throw err;
|
|
275
|
-
} finally {
|
|
276
|
-
clearTimeout(timer);
|
|
277
|
-
}
|
|
278
|
-
if (ingressResult.syncStatus !== void 0) {
|
|
279
|
-
if (options?.onResult) options.onResult(ingressResult);
|
|
280
|
-
return ingressResult;
|
|
281
|
-
}
|
|
282
|
-
if (options?.waitForResult && ingressResult.streamToken) {
|
|
283
|
-
return __privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep);
|
|
284
|
-
}
|
|
285
|
-
if (options?.onResult && ingressResult.streamToken) {
|
|
286
|
-
__privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep).then((result) => options.onResult(result)).catch(() => {
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
return ingressResult;
|
|
229
|
+
return __privateMethod(this, _HostWebhook_instances, doSend_fn).call(this, token, payload, options);
|
|
290
230
|
}
|
|
291
231
|
/**
|
|
292
232
|
* Convenience: send multiple payloads in parallel.
|
|
@@ -301,41 +241,60 @@ var HostWebhook = class {
|
|
|
301
241
|
/**
|
|
302
242
|
* Send a webhook with file attachments.
|
|
303
243
|
*
|
|
304
|
-
*
|
|
305
|
-
* then
|
|
244
|
+
* Requires `apiKey` with `canvas:write` scope for file operations.
|
|
245
|
+
* Files are uploaded via presigned URLs, then references are injected into the payload.
|
|
306
246
|
*
|
|
307
|
-
* @
|
|
308
|
-
* @
|
|
309
|
-
* @param options - Optional per-request configuration
|
|
247
|
+
* @overload sendWithFiles(payload, files, options?) — uses default token
|
|
248
|
+
* @overload sendWithFiles(token, payload, files, options?) — specify token per call
|
|
310
249
|
*
|
|
311
250
|
* @example
|
|
312
251
|
* ```ts
|
|
313
|
-
* const hw = new HostWebhook({ token: '
|
|
252
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
314
253
|
* await hw.sendWithFiles(
|
|
315
|
-
* { orderId: '123'
|
|
254
|
+
* { orderId: '123' },
|
|
316
255
|
* { invoice: { data: pdfBuffer, fileName: 'inv.pdf', mimeType: 'application/pdf' } },
|
|
317
256
|
* );
|
|
318
257
|
* ```
|
|
319
258
|
*/
|
|
320
|
-
async sendWithFiles(
|
|
259
|
+
async sendWithFiles(tokenOrPayload, payloadOrFiles, filesOrOptions, maybeOptions) {
|
|
260
|
+
let token;
|
|
261
|
+
let payload;
|
|
262
|
+
let files;
|
|
263
|
+
let options;
|
|
264
|
+
if (typeof tokenOrPayload === "string") {
|
|
265
|
+
token = tokenOrPayload;
|
|
266
|
+
payload = payloadOrFiles;
|
|
267
|
+
files = filesOrOptions ?? {};
|
|
268
|
+
options = maybeOptions;
|
|
269
|
+
} else {
|
|
270
|
+
if (!__privateGet(this, _token)) {
|
|
271
|
+
throw new Error("HostWebhook: no default token set \u2014 pass token as first argument");
|
|
272
|
+
}
|
|
273
|
+
token = __privateGet(this, _token);
|
|
274
|
+
payload = tokenOrPayload;
|
|
275
|
+
files = payloadOrFiles ?? {};
|
|
276
|
+
options = filesOrOptions;
|
|
277
|
+
}
|
|
278
|
+
if (!__privateGet(this, _apiKey)) {
|
|
279
|
+
throw new Error("HostWebhook: apiKey is required for sendWithFiles (needs canvas:write scope)");
|
|
280
|
+
}
|
|
321
281
|
const enrichedPayload = { ...payload };
|
|
322
282
|
for (const [fieldName, file] of Object.entries(files)) {
|
|
323
283
|
const uploadRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/upload-url`, {
|
|
324
284
|
method: "POST",
|
|
325
285
|
headers: {
|
|
326
286
|
"Content-Type": "application/json",
|
|
327
|
-
"Authorization": `Bearer ${__privateGet(this,
|
|
287
|
+
"Authorization": `Bearer ${__privateGet(this, _apiKey)}`
|
|
328
288
|
},
|
|
329
289
|
body: JSON.stringify({ fileName: file.fileName, mimeType: file.mimeType })
|
|
330
290
|
});
|
|
331
291
|
if (!uploadRes.ok) throw new SendError(`Failed to get upload URL for ${fieldName}`, uploadRes.status, await uploadRes.text());
|
|
332
292
|
const { fileId, uploadUrl, key } = await uploadRes.json();
|
|
333
|
-
const
|
|
334
|
-
const putRes = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": file.mimeType }, body: uploadBody });
|
|
293
|
+
const putRes = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": file.mimeType }, body: file.data });
|
|
335
294
|
if (!putRes.ok) throw new SendError(`Failed to upload ${fieldName} to storage`, putRes.status, await putRes.text());
|
|
336
295
|
const confirmRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/${fileId}/confirm`, {
|
|
337
296
|
method: "POST",
|
|
338
|
-
headers: { "Authorization": `Bearer ${__privateGet(this,
|
|
297
|
+
headers: { "Authorization": `Bearer ${__privateGet(this, _apiKey)}` }
|
|
339
298
|
});
|
|
340
299
|
if (!confirmRes.ok) throw new SendError(`Failed to confirm upload for ${fieldName}`, confirmRes.status, await confirmRes.text());
|
|
341
300
|
const confirmed = await confirmRes.json();
|
|
@@ -343,9 +302,10 @@ var HostWebhook = class {
|
|
|
343
302
|
_file: { id: confirmed.id, key, originalName: confirmed.originalName, mimeType: confirmed.mimeType, size: confirmed.size }
|
|
344
303
|
};
|
|
345
304
|
}
|
|
346
|
-
return this.
|
|
305
|
+
return __privateMethod(this, _HostWebhook_instances, doSend_fn).call(this, token, enrichedPayload, options);
|
|
347
306
|
}
|
|
348
307
|
};
|
|
308
|
+
_apiKey = new WeakMap();
|
|
349
309
|
_token = new WeakMap();
|
|
350
310
|
_baseUrl = new WeakMap();
|
|
351
311
|
_signingSecret = new WeakMap();
|
|
@@ -353,6 +313,88 @@ _signature = new WeakMap();
|
|
|
353
313
|
_defaultHeaders = new WeakMap();
|
|
354
314
|
_timeoutMs = new WeakMap();
|
|
355
315
|
_HostWebhook_instances = new WeakSet();
|
|
316
|
+
doSend_fn = async function(token, payload, options) {
|
|
317
|
+
const body = options?.rawBody !== void 0 ? typeof options.rawBody === "string" ? options.rawBody : options.rawBody.toString("utf8") : JSON.stringify(payload);
|
|
318
|
+
let tz = "";
|
|
319
|
+
try {
|
|
320
|
+
tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
const headers = {
|
|
324
|
+
"Content-Type": options?.contentType ?? "application/json",
|
|
325
|
+
"User-Agent": `HostWebhook-SDK/${"1.4.0"}`,
|
|
326
|
+
"X-HostWebhook-Sent-At": (/* @__PURE__ */ new Date()).toISOString(),
|
|
327
|
+
...tz ? { "X-HostWebhook-Timezone": tz } : {},
|
|
328
|
+
...__privateGet(this, _defaultHeaders),
|
|
329
|
+
...options?.headers
|
|
330
|
+
};
|
|
331
|
+
if (__privateGet(this, _apiKey)) {
|
|
332
|
+
headers["Authorization"] = `Bearer ${__privateGet(this, _apiKey)}`;
|
|
333
|
+
}
|
|
334
|
+
if (!__privateGet(this, _apiKey)) {
|
|
335
|
+
if (__privateGet(this, _signature)?.type === "static") {
|
|
336
|
+
const { secret, header } = __privateGet(this, _signature);
|
|
337
|
+
if (header) {
|
|
338
|
+
headers[header] = secret;
|
|
339
|
+
} else {
|
|
340
|
+
headers["Authorization"] = `Bearer ${secret}`;
|
|
341
|
+
}
|
|
342
|
+
} else if (__privateGet(this, _signingSecret)) {
|
|
343
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
344
|
+
const sig = createHmac2("sha256", __privateGet(this, _signingSecret)).update(`${timestamp}.${body}`).digest("hex");
|
|
345
|
+
headers["X-HostWebhook-Signature"] = `t=${timestamp},v1=${sig}`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const url = `${__privateGet(this, _baseUrl)}/api/in/${token}`;
|
|
349
|
+
const controller = new AbortController();
|
|
350
|
+
const timer = setTimeout(() => controller.abort(), __privateGet(this, _timeoutMs));
|
|
351
|
+
let ingressResult;
|
|
352
|
+
try {
|
|
353
|
+
const res = await fetch(url, {
|
|
354
|
+
method: "POST",
|
|
355
|
+
headers,
|
|
356
|
+
body,
|
|
357
|
+
signal: controller.signal
|
|
358
|
+
});
|
|
359
|
+
const text = await res.text();
|
|
360
|
+
if (!res.ok) {
|
|
361
|
+
throw new SendError(
|
|
362
|
+
`HostWebhook ingress error (${res.status}): ${text}`,
|
|
363
|
+
res.status,
|
|
364
|
+
text
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
ingressResult = JSON.parse(text);
|
|
369
|
+
} catch {
|
|
370
|
+
ingressResult = { eventId: text };
|
|
371
|
+
}
|
|
372
|
+
} catch (err) {
|
|
373
|
+
if (err instanceof SendError) throw err;
|
|
374
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
375
|
+
throw new SendError(
|
|
376
|
+
`HostWebhook request timed out after ${__privateGet(this, _timeoutMs)}ms`,
|
|
377
|
+
0,
|
|
378
|
+
""
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
throw err;
|
|
382
|
+
} finally {
|
|
383
|
+
clearTimeout(timer);
|
|
384
|
+
}
|
|
385
|
+
if (ingressResult.syncStatus !== void 0) {
|
|
386
|
+
if (options?.onResult) options.onResult(ingressResult);
|
|
387
|
+
return ingressResult;
|
|
388
|
+
}
|
|
389
|
+
if (options?.waitForResult && ingressResult.streamToken) {
|
|
390
|
+
return __privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep);
|
|
391
|
+
}
|
|
392
|
+
if (options?.onResult && ingressResult.streamToken) {
|
|
393
|
+
__privateMethod(this, _HostWebhook_instances, waitForStream_fn).call(this, ingressResult, options?.onStep).then((result) => options.onResult(result)).catch(() => {
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return ingressResult;
|
|
397
|
+
};
|
|
356
398
|
waitForStream_fn = async function(ingressResult, onStep) {
|
|
357
399
|
const { eventId, streamToken } = ingressResult;
|
|
358
400
|
const streamUrl = `${__privateGet(this, _baseUrl)}/api/events/${eventId}/stream?token=${streamToken}`;
|