@t4h.framework/webhook 0.0.0-experimental-20260908180423

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.
Files changed (52) hide show
  1. package/.ai/skills/framework-webhook/SKILL.md +118 -0
  2. package/CHANGELOG.md +49 -0
  3. package/README.md +42 -0
  4. package/dist/activities/WebhookActivity.d.ts +37 -0
  5. package/dist/activities/WebhookActivity.d.ts.map +1 -0
  6. package/dist/activities/WebhookActivity.js +112 -0
  7. package/dist/activities/WebhookActivity.js.map +1 -0
  8. package/dist/errors/WebhookCallbackUrlError.d.ts +6 -0
  9. package/dist/errors/WebhookCallbackUrlError.d.ts.map +1 -0
  10. package/dist/errors/WebhookCallbackUrlError.js +9 -0
  11. package/dist/errors/WebhookCallbackUrlError.js.map +1 -0
  12. package/dist/errors/WebhookRejectedError.d.ts +17 -0
  13. package/dist/errors/WebhookRejectedError.d.ts.map +1 -0
  14. package/dist/errors/WebhookRejectedError.js +26 -0
  15. package/dist/errors/WebhookRejectedError.js.map +1 -0
  16. package/dist/models/WebhookCallback.d.ts +28 -0
  17. package/dist/models/WebhookCallback.d.ts.map +1 -0
  18. package/dist/models/WebhookCallback.js +2 -0
  19. package/dist/models/WebhookCallback.js.map +1 -0
  20. package/dist/models/WebhookRequest.d.ts +25 -0
  21. package/dist/models/WebhookRequest.d.ts.map +1 -0
  22. package/dist/models/WebhookRequest.js +2 -0
  23. package/dist/models/WebhookRequest.js.map +1 -0
  24. package/dist/tools/build-webhook-dispatch.d.ts +14 -0
  25. package/dist/tools/build-webhook-dispatch.d.ts.map +1 -0
  26. package/dist/tools/build-webhook-dispatch.js +44 -0
  27. package/dist/tools/build-webhook-dispatch.js.map +1 -0
  28. package/dist/tools/callback-token.d.ts +3 -0
  29. package/dist/tools/callback-token.d.ts.map +1 -0
  30. package/dist/tools/callback-token.js +14 -0
  31. package/dist/tools/callback-token.js.map +1 -0
  32. package/dist/tools/get-header.d.ts +4 -0
  33. package/dist/tools/get-header.d.ts.map +1 -0
  34. package/dist/tools/get-header.js +21 -0
  35. package/dist/tools/get-header.js.map +1 -0
  36. package/dist/tools/parse-callback-body.d.ts +3 -0
  37. package/dist/tools/parse-callback-body.d.ts.map +1 -0
  38. package/dist/tools/parse-callback-body.js +25 -0
  39. package/dist/tools/parse-callback-body.js.map +1 -0
  40. package/dist/tools/timing-safe-equal-string.d.ts +2 -0
  41. package/dist/tools/timing-safe-equal-string.d.ts.map +1 -0
  42. package/dist/tools/timing-safe-equal-string.js +9 -0
  43. package/dist/tools/timing-safe-equal-string.js.map +1 -0
  44. package/dist/tools/verify-callback-auth.d.ts +9 -0
  45. package/dist/tools/verify-callback-auth.d.ts.map +1 -0
  46. package/dist/tools/verify-callback-auth.js +89 -0
  47. package/dist/tools/verify-callback-auth.js.map +1 -0
  48. package/dist/webhook.d.ts +11 -0
  49. package/dist/webhook.d.ts.map +1 -0
  50. package/dist/webhook.js +11 -0
  51. package/dist/webhook.js.map +1 -0
  52. package/package.json +52 -0
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: framework-webhook
3
+ description: >-
4
+ Guides correct use of @t4h.framework/webhook in workflows: WebhookActivity,
5
+ the <<callbackUrl>> placeholder, callback auth (hmac/basic/header/bearer/jwt),
6
+ body schema, timeout, the partner's dispatch answer, and how hosts implement
7
+ the core GetActivityCallbackUrlClaim and the callbacks route. Use when a
8
+ workflow must wait for a third-party HTTP callback, when working in
9
+ packages/webhook, or when wiring a host (runtime or dev) for external advances.
10
+ ---
11
+
12
+ # @t4h.framework/webhook
13
+
14
+ Suspend a workflow until a third party calls back. Package path:
15
+ `framework/packages/webhook`. Peer dependency: `@t4h.framework/core`.
16
+
17
+ ## Import surface
18
+
19
+ ```typescript
20
+ import { webhook, WebhookActivity } from '@t4h.framework/webhook'
21
+ import type { AsyncActivityCallbackAuth } from '@t4h.framework/core'
22
+ ```
23
+
24
+ ## Workflow usage
25
+
26
+ ```typescript
27
+ const callback = await webhook({
28
+ request: {
29
+ url: 'https://partner.example.com/jobs',
30
+ body: { json: { notifyUrl: '<<callbackUrl>>' } },
31
+ },
32
+ auth: { type: 'hmac', secret: env.SECRET, header: 'x-signature' },
33
+ schema: Type.Object({ status: Type.String() }),
34
+ timeoutInMs: 86_400_000,
35
+ })
36
+
37
+ callback.dispatch?.body // the partner's answer to the request
38
+ callback.body.status // the callback body, typed by schema
39
+ ```
40
+
41
+ - `request` is optional (inbound only). Put `<<callbackUrl>>` anywhere in it.
42
+ It is sent during `bootstrap` through `HttpClientRequestClaim`; a network
43
+ error or status ≥ 400 rejects the activity at once.
44
+ - `auth` is optional; the URL token alone is the credential without it.
45
+ - `schema` is the schema of the callback **body**; the host validates it and
46
+ it types `callback.body`.
47
+ - Rejections surface as errors from `await webhook()`: `WebhookDispatchError`
48
+ (`status?`, `message`) or `WebhookTimeoutError`. The stored output is
49
+ `{ reason: 'dispatch_failed', status?, message }` or `{ reason: 'timeout' }`.
50
+ - `webhook(options, { wait: false })` sends the request and returns a
51
+ `WebhookHandle` instead of suspending; do other work, then
52
+ `await handle.wait()`.
53
+ - The pending payload is only the partner's answer to the request. A workflow
54
+ that needs its own callback URL puts `<<callbackUrl>>` in the request body
55
+ and reads it back from `callback.dispatch`.
56
+ - Secrets come from `env`, never literals.
57
+
58
+ ## Auth cheat sheet
59
+
60
+ - GitHub: `{ type: 'hmac', header: 'x-hub-signature-256', signaturePrefix: 'sha256=' }`
61
+ - Shopify: `{ type: 'hmac', header: 'x-shopify-hmac-sha256', signatureFormat: 'base64' }`
62
+ - Slack: `{ type: 'hmac', header: 'x-slack-signature', signaturePrefix: 'v0=', timestampHeader: 'x-slack-request-timestamp', toleranceInSeconds: 300, signedPayload: 'v0:{timestamp}:{body}' }`
63
+ - Fixed credentials: `basic`, `header`, `bearer`
64
+ - Google Pub/Sub push style: `{ type: 'jwt', jwksUri, issuer, audience }`
65
+
66
+ ## How it fits the core
67
+
68
+ Every pending async activity can be advanced from outside through
69
+ `POST .../activities/:activityId/callbacks?token=…`. `WebhookActivity` only
70
+ adds the outbound request: `bootstrap` reads the URL from the core
71
+ `GetActivityCallbackUrlClaim`, sends `request` with `HttpClientRequestClaim`,
72
+ and returns `pending({ payload: dispatch, schema, auth, timeoutAt })`.
73
+ `advance` receives the parsed body as payload, the bootstrap pending (for
74
+ `dispatch`) and `{ method, headers }` as the third argument
75
+ (`AsyncActivityAdvanceContext`).
76
+
77
+ ## Host contract
78
+
79
+ 1. `GetActivityCallbackUrlClaim.getActivityCallbackUrl()`: absolute `http(s)`
80
+ URL of the current activity's callbacks route with
81
+ `?token=deriveCallbackToken(secret, activityId)`. Nothing stored.
82
+ 2. Callback route: `isCallbackTokenValid` **first** (404 if the token was
83
+ never minted) → activity/pending lookup (503 + `Retry-After` if the pending
84
+ is not visible yet: the partner called back before the tick committed) →
85
+ `verifyCallbackAuth(pending.auth, { method, headers, rawBody })`
86
+ (401) → advance with `parseCallbackBody(contentType, rawBody)` and
87
+ `{ method, headers: flattenHeaders(headers) }`. 410 when no longer pending,
88
+ 202 otherwise (repeats included).
89
+ 3. Verification needs the **raw body**; parse JSON only after it.
90
+
91
+ The dev server implements this in `packages/dev` (`DevGetActivityCallbackUrlClaim`,
92
+ `CallbackTokenService`, `CreateWorkflowRunAsyncActivityPendingCallback`, route
93
+ `POST /projects/:project/workflows/:workflow/runs/:run/activities/:activity/callbacks`).
94
+
95
+ ## Testing
96
+
97
+ ```typescript
98
+ import { mockAsync, fulfilled } from '@t4h.framework/core/testing'
99
+
100
+ mockAsync().on(
101
+ WebhookActivity,
102
+ fulfilled({ method: 'POST', headers: {}, body }),
103
+ )
104
+ ```
105
+
106
+ Unit-test the activity with fake claims under `Claim.run`:
107
+
108
+ ```typescript
109
+ Claim.run(
110
+ [
111
+ { provide: HttpClientRequestClaim, value: fakeHttpClaim },
112
+ { provide: GetActivityCallbackUrlClaim, value: fakeCallbackUrlClaim },
113
+ ],
114
+ () => activity.bootstrap(input),
115
+ )
116
+ ```
117
+
118
+ See the **framework-workflow-testing** skill for the full harness API.
package/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # @t4h.framework/webhook
2
+
3
+ ## 0.0.0-experimental-20260908180423
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`e8c5796`](https://github.com/tech4humans-brasil/framework/commit/e8c5796ed6f492d7504052f59cc38b96734d0ab2)]:
8
+ - @t4h.framework/core@0.0.0-experimental-20260908180423
9
+ - @t4h.framework/hmac@0.0.0-experimental-20260908180423
10
+ - @t4h.framework/http@0.0.0-experimental-20260908180423
11
+ - @t4h.framework/jwks@0.0.0-experimental-20260908180423
12
+
13
+ ## 0.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [#158](https://github.com/tech4humans-brasil/framework/pull/158) [`8e8ffb2`](https://github.com/tech4humans-brasil/framework/commit/8e8ffb2ac0739e2f2a1f0af2ed9ebeffe2964776) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Adds `@t4h.framework/webhook` and unifies the external advance of a pending
18
+ activity behind one callback route.
19
+
20
+ **webhook**: `webhook()` sends an outbound request (with `<<callbackUrl>>`
21
+ replaced by the activity callback URL, 60s timeout) through
22
+ `HttpClientRequestClaim` and suspends the workflow until the third party posts
23
+ to that URL. The partner's answer is kept as `callback.dispatch`; the callback
24
+ body is validated against `schema`; `{ wait: false }` returns a handle to wait
25
+ on later. Ships `deriveCallbackToken`/`isCallbackTokenValid` and
26
+ `verifyCallbackAuth` for hosts.
27
+
28
+ **core**: `AsyncActivityCallbackAuth` (hmac, basic, header, bearer, jwt),
29
+ `pending({ auth })`, `advance(payload, bootstrap, context?)` receiving the
30
+ callback method and headers, and the `GetActivityCallbackUrlClaim` hosts must
31
+ provide.
32
+
33
+ **http**: `timeoutInMs` on `HttpClientRequestClaimInput`.
34
+
35
+ **dev**: `POST /projects/:project/workflows/:workflow/runs/:run/activities/:activity/callbacks?token=…`
36
+ replaces the `PATCH …/activities/:id` advance route. Each delivery is stored
37
+ as a pending callback (method + request); the token is derived from the
38
+ activity id, the declared `auth` is verified before the workflow advances.
39
+
40
+ **vite-panel-plugin**: the Inbox advance route follows the new callbacks
41
+ endpoint, with the token in the curl.
42
+
43
+ ### Patch Changes
44
+
45
+ - Updated dependencies [[`8e8ffb2`](https://github.com/tech4humans-brasil/framework/commit/8e8ffb2ac0739e2f2a1f0af2ed9ebeffe2964776)]:
46
+ - @t4h.framework/core@0.11.0
47
+ - @t4h.framework/http@0.6.0
48
+ - @t4h.framework/hmac@0.2.1
49
+ - @t4h.framework/jwks@0.5.1
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @t4h.framework/webhook
2
+
3
+ Suspend a workflow until a third party calls back over HTTP.
4
+
5
+ ## Usage
6
+
7
+ ```typescript
8
+ import { webhook } from '@t4h.framework/webhook'
9
+ import Type from 'typebox'
10
+
11
+ const callback = await webhook({
12
+ request: {
13
+ url: 'https://partner.example.com/jobs',
14
+ body: { json: { callback: '<<callbackUrl>>' } },
15
+ },
16
+ auth: { type: 'hmac', secret: env.PARTNER_SECRET, header: 'x-signature' },
17
+ schema: Type.Object({ status: Type.String() }),
18
+ timeoutInMs: 24 * 60 * 60 * 1000,
19
+ })
20
+
21
+ callback.dispatch?.body // what the partner answered to the request
22
+ callback.body.status // typed by the schema
23
+ ```
24
+
25
+ Every pending activity already has a callback URL; `webhook()` sends
26
+ `request` with the placeholder replaced by that URL and resumes the workflow
27
+ when the partner posts to it. The callback must carry the token in the URL and
28
+ pass the `auth` you declared; the body must match `schema`.
29
+
30
+ A failed request throws `WebhookDispatchError` and a callback that never
31
+ arrives before `timeoutInMs` throws `WebhookTimeoutError`, so the workflow
32
+ never sees a callback that did not happen.
33
+
34
+ Supported `auth`: `hmac` (GitHub, Shopify, Slack style), `basic`, `header`,
35
+ `bearer` and `jwt` (JWKS).
36
+
37
+ Pass `{ wait: false }` as the second argument to send the request without
38
+ suspending; you get a handle and call `handle.wait()` later.
39
+
40
+ Hosts implement the core `GetActivityCallbackUrlClaim` and the callbacks
41
+ route; the outbound request goes through `HttpClientRequestClaim` from
42
+ `@t4h.framework/http`. Peer dependency: `@t4h.framework/core`.
@@ -0,0 +1,37 @@
1
+ import { AsyncActivity, type AsyncActivityAdvanceContext, type AsyncActivityAwaitable, type AsyncActivityResultPending, type AsyncActivityCallbackAuth } from '@t4h.framework/core';
2
+ import type Type from 'typebox';
3
+ import type { WebhookCallback, WebhookCallbackOutput, WebhookDispatchResponse, WebhookRejectedOutput } from '../models/WebhookCallback.js';
4
+ import type { WebhookRequest } from '../models/WebhookRequest.js';
5
+ export type WebhookOptions<TSchema extends Type.TSchema = Type.TSchema> = {
6
+ request?: WebhookRequest;
7
+ auth?: AsyncActivityCallbackAuth;
8
+ schema?: TSchema;
9
+ timeoutInMs?: number;
10
+ };
11
+ export type WebhookActivityInput = {
12
+ request?: WebhookRequest;
13
+ auth?: AsyncActivityCallbackAuth;
14
+ schema?: Type.TSchema;
15
+ timeoutInMs?: number;
16
+ };
17
+ declare const WEBHOOK_ACTIVITY_CLAIMS: unique symbol;
18
+ export declare const WEBHOOK_DISPATCH_TIMEOUT_IN_MS = 60000;
19
+ export declare class WebhookActivity<TSchema extends Type.TSchema = Type.TSchema> extends AsyncActivity<readonly [options: WebhookOptions<TSchema>], WebhookActivityInput, WebhookCallbackOutput | WebhookRejectedOutput, WebhookCallback<Type.Static<TSchema>>, WebhookDispatchResponse | undefined, TSchema> {
20
+ private readonly [WEBHOOK_ACTIVITY_CLAIMS];
21
+ toInput(options: WebhookOptions<TSchema>): WebhookActivityInput;
22
+ bootstrap(input: WebhookActivityInput): Promise<AsyncActivityResultPending<WebhookDispatchResponse | undefined, TSchema> | import("@t4h.framework/core").AsyncActivityResultRejected<void | import("@t4h.framework/core").Serializable>>;
23
+ private dispatch;
24
+ advance(payload: Type.Static<TSchema>, bootstrap: AsyncActivityResultPending<WebhookDispatchResponse | undefined>, context?: AsyncActivityAdvanceContext): import("@t4h.framework/core").AsyncActivityResultFulfilled<WebhookRejectedOutput | WebhookCallbackOutput | undefined>;
25
+ toOutput(output: WebhookCallbackOutput | WebhookRejectedOutput): WebhookCallback<Type.Static<TSchema>>;
26
+ }
27
+ export type WebhookWaitOptions = {
28
+ wait?: boolean;
29
+ };
30
+ export type WebhookHandle<TSchema extends Type.TSchema = Type.TSchema> = AsyncActivityAwaitable<WebhookActivity<TSchema>>;
31
+ export declare function webhook<TSchema extends Type.TSchema = Type.TUnknown>(options: WebhookOptions<TSchema>): Promise<WebhookCallback<Type.Static<TSchema>>>;
32
+ export declare function webhook<TSchema extends Type.TSchema = Type.TUnknown>(options: WebhookOptions<TSchema>, wait: {
33
+ wait: false;
34
+ }): Promise<WebhookHandle<TSchema>>;
35
+ export declare function webhook<TSchema extends Type.TSchema = Type.TUnknown>(options: WebhookOptions<TSchema>, wait: WebhookWaitOptions): Promise<WebhookCallback<Type.Static<TSchema>> | WebhookHandle<TSchema>>;
36
+ export {};
37
+ //# sourceMappingURL=WebhookActivity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookActivity.d.ts","sourceRoot":"","sources":["../../src/activities/WebhookActivity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ import { text } from 'node:stream/consumers';
2
+ import { AsyncActivity, Claim, GetActivityCallbackUrlClaim, History, } from '@t4h.framework/core';
3
+ import { HttpClientRequestClaim } from '@t4h.framework/http';
4
+ import { WebhookCallbackUrlError } from '../errors/WebhookCallbackUrlError.js';
5
+ import { toWebhookRejectedError } from '../errors/WebhookRejectedError.js';
6
+ import { buildWebhookDispatch } from '../tools/build-webhook-dispatch.js';
7
+ import { flattenHeaders } from '../tools/get-header.js';
8
+ import { parseCallbackBody } from '../tools/parse-callback-body.js';
9
+ const WEBHOOK_ACTIVITY_CLAIMS = Symbol('webhook-activity-claims');
10
+ export const WEBHOOK_DISPATCH_TIMEOUT_IN_MS = 60_000;
11
+ function toCallbackUrl(callbackUrl) {
12
+ if (typeof callbackUrl !== 'string' || !callbackUrl.length)
13
+ throw new WebhookCallbackUrlError('GetActivityCallbackUrlClaim must return a callback URL', callbackUrl);
14
+ let url;
15
+ try {
16
+ url = new URL(callbackUrl);
17
+ }
18
+ catch {
19
+ throw new WebhookCallbackUrlError(`GetActivityCallbackUrlClaim returned an invalid callback URL: ${callbackUrl}`, callbackUrl);
20
+ }
21
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
22
+ throw new WebhookCallbackUrlError(`GetActivityCallbackUrlClaim must return an http(s) callback URL, got ${url.protocol}`, callbackUrl);
23
+ return url.href;
24
+ }
25
+ export class WebhookActivity extends AsyncActivity {
26
+ [WEBHOOK_ACTIVITY_CLAIMS] = new Claim({
27
+ http: HttpClientRequestClaim,
28
+ callback: GetActivityCallbackUrlClaim,
29
+ });
30
+ toInput(options) {
31
+ return {
32
+ request: options.request,
33
+ auth: options.auth,
34
+ schema: options.schema,
35
+ timeoutInMs: options.timeoutInMs,
36
+ };
37
+ }
38
+ async bootstrap(input) {
39
+ const callbackUrl = toCallbackUrl(this[WEBHOOK_ACTIVITY_CLAIMS].callback.getActivityCallbackUrl());
40
+ let dispatch;
41
+ if (input.request) {
42
+ const outcome = await this.dispatch(input.request, callbackUrl);
43
+ if ('reason' in outcome)
44
+ return this.rejected(outcome);
45
+ dispatch = outcome;
46
+ }
47
+ const options = {
48
+ payload: dispatch,
49
+ schema: input.schema,
50
+ auth: input.auth,
51
+ };
52
+ if (input.timeoutInMs)
53
+ options.timeoutAt = new Date(Date.now() + input.timeoutInMs);
54
+ return this.pending(options);
55
+ }
56
+ async dispatch(request, callbackUrl) {
57
+ const dispatch = buildWebhookDispatch(request, callbackUrl);
58
+ try {
59
+ const response = await this[WEBHOOK_ACTIVITY_CLAIMS].http.request({
60
+ url: new URL(dispatch.url),
61
+ method: dispatch.method,
62
+ body: dispatch.body === undefined ? undefined : Buffer.from(dispatch.body),
63
+ headers: dispatch.headers,
64
+ redirect: 'follow',
65
+ retry: true,
66
+ timeoutInMs: WEBHOOK_DISPATCH_TIMEOUT_IN_MS,
67
+ });
68
+ const headers = flattenHeaders(response.headers);
69
+ const rawBody = await text(response.body);
70
+ if (response.status >= 400)
71
+ return {
72
+ reason: 'dispatch_failed',
73
+ status: response.status,
74
+ message: `Dispatch to ${dispatch.url} answered ${response.status}`,
75
+ };
76
+ return {
77
+ status: response.status,
78
+ headers,
79
+ body: parseCallbackBody(headers['content-type'], rawBody),
80
+ };
81
+ }
82
+ catch (error) {
83
+ return {
84
+ reason: 'dispatch_failed',
85
+ message: error instanceof Error ? error.message : String(error),
86
+ };
87
+ }
88
+ }
89
+ advance(payload, bootstrap, context) {
90
+ return this.done({
91
+ method: context?.method ?? 'POST',
92
+ headers: context?.headers ?? {},
93
+ body: payload,
94
+ dispatch: bootstrap.payload,
95
+ });
96
+ }
97
+ toOutput(output) {
98
+ if ('reason' in output)
99
+ throw toWebhookRejectedError(this.id, output);
100
+ return {
101
+ method: output.method,
102
+ headers: output.headers,
103
+ body: output.body,
104
+ dispatch: output.dispatch,
105
+ };
106
+ }
107
+ }
108
+ export async function webhook(options, { wait = true } = {}) {
109
+ const handle = await History.reconciler(WebhookActivity, options);
110
+ return wait ? handle.wait() : handle;
111
+ }
112
+ //# sourceMappingURL=WebhookActivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookActivity.js","sourceRoot":"","sources":["../../src/activities/WebhookActivity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare class WebhookCallbackUrlError extends Error {
2
+ readonly callbackUrl: unknown;
3
+ readonly name = "WebhookCallbackUrlError";
4
+ constructor(message: string, callbackUrl: unknown);
5
+ }
6
+ //# sourceMappingURL=WebhookCallbackUrlError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookCallbackUrlError.d.ts","sourceRoot":"","sources":["../../src/errors/WebhookCallbackUrlError.ts"],"names":[],"mappings":"AAAA,qBAAa,uBAAwB,SAAQ,KAAK;aAK9B,WAAW,EAAE,OAAO;IAJtC,SAAyB,IAAI,6BAA4B;gBAGvD,OAAO,EAAE,MAAM,EACC,WAAW,EAAE,OAAO;CAIvC"}
@@ -0,0 +1,9 @@
1
+ export class WebhookCallbackUrlError extends Error {
2
+ callbackUrl;
3
+ name = 'WebhookCallbackUrlError';
4
+ constructor(message, callbackUrl) {
5
+ super(message);
6
+ this.callbackUrl = callbackUrl;
7
+ }
8
+ }
9
+ //# sourceMappingURL=WebhookCallbackUrlError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookCallbackUrlError.js","sourceRoot":"","sources":["../../src/errors/WebhookCallbackUrlError.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAK9B;IAJO,IAAI,GAAG,yBAAyB,CAAA;IAEzD,YACE,OAAe,EACC,WAAoB;QAEpC,KAAK,CAAC,OAAO,CAAC,CAAA;QAFE,gBAAW,GAAX,WAAW,CAAS;IAGtC,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ import type { WebhookDispatchFailed, WebhookRejectedOutput } from '../models/WebhookCallback.js';
2
+ export declare class WebhookTimeoutError extends Error {
3
+ readonly activityId: string;
4
+ readonly name = "WebhookTimeoutError";
5
+ readonly reason = "timeout";
6
+ constructor(activityId: string);
7
+ }
8
+ export declare class WebhookDispatchError extends Error {
9
+ readonly activityId: string;
10
+ readonly name = "WebhookDispatchError";
11
+ readonly reason = "dispatch_failed";
12
+ readonly status: number | undefined;
13
+ constructor(activityId: string, failure: WebhookDispatchFailed);
14
+ }
15
+ export type WebhookRejectedError = WebhookTimeoutError | WebhookDispatchError;
16
+ export declare function toWebhookRejectedError(activityId: string, output: WebhookRejectedOutput): WebhookRejectedError;
17
+ //# sourceMappingURL=WebhookRejectedError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookRejectedError.d.ts","sourceRoot":"","sources":["../../src/errors/WebhookRejectedError.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,8BAA8B,CAAA;AAErC,qBAAa,mBAAoB,SAAQ,KAAK;aAKhB,UAAU,EAAE,MAAM;IAJ9C,SAAyB,IAAI,yBAAwB;IAErD,SAAgB,MAAM,aAAY;gBAEN,UAAU,EAAE,MAAM;CAG/C;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aAQ3B,UAAU,EAAE,MAAM;IAPpC,SAAyB,IAAI,0BAAyB;IAEtD,SAAgB,MAAM,qBAAoB;IAE1C,SAAgB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;gBAGxB,UAAU,EAAE,MAAM,EAClC,OAAO,EAAE,qBAAqB;CAMjC;AAED,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG,oBAAoB,CAAA;AAE7E,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,qBAAqB,GAC5B,oBAAoB,CAItB"}
@@ -0,0 +1,26 @@
1
+ export class WebhookTimeoutError extends Error {
2
+ activityId;
3
+ name = 'WebhookTimeoutError';
4
+ reason = 'timeout';
5
+ constructor(activityId) {
6
+ super(`Webhook ${activityId} timed out waiting for the callback`);
7
+ this.activityId = activityId;
8
+ }
9
+ }
10
+ export class WebhookDispatchError extends Error {
11
+ activityId;
12
+ name = 'WebhookDispatchError';
13
+ reason = 'dispatch_failed';
14
+ status;
15
+ constructor(activityId, failure) {
16
+ super(failure.message);
17
+ this.activityId = activityId;
18
+ this.status = failure.status;
19
+ }
20
+ }
21
+ export function toWebhookRejectedError(activityId, output) {
22
+ return output.reason === 'timeout'
23
+ ? new WebhookTimeoutError(activityId)
24
+ : new WebhookDispatchError(activityId, output);
25
+ }
26
+ //# sourceMappingURL=WebhookRejectedError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookRejectedError.js","sourceRoot":"","sources":["../../src/errors/WebhookRejectedError.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAKhB;IAJH,IAAI,GAAG,qBAAqB,CAAA;IAErC,MAAM,GAAG,SAAS,CAAA;IAElC,YAA4B,UAAkB;QAC5C,KAAK,CAAC,WAAW,UAAU,qCAAqC,CAAC,CAAA;QADvC,eAAU,GAAV,UAAU,CAAQ;IAE9C,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAQ3B;IAPO,IAAI,GAAG,sBAAsB,CAAA;IAEtC,MAAM,GAAG,iBAAiB,CAAA;IAE1B,MAAM,CAAoB;IAE1C,YACkB,UAAkB,EAClC,OAA8B;QAE9B,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QAHN,eAAU,GAAV,UAAU,CAAQ;QAKlC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAC9B,CAAC;CACF;AAID,MAAM,UAAU,sBAAsB,CACpC,UAAkB,EAClB,MAA6B;IAE7B,OAAO,MAAM,CAAC,MAAM,KAAK,SAAS;QAChC,CAAC,CAAC,IAAI,mBAAmB,CAAC,UAAU,CAAC;QACrC,CAAC,CAAC,IAAI,oBAAoB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;AAClD,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { WebhookJson } from './WebhookRequest.js';
2
+ export type WebhookDispatchFailed = {
3
+ reason: 'dispatch_failed';
4
+ status?: number;
5
+ message: string;
6
+ };
7
+ export type WebhookTimedOut = {
8
+ reason: 'timeout';
9
+ };
10
+ export type WebhookRejectedOutput = WebhookDispatchFailed | WebhookTimedOut;
11
+ export type WebhookDispatchResponse = {
12
+ status: number;
13
+ headers: Record<string, string>;
14
+ body?: WebhookJson;
15
+ };
16
+ export type WebhookCallbackOutput = {
17
+ method: string;
18
+ headers: Record<string, string>;
19
+ body?: WebhookJson;
20
+ dispatch?: WebhookDispatchResponse;
21
+ };
22
+ export interface WebhookCallback<TBody = unknown> {
23
+ method: string;
24
+ headers: Record<string, string>;
25
+ body: TBody;
26
+ dispatch?: WebhookDispatchResponse;
27
+ }
28
+ //# sourceMappingURL=WebhookCallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookCallback.d.ts","sourceRoot":"","sources":["../../src/models/WebhookCallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,iBAAiB,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,SAAS,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,qBAAqB,GAAG,eAAe,CAAA;AAE3E,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,QAAQ,CAAC,EAAE,uBAAuB,CAAA;CACnC,CAAA;AAED,MAAM,WAAW,eAAe,CAAC,KAAK,GAAG,OAAO;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,EAAE,KAAK,CAAA;IACX,QAAQ,CAAC,EAAE,uBAAuB,CAAA;CACnC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=WebhookCallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookCallback.js","sourceRoot":"","sources":["../../src/models/WebhookCallback.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ export type WebhookJson = string | number | boolean | null | readonly WebhookJson[] | {
2
+ readonly [key: string]: WebhookJson | undefined;
3
+ };
4
+ export type WebhookRequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
5
+ export type WebhookRequestBody = {
6
+ json: WebhookJson;
7
+ text?: never;
8
+ } | {
9
+ json?: never;
10
+ text: string;
11
+ };
12
+ /**
13
+ * The outbound HTTP request the host dispatches once the webhook is open.
14
+ * Any string inside `url`, `headers` or `body` may carry
15
+ * {@link WEBHOOK_CALLBACK_URL_PLACEHOLDER}; the host replaces it with the
16
+ * callback URL right before sending.
17
+ */
18
+ export type WebhookRequest = {
19
+ url: string;
20
+ method?: WebhookRequestMethod;
21
+ headers?: Record<string, string>;
22
+ body?: WebhookRequestBody;
23
+ };
24
+ export declare const WEBHOOK_CALLBACK_URL_PLACEHOLDER = "<<callbackUrl>>";
25
+ //# sourceMappingURL=WebhookRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookRequest.d.ts","sourceRoot":"","sources":["../../src/models/WebhookRequest.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,WAAW,EAAE,GACtB;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAA;CAAE,CAAA;AAEvD,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE9E,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,KAAK,CAAA;CAAE,GACnC;IAAE,IAAI,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAElC;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,oBAAoB,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC,EAAE,kBAAkB,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,gCAAgC,oBAAoB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export const WEBHOOK_CALLBACK_URL_PLACEHOLDER = '<<callbackUrl>>';
2
+ //# sourceMappingURL=WebhookRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookRequest.js","sourceRoot":"","sources":["../../src/models/WebhookRequest.ts"],"names":[],"mappings":"AA2BA,MAAM,CAAC,MAAM,gCAAgC,GAAG,iBAAiB,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { type WebhookRequest, type WebhookRequestMethod } from '../models/WebhookRequest.js';
2
+ export interface WebhookDispatch {
3
+ url: string;
4
+ method: WebhookRequestMethod;
5
+ headers: Record<string, string>;
6
+ body?: string;
7
+ }
8
+ /**
9
+ * Resolves {@link WEBHOOK_CALLBACK_URL_PLACEHOLDER} everywhere in the request
10
+ * and encodes the body. Hosts call this right before the HTTP call so both
11
+ * hosts dispatch the same bytes for the same input.
12
+ */
13
+ export declare function buildWebhookDispatch(request: WebhookRequest, callbackUrl: string): WebhookDispatch;
14
+ //# sourceMappingURL=build-webhook-dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-webhook-dispatch.d.ts","sourceRoot":"","sources":["../../src/tools/build-webhook-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAC1B,MAAM,6BAA6B,CAAA;AAEpC,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,oBAAoB,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA0BD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,GAClB,eAAe,CAyBjB"}
@@ -0,0 +1,44 @@
1
+ import { WEBHOOK_CALLBACK_URL_PLACEHOLDER, } from '../models/WebhookRequest.js';
2
+ function replacePlaceholder(value, callbackUrl) {
3
+ if (typeof value === 'string')
4
+ return value.replaceAll(WEBHOOK_CALLBACK_URL_PLACEHOLDER, callbackUrl);
5
+ if (Array.isArray(value))
6
+ return value.map(item => replacePlaceholder(item, callbackUrl));
7
+ if (value && typeof value === 'object')
8
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
9
+ key,
10
+ replacePlaceholder(item, callbackUrl),
11
+ ]));
12
+ return value;
13
+ }
14
+ function hasHeader(headers, name) {
15
+ const wanted = name.toLowerCase();
16
+ return Object.keys(headers).some(key => key.toLowerCase() === wanted);
17
+ }
18
+ /**
19
+ * Resolves {@link WEBHOOK_CALLBACK_URL_PLACEHOLDER} everywhere in the request
20
+ * and encodes the body. Hosts call this right before the HTTP call so both
21
+ * hosts dispatch the same bytes for the same input.
22
+ */
23
+ export function buildWebhookDispatch(request, callbackUrl) {
24
+ const resolved = replacePlaceholder(request, callbackUrl);
25
+ const headers = { ...resolved.headers };
26
+ let body;
27
+ if (resolved.body?.json !== undefined) {
28
+ body = JSON.stringify(resolved.body.json);
29
+ if (!hasHeader(headers, 'content-type'))
30
+ headers['content-type'] = 'application/json';
31
+ }
32
+ else if (resolved.body?.text !== undefined) {
33
+ body = resolved.body.text;
34
+ if (!hasHeader(headers, 'content-type'))
35
+ headers['content-type'] = 'text/plain';
36
+ }
37
+ return {
38
+ url: resolved.url,
39
+ method: resolved.method ?? 'POST',
40
+ headers,
41
+ body,
42
+ };
43
+ }
44
+ //# sourceMappingURL=build-webhook-dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-webhook-dispatch.js","sourceRoot":"","sources":["../../src/tools/build-webhook-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,GAGjC,MAAM,6BAA6B,CAAA;AASpC,SAAS,kBAAkB,CAAC,KAAc,EAAE,WAAmB;IAC7D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAC3B,OAAO,KAAK,CAAC,UAAU,CAAC,gCAAgC,EAAE,WAAW,CAAC,CAAA;IAExE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACtB,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAA;IAEjE,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QACpC,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;YACzC,GAAG;YACH,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC;SACtC,CAAC,CACH,CAAA;IAEH,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,SAAS,CAAC,OAA+B,EAAE,IAAY;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IAEjC,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAA;AACvE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAuB,EACvB,WAAmB;IAEnB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAmB,CAAA;IAE3E,MAAM,OAAO,GAA2B,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAA;IAE/D,IAAI,IAAwB,CAAA;IAE5B,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;QACtC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEzC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,cAAc,CAAC;YACrC,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAA;IAChD,CAAC;SAAM,IAAI,QAAQ,CAAC,IAAI,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7C,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAA;QAEzB,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,cAAc,CAAC;YACrC,OAAO,CAAC,cAAc,CAAC,GAAG,YAAY,CAAA;IAC1C,CAAC;IAED,OAAO;QACL,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,MAAM;QACjC,OAAO;QACP,IAAI;KACL,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function deriveCallbackToken(secret: string, activityId: string): string;
2
+ export declare function isCallbackTokenValid(secret: string, activityId: string, token: string): boolean;
3
+ //# sourceMappingURL=callback-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-token.d.ts","sourceRoot":"","sources":["../../src/tools/callback-token.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,UAKrE;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,WASd"}
@@ -0,0 +1,14 @@
1
+ import crypto from 'node:crypto';
2
+ export function deriveCallbackToken(secret, activityId) {
3
+ return crypto
4
+ .createHmac('sha256', secret)
5
+ .update(activityId)
6
+ .digest('base64url');
7
+ }
8
+ export function isCallbackTokenValid(secret, activityId, token) {
9
+ const expected = Buffer.from(deriveCallbackToken(secret, activityId));
10
+ const candidate = Buffer.from(token);
11
+ return (expected.length === candidate.length &&
12
+ crypto.timingSafeEqual(expected, candidate));
13
+ }
14
+ //# sourceMappingURL=callback-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-token.js","sourceRoot":"","sources":["../../src/tools/callback-token.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAA;AAEhC,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,UAAkB;IACpE,OAAO,MAAM;SACV,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;SAC5B,MAAM,CAAC,UAAU,CAAC;SAClB,MAAM,CAAC,WAAW,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,UAAkB,EAClB,KAAa;IAEb,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;IACrE,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAEpC,OAAO,CACL,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;QACpC,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,CAC5C,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ export type CallbackHeaders = Record<string, string | string[] | undefined>;
2
+ export declare function getHeader(headers: CallbackHeaders, name: string): string | undefined;
3
+ export declare function flattenHeaders(headers: CallbackHeaders): Record<string, string>;
4
+ //# sourceMappingURL=get-header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-header.d.ts","sourceRoot":"","sources":["../../src/tools/get-header.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;AAE3E,wBAAgB,SAAS,CACvB,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAYpB;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,eAAe,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB"}
@@ -0,0 +1,21 @@
1
+ export function getHeader(headers, name) {
2
+ const wanted = name.toLowerCase();
3
+ for (const [key, value] of Object.entries(headers)) {
4
+ if (key.toLowerCase() !== wanted)
5
+ continue;
6
+ if (Array.isArray(value))
7
+ return value[0];
8
+ return value;
9
+ }
10
+ return undefined;
11
+ }
12
+ export function flattenHeaders(headers) {
13
+ const result = {};
14
+ for (const [name, value] of Object.entries(headers)) {
15
+ if (value === undefined)
16
+ continue;
17
+ result[name.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value;
18
+ }
19
+ return result;
20
+ }
21
+ //# sourceMappingURL=get-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-header.js","sourceRoot":"","sources":["../../src/tools/get-header.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CACvB,OAAwB,EACxB,IAAY;IAEZ,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IAEjC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,MAAM;YAAE,SAAQ;QAE1C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAA;QAEzC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAAwB;IAExB,MAAM,MAAM,GAA2B,EAAE,CAAA;IAEzC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QAEjC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC9E,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { WebhookJson } from '../models/WebhookRequest.js';
2
+ export declare function parseCallbackBody(contentType: string | undefined, rawBody: string): WebhookJson | undefined;
3
+ //# sourceMappingURL=parse-callback-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-callback-body.d.ts","sourceRoot":"","sources":["../../src/tools/parse-callback-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAA;AAM9D,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,MAAM,GACd,WAAW,GAAG,SAAS,CA2BzB"}
@@ -0,0 +1,25 @@
1
+ const JSON_CONTENT_TYPE = /^application\/([\w.-]+\+)?json$/i;
2
+ const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded';
3
+ export function parseCallbackBody(contentType, rawBody) {
4
+ if (!rawBody.length)
5
+ return undefined;
6
+ const type = contentType?.split(';')[0]?.trim().toLowerCase() ?? '';
7
+ if (JSON_CONTENT_TYPE.test(type))
8
+ try {
9
+ return JSON.parse(rawBody);
10
+ }
11
+ catch {
12
+ return rawBody;
13
+ }
14
+ if (type === FORM_CONTENT_TYPE) {
15
+ const params = new URLSearchParams(rawBody);
16
+ const result = {};
17
+ for (const key of new Set(params.keys())) {
18
+ const values = params.getAll(key);
19
+ result[key] = values.length === 1 ? values[0] : values;
20
+ }
21
+ return result;
22
+ }
23
+ return rawBody;
24
+ }
25
+ //# sourceMappingURL=parse-callback-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-callback-body.js","sourceRoot":"","sources":["../../src/tools/parse-callback-body.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,kCAAkC,CAAA;AAE5D,MAAM,iBAAiB,GAAG,mCAAmC,CAAA;AAE7D,MAAM,UAAU,iBAAiB,CAC/B,WAA+B,EAC/B,OAAe;IAEf,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAErC,MAAM,IAAI,GAAG,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAA;IAEnE,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAA;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,OAAO,CAAA;QAChB,CAAC;IAEH,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;QAE3C,MAAM,MAAM,GAAgC,EAAE,CAAA;QAE9C,KAAK,MAAM,GAAG,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAEjC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,MAAM,CAAA;QACzD,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function timingSafeEqualString(expected: string, candidate: string): boolean;
2
+ //# sourceMappingURL=timing-safe-equal-string.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing-safe-equal-string.d.ts","sourceRoot":"","sources":["../../src/tools/timing-safe-equal-string.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,WAOxE"}
@@ -0,0 +1,9 @@
1
+ import crypto from 'node:crypto';
2
+ export function timingSafeEqualString(expected, candidate) {
3
+ const a = Buffer.from(expected, 'utf8');
4
+ const b = Buffer.from(candidate, 'utf8');
5
+ if (a.length !== b.length)
6
+ return false;
7
+ return crypto.timingSafeEqual(a, b);
8
+ }
9
+ //# sourceMappingURL=timing-safe-equal-string.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing-safe-equal-string.js","sourceRoot":"","sources":["../../src/tools/timing-safe-equal-string.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAA;AAEhC,MAAM,UAAU,qBAAqB,CAAC,QAAgB,EAAE,SAAiB;IACvE,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACvC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAExC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEvC,OAAO,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACrC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { AsyncActivityCallbackAuth } from '@t4h.framework/core';
2
+ import { type CallbackHeaders } from './get-header.js';
3
+ export interface CallbackRequest {
4
+ method: string;
5
+ headers: CallbackHeaders;
6
+ rawBody: string;
7
+ }
8
+ export declare function verifyCallbackAuth(auth: AsyncActivityCallbackAuth | undefined, request: CallbackRequest): Promise<boolean>;
9
+ //# sourceMappingURL=verify-callback-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-callback-auth.d.ts","sourceRoot":"","sources":["../../src/tools/verify-callback-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAM1B,MAAM,qBAAqB,CAAA;AAI5B,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,eAAe,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;CAChB;AAuGD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,yBAAyB,GAAG,SAAS,EAC3C,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,OAAO,CAAC,CAmBlB"}
@@ -0,0 +1,89 @@
1
+ import hmac from '@t4h.framework/hmac/authorize';
2
+ import jwks from '@t4h.framework/jwks/authorize';
3
+ import { getHeader } from './get-header.js';
4
+ import { timingSafeEqualString } from './timing-safe-equal-string.js';
5
+ const BEARER = /^Bearer\s+(.+)$/i;
6
+ function getBearerToken(headers, name = 'authorization') {
7
+ const value = getHeader(headers, name);
8
+ if (!value)
9
+ return undefined;
10
+ return BEARER.exec(value)?.[1]?.trim();
11
+ }
12
+ function verifyHmac(auth, request) {
13
+ const signature = getHeader(request.headers, auth.header);
14
+ if (!signature)
15
+ return false;
16
+ const timestamp = auth.timestampHeader
17
+ ? getHeader(request.headers, auth.timestampHeader)
18
+ : undefined;
19
+ if (auth.timestampHeader && !timestamp)
20
+ return false;
21
+ const rawPayload = (auth.signedPayload ?? '{body}')
22
+ .replaceAll('{timestamp}', timestamp ?? '')
23
+ .replaceAll('{body}', request.rawBody);
24
+ return hmac({
25
+ secret: auth.secret,
26
+ signature,
27
+ rawPayload,
28
+ algorithm: auth.algorithm,
29
+ signatureFormat: auth.signatureFormat,
30
+ signaturePrefix: auth.signaturePrefix,
31
+ timestamp,
32
+ toleranceInSeconds: auth.toleranceInSeconds,
33
+ });
34
+ }
35
+ function verifyBasic(auth, request) {
36
+ const value = getHeader(request.headers, 'authorization');
37
+ if (!value)
38
+ return false;
39
+ const expected = `Basic ${Buffer.from(`${auth.username}:${auth.password}`).toString('base64')}`;
40
+ return timingSafeEqualString(expected, value);
41
+ }
42
+ function verifyHeader(auth, request) {
43
+ const value = getHeader(request.headers, auth.name);
44
+ if (!value)
45
+ return false;
46
+ return timingSafeEqualString(auth.value, value);
47
+ }
48
+ function verifyBearer(auth, request) {
49
+ const token = getBearerToken(request.headers);
50
+ if (!token)
51
+ return false;
52
+ return timingSafeEqualString(auth.token, token);
53
+ }
54
+ async function verifyJwt(auth, request) {
55
+ const token = getBearerToken(request.headers, auth.header);
56
+ if (!token)
57
+ return false;
58
+ try {
59
+ return await jwks({
60
+ url: auth.jwksUri,
61
+ signature: token,
62
+ rawPayload: '',
63
+ issuer: auth.issuer,
64
+ audience: auth.audience,
65
+ });
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ export async function verifyCallbackAuth(auth, request) {
72
+ if (!auth)
73
+ return true;
74
+ switch (auth.type) {
75
+ case 'hmac':
76
+ return verifyHmac(auth, request);
77
+ case 'basic':
78
+ return verifyBasic(auth, request);
79
+ case 'header':
80
+ return verifyHeader(auth, request);
81
+ case 'bearer':
82
+ return verifyBearer(auth, request);
83
+ case 'jwt':
84
+ return verifyJwt(auth, request);
85
+ default:
86
+ throw new TypeError(`Unsupported callback auth type: ${String(auth.type)}`);
87
+ }
88
+ }
89
+ //# sourceMappingURL=verify-callback-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-callback-auth.js","sourceRoot":"","sources":["../../src/tools/verify-callback-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export * from './activities/WebhookActivity.js';
2
+ export * from './errors/WebhookCallbackUrlError.js';
3
+ export * from './errors/WebhookRejectedError.js';
4
+ export * from './models/WebhookCallback.js';
5
+ export * from './models/WebhookRequest.js';
6
+ export * from './tools/build-webhook-dispatch.js';
7
+ export * from './tools/callback-token.js';
8
+ export * from './tools/get-header.js';
9
+ export * from './tools/parse-callback-body.js';
10
+ export * from './tools/verify-callback-auth.js';
11
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAA;AAC/C,cAAc,qCAAqC,CAAA;AACnD,cAAc,kCAAkC,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,mCAAmC,CAAA;AACjD,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA"}
@@ -0,0 +1,11 @@
1
+ export * from './activities/WebhookActivity.js';
2
+ export * from './errors/WebhookCallbackUrlError.js';
3
+ export * from './errors/WebhookRejectedError.js';
4
+ export * from './models/WebhookCallback.js';
5
+ export * from './models/WebhookRequest.js';
6
+ export * from './tools/build-webhook-dispatch.js';
7
+ export * from './tools/callback-token.js';
8
+ export * from './tools/get-header.js';
9
+ export * from './tools/parse-callback-body.js';
10
+ export * from './tools/verify-callback-auth.js';
11
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../src/webhook.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAA;AAC/C,cAAc,qCAAqC,CAAA;AACnD,cAAc,kCAAkC,CAAA;AAChD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,mCAAmC,CAAA;AACjD,cAAc,2BAA2B,CAAA;AACzC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,iCAAiC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@t4h.framework/webhook",
3
+ "version": "0.0.0-experimental-20260908180423",
4
+ "description": "Webhook module for the T4H Framework",
5
+ "homepage": "https://github.com/tech4humans-brasil/framework/tree/main/packages/webhook",
6
+ "bugs": "https://github.com/tech4humans-brasil/framework/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/tech4humans-brasil/framework.git",
10
+ "directory": "packages/webhook"
11
+ },
12
+ "license": "MIT",
13
+ "author": "Tech4Humans <contact@tech4h.com.br> (https://tech4h.com.br)",
14
+ "type": "module",
15
+ "exports": {
16
+ "types": "./dist/webhook.d.ts",
17
+ "import": "./dist/webhook.js"
18
+ },
19
+ "module": "./dist/webhook.js",
20
+ "types": "./dist/webhook.d.ts",
21
+ "files": [
22
+ "dist",
23
+ "LICENSE",
24
+ ".ai"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc --project tsconfig.build.json",
28
+ "check-types": "tsc --noEmit",
29
+ "prepublishOnly": "yarn build",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest"
32
+ },
33
+ "dependencies": {
34
+ "@t4h.framework/hmac": "^0.0.0-experimental-20260908180423",
35
+ "@t4h.framework/jwks": "^0.0.0-experimental-20260908180423",
36
+ "typebox": "^1.1.6"
37
+ },
38
+ "devDependencies": {
39
+ "@t4h.framework/core": "^0.0.0-experimental-20260908180423",
40
+ "@t4h.framework/http": "^0.0.0-experimental-20260908180423",
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^4.0.18"
43
+ },
44
+ "peerDependencies": {
45
+ "@t4h.framework/core": "^0.0.0-experimental-20260908180423",
46
+ "@t4h.framework/http": "^0.0.0-experimental-20260908180423"
47
+ },
48
+ "packageManager": "yarn@4.12.0",
49
+ "engines": {
50
+ "node": ">=22"
51
+ }
52
+ }