@t4h.framework/webhook 0.0.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/.ai/skills/framework-webhook/SKILL.md +118 -0
- package/README.md +42 -0
- package/dist/activities/WebhookActivity.d.ts +37 -0
- package/dist/activities/WebhookActivity.d.ts.map +1 -0
- package/dist/activities/WebhookActivity.js +112 -0
- package/dist/activities/WebhookActivity.js.map +1 -0
- package/dist/errors/WebhookCallbackUrlError.d.ts +6 -0
- package/dist/errors/WebhookCallbackUrlError.d.ts.map +1 -0
- package/dist/errors/WebhookCallbackUrlError.js +9 -0
- package/dist/errors/WebhookCallbackUrlError.js.map +1 -0
- package/dist/errors/WebhookRejectedError.d.ts +17 -0
- package/dist/errors/WebhookRejectedError.d.ts.map +1 -0
- package/dist/errors/WebhookRejectedError.js +26 -0
- package/dist/errors/WebhookRejectedError.js.map +1 -0
- package/dist/models/WebhookCallback.d.ts +28 -0
- package/dist/models/WebhookCallback.d.ts.map +1 -0
- package/dist/models/WebhookCallback.js +2 -0
- package/dist/models/WebhookCallback.js.map +1 -0
- package/dist/models/WebhookClaim.d.ts +19 -0
- package/dist/models/WebhookClaim.d.ts.map +1 -0
- package/dist/models/WebhookClaim.js +10 -0
- package/dist/models/WebhookClaim.js.map +1 -0
- package/dist/models/WebhookRequest.d.ts +25 -0
- package/dist/models/WebhookRequest.d.ts.map +1 -0
- package/dist/models/WebhookRequest.js +2 -0
- package/dist/models/WebhookRequest.js.map +1 -0
- package/dist/tools/build-webhook-dispatch.d.ts +14 -0
- package/dist/tools/build-webhook-dispatch.d.ts.map +1 -0
- package/dist/tools/build-webhook-dispatch.js +44 -0
- package/dist/tools/build-webhook-dispatch.js.map +1 -0
- package/dist/tools/callback-token.d.ts +3 -0
- package/dist/tools/callback-token.d.ts.map +1 -0
- package/dist/tools/callback-token.js +14 -0
- package/dist/tools/callback-token.js.map +1 -0
- package/dist/tools/get-header.d.ts +4 -0
- package/dist/tools/get-header.d.ts.map +1 -0
- package/dist/tools/get-header.js +21 -0
- package/dist/tools/get-header.js.map +1 -0
- package/dist/tools/parse-callback-body.d.ts +3 -0
- package/dist/tools/parse-callback-body.d.ts.map +1 -0
- package/dist/tools/parse-callback-body.js +25 -0
- package/dist/tools/parse-callback-body.js.map +1 -0
- package/dist/tools/timing-safe-equal-string.d.ts +2 -0
- package/dist/tools/timing-safe-equal-string.d.ts.map +1 -0
- package/dist/tools/timing-safe-equal-string.js +9 -0
- package/dist/tools/timing-safe-equal-string.js.map +1 -0
- package/dist/tools/verify-callback-auth.d.ts +9 -0
- package/dist/tools/verify-callback-auth.d.ts.map +1 -0
- package/dist/tools/verify-callback-auth.js +89 -0
- package/dist/tools/verify-callback-auth.js.map +1 -0
- package/dist/webhook.d.ts +11 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +11 -0
- package/dist/webhook.js.map +1 -0
- 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/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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"WebhookCallback.js","sourceRoot":"","sources":["../../src/models/WebhookCallback.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WebhookRequest } from './WebhookRequest.js';
|
|
2
|
+
export type WebhookClaimDispatchInput = {
|
|
3
|
+
/** Id of the activity that will wait for the callback. */
|
|
4
|
+
activityId: string;
|
|
5
|
+
/** Outbound request; `{{callbackUrl}}` inside it is replaced by `callbackUrl`. */
|
|
6
|
+
request: WebhookRequest;
|
|
7
|
+
callbackUrl: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Host side of the outbound half of a webhook. `dispatch` is called from
|
|
11
|
+
* `WebhookActivity.bootstrap`, so in a transactional host it runs inside the
|
|
12
|
+
* activity transaction: defer the HTTP call (outbox, queue) to after the
|
|
13
|
+
* commit, so the third party can never call back before the pending exists.
|
|
14
|
+
* A dispatch that fails must reject the activity with `dispatch_failed`.
|
|
15
|
+
*/
|
|
16
|
+
export declare abstract class WebhookClaim {
|
|
17
|
+
abstract dispatch(input: WebhookClaimDispatchInput): void | Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=WebhookClaim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebhookClaim.d.ts","sourceRoot":"","sources":["../../src/models/WebhookClaim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD,MAAM,MAAM,yBAAyB,GAAG;IACtC,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,OAAO,EAAE,cAAc,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;GAMG;AACH,8BAAsB,YAAY;aAChB,QAAQ,CACtB,KAAK,EAAE,yBAAyB,GAC/B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CACxB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host side of the outbound half of a webhook. `dispatch` is called from
|
|
3
|
+
* `WebhookActivity.bootstrap`, so in a transactional host it runs inside the
|
|
4
|
+
* activity transaction: defer the HTTP call (outbox, queue) to after the
|
|
5
|
+
* commit, so the third party can never call back before the pending exists.
|
|
6
|
+
* A dispatch that fails must reject the activity with `dispatch_failed`.
|
|
7
|
+
*/
|
|
8
|
+
export class WebhookClaim {
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=WebhookClaim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebhookClaim.js","sourceRoot":"","sources":["../../src/models/WebhookClaim.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,MAAM,OAAgB,YAAY;CAIjC"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/webhook.js
ADDED
|
@@ -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",
|
|
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.1.0",
|
|
35
|
+
"@t4h.framework/jwks": "^0.4.0",
|
|
36
|
+
"typebox": "^1.1.6"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@t4h.framework/core": "^0.9.1",
|
|
40
|
+
"@t4h.framework/http": "^0.4.4",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vitest": "^4.0.18"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@t4h.framework/core": "^0.9.1",
|
|
46
|
+
"@t4h.framework/http": "^0.4.4"
|
|
47
|
+
},
|
|
48
|
+
"packageManager": "yarn@4.12.0",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22"
|
|
51
|
+
}
|
|
52
|
+
}
|