@hostwebhook/node 1.2.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/README.md +61 -5
- package/dist/index.d.mts +52 -13
- package/dist/index.d.ts +52 -13
- package/dist/index.js +172 -83
- package/dist/index.mjs +172 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,19 +92,20 @@ Options: `verify(payload, headers, secret, { maxAgeSec: 300 })` — default 300s
|
|
|
92
92
|
|
|
93
93
|
Send webhook payloads through HostWebhook for reliable delivery with retries and monitoring.
|
|
94
94
|
|
|
95
|
-
###
|
|
95
|
+
### Fire & forget (default)
|
|
96
96
|
|
|
97
97
|
```ts
|
|
98
98
|
import { HostWebhook } from '@hostwebhook/node';
|
|
99
99
|
|
|
100
100
|
const hw = new HostWebhook({ token: 'your_ingress_token' });
|
|
101
101
|
|
|
102
|
+
// Returns immediately with the event ID — you don't know what happened in the pipeline
|
|
102
103
|
const { eventId } = await hw.send({ event: 'order.created', orderId: '123' });
|
|
103
104
|
```
|
|
104
105
|
|
|
105
|
-
### Wait for pipeline result (
|
|
106
|
+
### Wait for pipeline result (blocking)
|
|
106
107
|
|
|
107
|
-
Use `waitForResult` to wait for the full pipeline to complete. The SDK opens an SSE stream behind the scenes and returns the result with all pipeline steps.
|
|
108
|
+
Use `waitForResult` to wait for the full pipeline to complete. The SDK opens an SSE stream behind the scenes and returns the result with all pipeline steps. **This blocks until the pipeline finishes** (~120s max).
|
|
108
109
|
|
|
109
110
|
```ts
|
|
110
111
|
const result = await hw.send(
|
|
@@ -117,7 +118,34 @@ console.log(result.steps); // array of pipeline steps with nodeType, statu
|
|
|
117
118
|
console.log(result.response); // response body from target URL
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
### Non-blocking result (recommended for UIs)
|
|
122
|
+
|
|
123
|
+
Use `onResult` to get the pipeline result in a background callback. `send()` returns immediately so your UI never blocks. Perfect for forms and user-facing apps.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const { eventId } = await hw.send(
|
|
127
|
+
{ event: 'order.created', orderId: '123' },
|
|
128
|
+
{
|
|
129
|
+
onResult: (result) => {
|
|
130
|
+
// Fires when the pipeline completes (via SSE in background)
|
|
131
|
+
if (result.awaitingApproval) {
|
|
132
|
+
showToast('Awaiting approval from ' + result.approvalNodeName);
|
|
133
|
+
} else if (result.syncStatus === 200) {
|
|
134
|
+
showToast('Delivered successfully');
|
|
135
|
+
} else {
|
|
136
|
+
showToast('Pipeline failed: ' + result.error);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
onStep: (step) => {
|
|
140
|
+
// Optional: fires for each pipeline node as it executes
|
|
141
|
+
updateProgress(step.nodeName, step.status);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
// UI shows "Sent!" immediately here — never blocks
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **Important:** `onResult` requires the endpoint to be in **None** response mode (async). In Sync mode, the server holds the HTTP connection open until the pipeline completes, which blocks `send()` regardless of callbacks. See [Choosing the right mode](#choosing-the-right-mode) below.
|
|
121
149
|
|
|
122
150
|
### Deduplication detection
|
|
123
151
|
|
|
@@ -151,6 +179,32 @@ const { eventId } = await send(
|
|
|
151
179
|
);
|
|
152
180
|
```
|
|
153
181
|
|
|
182
|
+
## Choosing the right mode
|
|
183
|
+
|
|
184
|
+
Your endpoint has a **Response Mode** setting (None or Sync) that affects how `send()` behaves:
|
|
185
|
+
|
|
186
|
+
| Scenario | Endpoint mode | SDK option | Behavior |
|
|
187
|
+
|----------|--------------|------------|----------|
|
|
188
|
+
| **Forms / UIs** | None | `onResult` | `send()` returns instantly. Pipeline result arrives in background callback. **UI never blocks.** |
|
|
189
|
+
| **Backend scripts** | None | `waitForResult` | `send()` blocks until pipeline completes via SSE (~120s max). |
|
|
190
|
+
| **API proxy / Stripe** | Sync | *(none)* | `send()` blocks until pipeline completes via HTTP (~120s max). Result is inline in the response. |
|
|
191
|
+
| **Testing (Postman)** | Sync | *(none)* | Single request, response includes full pipeline result. |
|
|
192
|
+
|
|
193
|
+
> **Warning: Never use Sync mode for user-facing forms or UIs.** Sync mode holds the HTTP connection open on the server, which blocks `send()` and freezes the UI until the pipeline completes. If the pipeline has approval nodes, merge nodes, or long delays, the SDK will timeout. Use **None** mode with `onResult` instead — the user sees "Sent!" immediately and the pipeline result arrives in the background.
|
|
194
|
+
|
|
195
|
+
### When to use Sync mode
|
|
196
|
+
|
|
197
|
+
- The caller is a **server or script**, not a human looking at a UI
|
|
198
|
+
- The caller **needs the pipeline result in the same HTTP request** (e.g., Stripe expects a specific response body)
|
|
199
|
+
- The pipeline is **fast and linear** (filter -> transform -> action) with no human-gated nodes (approval, merge)
|
|
200
|
+
|
|
201
|
+
### When to use None mode + onResult
|
|
202
|
+
|
|
203
|
+
- The caller is a **form, UI, or frontend app**
|
|
204
|
+
- You want **instant feedback** ("Sent!") without waiting for the pipeline
|
|
205
|
+
- The pipeline has **approval nodes, merge nodes, or delays**
|
|
206
|
+
- You want **real-time progress** via `onStep` callbacks
|
|
207
|
+
|
|
154
208
|
## Configuration
|
|
155
209
|
|
|
156
210
|
| Option | Type | Default | Description |
|
|
@@ -169,7 +223,9 @@ const { eventId } = await send(
|
|
|
169
223
|
| `headers` | `Record<string, string>` | — | Per-request headers |
|
|
170
224
|
| `contentType` | `string` | `application/json` | Override content type |
|
|
171
225
|
| `rawBody` | `string \| Buffer` | — | Send raw body instead of JSON |
|
|
172
|
-
| `waitForResult` | `boolean` | `false` | Wait for pipeline result via SSE stream |
|
|
226
|
+
| `waitForResult` | `boolean` | `false` | Wait for pipeline result via SSE stream (blocking) |
|
|
227
|
+
| `onResult` | `(result) => void` | — | Non-blocking pipeline result callback via SSE |
|
|
228
|
+
| `onStep` | `(step) => void` | — | Real-time pipeline step callback (with `onResult` or `waitForResult`) |
|
|
173
229
|
|
|
174
230
|
## Error Handling
|
|
175
231
|
|
package/dist/index.d.mts
CHANGED
|
@@ -154,15 +154,23 @@ interface StaticSignature {
|
|
|
154
154
|
}
|
|
155
155
|
/** Configuration for the HostWebhook client. */
|
|
156
156
|
interface HostWebhookConfig {
|
|
157
|
-
/**
|
|
158
|
-
|
|
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;
|
|
159
167
|
/** Base URL of the HostWebhook API. Default: `https://api.hostwebhook.com` */
|
|
160
168
|
baseUrl?: string;
|
|
161
169
|
/**
|
|
162
170
|
* HMAC-SHA256 signing secret. When set, every request is signed with the
|
|
163
171
|
* HostWebhook signature format (`X-HostWebhook-Signature: t=<ts>,v1=<hmac>`).
|
|
164
172
|
* Pair with `incomingSignatureType: 'hostwebhook'` on the endpoint.
|
|
165
|
-
* @deprecated Prefer `
|
|
173
|
+
* @deprecated Prefer `apiKey` for authentication.
|
|
166
174
|
*/
|
|
167
175
|
signingSecret?: string;
|
|
168
176
|
/**
|
|
@@ -271,10 +279,17 @@ declare class SendError extends Error {
|
|
|
271
279
|
/**
|
|
272
280
|
* HostWebhook client for sending webhooks through the platform.
|
|
273
281
|
*
|
|
274
|
-
* @example
|
|
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
|
|
275
289
|
* ```ts
|
|
276
|
-
* const hw = new HostWebhook({
|
|
277
|
-
*
|
|
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' });
|
|
278
293
|
* ```
|
|
279
294
|
*/
|
|
280
295
|
declare class HostWebhook {
|
|
@@ -283,16 +298,13 @@ declare class HostWebhook {
|
|
|
283
298
|
/**
|
|
284
299
|
* Send a webhook payload through HostWebhook.
|
|
285
300
|
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* monitoring, transforms, and signing.
|
|
301
|
+
* @overload send(payload, options?) — uses default token from constructor
|
|
302
|
+
* @overload send(token, payload, options?) — specify token per call
|
|
289
303
|
*
|
|
290
|
-
* @param payload - The webhook payload (will be JSON.stringify'd)
|
|
291
|
-
* @param options - Optional per-request configuration
|
|
292
|
-
* @returns The event ID assigned by HostWebhook
|
|
293
304
|
* @throws {SendError} If the API returns a non-2xx status
|
|
305
|
+
* @throws {Error} If no token is available
|
|
294
306
|
*/
|
|
295
|
-
send(
|
|
307
|
+
send(tokenOrPayload: string | Record<string, unknown>, payloadOrOptions?: Record<string, unknown> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
296
308
|
/**
|
|
297
309
|
* Convenience: send multiple payloads in parallel.
|
|
298
310
|
*
|
|
@@ -301,6 +313,33 @@ declare class HostWebhook {
|
|
|
301
313
|
* @returns Array of results (in same order as payloads)
|
|
302
314
|
*/
|
|
303
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>;
|
|
304
343
|
}
|
|
305
344
|
/**
|
|
306
345
|
* Standalone send function (no class instantiation needed).
|
package/dist/index.d.ts
CHANGED
|
@@ -154,15 +154,23 @@ interface StaticSignature {
|
|
|
154
154
|
}
|
|
155
155
|
/** Configuration for the HostWebhook client. */
|
|
156
156
|
interface HostWebhookConfig {
|
|
157
|
-
/**
|
|
158
|
-
|
|
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;
|
|
159
167
|
/** Base URL of the HostWebhook API. Default: `https://api.hostwebhook.com` */
|
|
160
168
|
baseUrl?: string;
|
|
161
169
|
/**
|
|
162
170
|
* HMAC-SHA256 signing secret. When set, every request is signed with the
|
|
163
171
|
* HostWebhook signature format (`X-HostWebhook-Signature: t=<ts>,v1=<hmac>`).
|
|
164
172
|
* Pair with `incomingSignatureType: 'hostwebhook'` on the endpoint.
|
|
165
|
-
* @deprecated Prefer `
|
|
173
|
+
* @deprecated Prefer `apiKey` for authentication.
|
|
166
174
|
*/
|
|
167
175
|
signingSecret?: string;
|
|
168
176
|
/**
|
|
@@ -271,10 +279,17 @@ declare class SendError extends Error {
|
|
|
271
279
|
/**
|
|
272
280
|
* HostWebhook client for sending webhooks through the platform.
|
|
273
281
|
*
|
|
274
|
-
* @example
|
|
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
|
|
275
289
|
* ```ts
|
|
276
|
-
* const hw = new HostWebhook({
|
|
277
|
-
*
|
|
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' });
|
|
278
293
|
* ```
|
|
279
294
|
*/
|
|
280
295
|
declare class HostWebhook {
|
|
@@ -283,16 +298,13 @@ declare class HostWebhook {
|
|
|
283
298
|
/**
|
|
284
299
|
* Send a webhook payload through HostWebhook.
|
|
285
300
|
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* monitoring, transforms, and signing.
|
|
301
|
+
* @overload send(payload, options?) — uses default token from constructor
|
|
302
|
+
* @overload send(token, payload, options?) — specify token per call
|
|
289
303
|
*
|
|
290
|
-
* @param payload - The webhook payload (will be JSON.stringify'd)
|
|
291
|
-
* @param options - Optional per-request configuration
|
|
292
|
-
* @returns The event ID assigned by HostWebhook
|
|
293
304
|
* @throws {SendError} If the API returns a non-2xx status
|
|
305
|
+
* @throws {Error} If no token is available
|
|
294
306
|
*/
|
|
295
|
-
send(
|
|
307
|
+
send(tokenOrPayload: string | Record<string, unknown>, payloadOrOptions?: Record<string, unknown> | SendOptions, maybeOptions?: SendOptions): Promise<SendResult>;
|
|
296
308
|
/**
|
|
297
309
|
* Convenience: send multiple payloads in parallel.
|
|
298
310
|
*
|
|
@@ -301,6 +313,33 @@ declare class HostWebhook {
|
|
|
301
313
|
* @returns Array of results (in same order as payloads)
|
|
302
314
|
*/
|
|
303
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>;
|
|
304
343
|
}
|
|
305
344
|
/**
|
|
306
345
|
* Standalone send function (no class instantiation needed).
|
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.
|
|
@@ -331,7 +271,74 @@ var HostWebhook = class {
|
|
|
331
271
|
async sendBatch(payloads, options) {
|
|
332
272
|
return Promise.all(payloads.map((p) => this.send(p, options)));
|
|
333
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Send a webhook with file attachments.
|
|
276
|
+
*
|
|
277
|
+
* Requires `apiKey` with `canvas:write` scope for file operations.
|
|
278
|
+
* Files are uploaded via presigned URLs, then references are injected into the payload.
|
|
279
|
+
*
|
|
280
|
+
* @overload sendWithFiles(payload, files, options?) — uses default token
|
|
281
|
+
* @overload sendWithFiles(token, payload, files, options?) — specify token per call
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
286
|
+
* await hw.sendWithFiles(
|
|
287
|
+
* { orderId: '123' },
|
|
288
|
+
* { invoice: { data: pdfBuffer, fileName: 'inv.pdf', mimeType: 'application/pdf' } },
|
|
289
|
+
* );
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
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
|
+
}
|
|
314
|
+
const enrichedPayload = { ...payload };
|
|
315
|
+
for (const [fieldName, file] of Object.entries(files)) {
|
|
316
|
+
const uploadRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/upload-url`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
"Content-Type": "application/json",
|
|
320
|
+
"Authorization": `Bearer ${__privateGet(this, _apiKey)}`
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify({ fileName: file.fileName, mimeType: file.mimeType })
|
|
323
|
+
});
|
|
324
|
+
if (!uploadRes.ok) throw new SendError(`Failed to get upload URL for ${fieldName}`, uploadRes.status, await uploadRes.text());
|
|
325
|
+
const { fileId, uploadUrl, key } = await uploadRes.json();
|
|
326
|
+
const putRes = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": file.mimeType }, body: file.data });
|
|
327
|
+
if (!putRes.ok) throw new SendError(`Failed to upload ${fieldName} to storage`, putRes.status, await putRes.text());
|
|
328
|
+
const confirmRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/${fileId}/confirm`, {
|
|
329
|
+
method: "POST",
|
|
330
|
+
headers: { "Authorization": `Bearer ${__privateGet(this, _apiKey)}` }
|
|
331
|
+
});
|
|
332
|
+
if (!confirmRes.ok) throw new SendError(`Failed to confirm upload for ${fieldName}`, confirmRes.status, await confirmRes.text());
|
|
333
|
+
const confirmed = await confirmRes.json();
|
|
334
|
+
enrichedPayload[fieldName] = {
|
|
335
|
+
_file: { id: confirmed.id, key, originalName: confirmed.originalName, mimeType: confirmed.mimeType, size: confirmed.size }
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
return __privateMethod(this, _HostWebhook_instances, doSend_fn).call(this, token, enrichedPayload, options);
|
|
339
|
+
}
|
|
334
340
|
};
|
|
341
|
+
_apiKey = new WeakMap();
|
|
335
342
|
_token = new WeakMap();
|
|
336
343
|
_baseUrl = new WeakMap();
|
|
337
344
|
_signingSecret = new WeakMap();
|
|
@@ -339,6 +346,88 @@ _signature = new WeakMap();
|
|
|
339
346
|
_defaultHeaders = new WeakMap();
|
|
340
347
|
_timeoutMs = new WeakMap();
|
|
341
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
|
+
};
|
|
342
431
|
waitForStream_fn = async function(ingressResult, onStep) {
|
|
343
432
|
const { eventId, streamToken } = ingressResult;
|
|
344
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.
|
|
@@ -298,7 +238,74 @@ var HostWebhook = class {
|
|
|
298
238
|
async sendBatch(payloads, options) {
|
|
299
239
|
return Promise.all(payloads.map((p) => this.send(p, options)));
|
|
300
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Send a webhook with file attachments.
|
|
243
|
+
*
|
|
244
|
+
* Requires `apiKey` with `canvas:write` scope for file operations.
|
|
245
|
+
* Files are uploaded via presigned URLs, then references are injected into the payload.
|
|
246
|
+
*
|
|
247
|
+
* @overload sendWithFiles(payload, files, options?) — uses default token
|
|
248
|
+
* @overload sendWithFiles(token, payload, files, options?) — specify token per call
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* const hw = new HostWebhook({ apiKey: 'hwk_...', token: 'ingress_token' });
|
|
253
|
+
* await hw.sendWithFiles(
|
|
254
|
+
* { orderId: '123' },
|
|
255
|
+
* { invoice: { data: pdfBuffer, fileName: 'inv.pdf', mimeType: 'application/pdf' } },
|
|
256
|
+
* );
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
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
|
+
}
|
|
281
|
+
const enrichedPayload = { ...payload };
|
|
282
|
+
for (const [fieldName, file] of Object.entries(files)) {
|
|
283
|
+
const uploadRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/upload-url`, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: {
|
|
286
|
+
"Content-Type": "application/json",
|
|
287
|
+
"Authorization": `Bearer ${__privateGet(this, _apiKey)}`
|
|
288
|
+
},
|
|
289
|
+
body: JSON.stringify({ fileName: file.fileName, mimeType: file.mimeType })
|
|
290
|
+
});
|
|
291
|
+
if (!uploadRes.ok) throw new SendError(`Failed to get upload URL for ${fieldName}`, uploadRes.status, await uploadRes.text());
|
|
292
|
+
const { fileId, uploadUrl, key } = await uploadRes.json();
|
|
293
|
+
const putRes = await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": file.mimeType }, body: file.data });
|
|
294
|
+
if (!putRes.ok) throw new SendError(`Failed to upload ${fieldName} to storage`, putRes.status, await putRes.text());
|
|
295
|
+
const confirmRes = await fetch(`${__privateGet(this, _baseUrl)}/api/files/${fileId}/confirm`, {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: { "Authorization": `Bearer ${__privateGet(this, _apiKey)}` }
|
|
298
|
+
});
|
|
299
|
+
if (!confirmRes.ok) throw new SendError(`Failed to confirm upload for ${fieldName}`, confirmRes.status, await confirmRes.text());
|
|
300
|
+
const confirmed = await confirmRes.json();
|
|
301
|
+
enrichedPayload[fieldName] = {
|
|
302
|
+
_file: { id: confirmed.id, key, originalName: confirmed.originalName, mimeType: confirmed.mimeType, size: confirmed.size }
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return __privateMethod(this, _HostWebhook_instances, doSend_fn).call(this, token, enrichedPayload, options);
|
|
306
|
+
}
|
|
301
307
|
};
|
|
308
|
+
_apiKey = new WeakMap();
|
|
302
309
|
_token = new WeakMap();
|
|
303
310
|
_baseUrl = new WeakMap();
|
|
304
311
|
_signingSecret = new WeakMap();
|
|
@@ -306,6 +313,88 @@ _signature = new WeakMap();
|
|
|
306
313
|
_defaultHeaders = new WeakMap();
|
|
307
314
|
_timeoutMs = new WeakMap();
|
|
308
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
|
+
};
|
|
309
398
|
waitForStream_fn = async function(ingressResult, onStep) {
|
|
310
399
|
const { eventId, streamToken } = ingressResult;
|
|
311
400
|
const streamUrl = `${__privateGet(this, _baseUrl)}/api/events/${eventId}/stream?token=${streamToken}`;
|