@blokjs/trigger-webhook 0.2.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/WebhookTrigger.d.ts +100 -117
- package/dist/WebhookTrigger.js +315 -316
- package/dist/index.d.ts +36 -65
- package/dist/index.js +34 -71
- package/dist/verifiers.d.ts +80 -0
- package/dist/verifiers.js +294 -0
- package/package.json +6 -4
- package/src/WebhookTrigger.integration.test.ts +162 -0
- package/src/WebhookTrigger.test.ts +232 -143
- package/src/WebhookTrigger.ts +386 -407
- package/src/index.ts +41 -70
- package/src/verifiers.test.ts +316 -0
- package/src/verifiers.ts +357 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook signature verifiers — one strategy per supported provider.
|
|
3
|
+
*
|
|
4
|
+
* Each verifier reads the raw request bytes (NOT the JSON-parsed body)
|
|
5
|
+
* and the provider-specific signature header, computes the expected
|
|
6
|
+
* HMAC against a shared secret, and constant-time compares. On match,
|
|
7
|
+
* returns `{ ok: true, eventId, eventType }`. On mismatch / missing
|
|
8
|
+
* header / drift, returns `{ ok: false, reason, message }`.
|
|
9
|
+
*
|
|
10
|
+
* Constant-time comparison via `crypto.timingSafeEqual` is mandatory —
|
|
11
|
+
* a naive `===` compare leaks the expected HMAC byte by byte through
|
|
12
|
+
* timing variance and a network-adjacent attacker can recover the
|
|
13
|
+
* secret in ~256 requests per byte.
|
|
14
|
+
*
|
|
15
|
+
* Built-in providers + their signature shapes:
|
|
16
|
+
*
|
|
17
|
+
* - **github**: `X-Hub-Signature-256: sha256=<hex>` over rawBody.
|
|
18
|
+
* Event id from `X-GitHub-Delivery`; event type
|
|
19
|
+
* from `X-GitHub-Event` header.
|
|
20
|
+
* - **stripe**: `Stripe-Signature: t=<ts>,v1=<hex>` over
|
|
21
|
+
* `<ts>.<rawBody>` with a 5-minute drift window.
|
|
22
|
+
* Event id + type from body.id / body.type.
|
|
23
|
+
* - **slack**: `X-Slack-Signature: v0=<hex>` over
|
|
24
|
+
* `v0:<X-Slack-Request-Timestamp>:<rawBody>` with a
|
|
25
|
+
* 5-minute drift window. Event type from
|
|
26
|
+
* body.event.type; event id from body.event_id.
|
|
27
|
+
* - **shopify**: `X-Shopify-Hmac-Sha256: <base64>` over rawBody.
|
|
28
|
+
* Event type from `X-Shopify-Topic`; event id from
|
|
29
|
+
* `X-Shopify-Webhook-Id`.
|
|
30
|
+
* - **svix**: Standard Webhooks. `webhook-signature:
|
|
31
|
+
* v1,<base64>` over `<webhook-id>.<webhook-timestamp>.<rawBody>`
|
|
32
|
+
* with a 5-minute drift window. Event id from
|
|
33
|
+
* `webhook-id`; event type from body.type.
|
|
34
|
+
*
|
|
35
|
+
* Custom (unknown provider) verifier is built dynamically by
|
|
36
|
+
* `buildCustomVerifier()` from the workflow's `signature` config.
|
|
37
|
+
*/
|
|
38
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
39
|
+
const DEFAULT_TOLERANCE_SEC = 300;
|
|
40
|
+
function safeEqualString(a, b) {
|
|
41
|
+
const aBuf = Buffer.from(a);
|
|
42
|
+
const bBuf = Buffer.from(b);
|
|
43
|
+
if (aBuf.length !== bBuf.length)
|
|
44
|
+
return false;
|
|
45
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
46
|
+
}
|
|
47
|
+
function hmacHex(algo, secret, data) {
|
|
48
|
+
return createHmac(algo, secret).update(data).digest("hex");
|
|
49
|
+
}
|
|
50
|
+
function hmacBase64(algo, secret, data) {
|
|
51
|
+
return createHmac(algo, secret).update(data).digest("base64");
|
|
52
|
+
}
|
|
53
|
+
function isWithinTolerance(timestampSec, toleranceSec) {
|
|
54
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
55
|
+
return Math.abs(nowSec - timestampSec) <= toleranceSec;
|
|
56
|
+
}
|
|
57
|
+
function getEventTypeFromBody(parsedBody, key = "type") {
|
|
58
|
+
if (!parsedBody || typeof parsedBody !== "object")
|
|
59
|
+
return undefined;
|
|
60
|
+
const value = parsedBody[key];
|
|
61
|
+
return typeof value === "string" ? value : undefined;
|
|
62
|
+
}
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Built-in providers
|
|
65
|
+
// =============================================================================
|
|
66
|
+
export const githubVerifier = {
|
|
67
|
+
verify({ headers, rawBody, secret }) {
|
|
68
|
+
if (!secret)
|
|
69
|
+
return { ok: false, reason: "missing_secret", message: "GitHub: secret not configured" };
|
|
70
|
+
const sig = headers["x-hub-signature-256"];
|
|
71
|
+
if (!sig)
|
|
72
|
+
return { ok: false, reason: "missing_signature", message: "GitHub: X-Hub-Signature-256 header missing" };
|
|
73
|
+
const expected = `sha256=${hmacHex("sha256", secret, rawBody)}`;
|
|
74
|
+
if (!safeEqualString(sig, expected)) {
|
|
75
|
+
return { ok: false, reason: "signature_mismatch", message: "GitHub: signature mismatch" };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
eventId: headers["x-github-delivery"] ?? "",
|
|
80
|
+
eventType: headers["x-github-event"] ?? "unknown",
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
export const stripeVerifier = {
|
|
85
|
+
verify({ headers, rawBody, parsedBody, secret, toleranceSec }) {
|
|
86
|
+
if (!secret)
|
|
87
|
+
return { ok: false, reason: "missing_secret", message: "Stripe: secret not configured" };
|
|
88
|
+
const sig = headers["stripe-signature"];
|
|
89
|
+
if (!sig)
|
|
90
|
+
return { ok: false, reason: "missing_signature", message: "Stripe: Stripe-Signature header missing" };
|
|
91
|
+
// Format: t=1234567890,v1=<hex>,v0=<hex>,...
|
|
92
|
+
const parts = Object.fromEntries(sig
|
|
93
|
+
.split(",")
|
|
94
|
+
.map((p) => p.trim())
|
|
95
|
+
.map((p) => {
|
|
96
|
+
const idx = p.indexOf("=");
|
|
97
|
+
return idx === -1 ? [p, ""] : [p.slice(0, idx), p.slice(idx + 1)];
|
|
98
|
+
}));
|
|
99
|
+
const ts = parts.t;
|
|
100
|
+
const v1 = parts.v1;
|
|
101
|
+
if (!ts || !v1) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
reason: "bad_format",
|
|
105
|
+
message: "Stripe: signature missing t= or v1= component",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const tsNum = Number.parseInt(ts, 10);
|
|
109
|
+
if (!Number.isFinite(tsNum)) {
|
|
110
|
+
return { ok: false, reason: "bad_format", message: "Stripe: t= is not numeric" };
|
|
111
|
+
}
|
|
112
|
+
if (!isWithinTolerance(tsNum, toleranceSec || DEFAULT_TOLERANCE_SEC)) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
reason: "timestamp_drift",
|
|
116
|
+
message: `Stripe: timestamp drift exceeds ${toleranceSec || DEFAULT_TOLERANCE_SEC}s tolerance`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const expected = hmacHex("sha256", secret, `${ts}.${rawBody}`);
|
|
120
|
+
if (!safeEqualString(v1, expected)) {
|
|
121
|
+
return { ok: false, reason: "signature_mismatch", message: "Stripe: signature mismatch" };
|
|
122
|
+
}
|
|
123
|
+
const eventId = parsedBody?.id;
|
|
124
|
+
const eventType = getEventTypeFromBody(parsedBody);
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
eventId: typeof eventId === "string" ? eventId : "",
|
|
128
|
+
eventType: eventType ?? "unknown",
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
export const slackVerifier = {
|
|
133
|
+
verify({ headers, rawBody, parsedBody, secret, toleranceSec }) {
|
|
134
|
+
if (!secret)
|
|
135
|
+
return { ok: false, reason: "missing_secret", message: "Slack: secret not configured" };
|
|
136
|
+
const sig = headers["x-slack-signature"];
|
|
137
|
+
if (!sig)
|
|
138
|
+
return { ok: false, reason: "missing_signature", message: "Slack: X-Slack-Signature header missing" };
|
|
139
|
+
const ts = headers["x-slack-request-timestamp"];
|
|
140
|
+
if (!ts) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
reason: "missing_timestamp",
|
|
144
|
+
message: "Slack: X-Slack-Request-Timestamp header missing",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const tsNum = Number.parseInt(ts, 10);
|
|
148
|
+
if (!Number.isFinite(tsNum)) {
|
|
149
|
+
return { ok: false, reason: "bad_format", message: "Slack: timestamp is not numeric" };
|
|
150
|
+
}
|
|
151
|
+
if (!isWithinTolerance(tsNum, toleranceSec || DEFAULT_TOLERANCE_SEC)) {
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
reason: "timestamp_drift",
|
|
155
|
+
message: `Slack: timestamp drift exceeds ${toleranceSec || DEFAULT_TOLERANCE_SEC}s tolerance`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const expected = `v0=${hmacHex("sha256", secret, `v0:${ts}:${rawBody}`)}`;
|
|
159
|
+
if (!safeEqualString(sig, expected)) {
|
|
160
|
+
return { ok: false, reason: "signature_mismatch", message: "Slack: signature mismatch" };
|
|
161
|
+
}
|
|
162
|
+
const event = parsedBody ?? {};
|
|
163
|
+
const eventType = typeof event.event?.type === "string" ? event.event.type : "unknown";
|
|
164
|
+
const eventId = typeof event.event_id === "string" ? event.event_id : "";
|
|
165
|
+
return { ok: true, eventId, eventType };
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
export const shopifyVerifier = {
|
|
169
|
+
verify({ headers, rawBody, secret }) {
|
|
170
|
+
if (!secret)
|
|
171
|
+
return { ok: false, reason: "missing_secret", message: "Shopify: secret not configured" };
|
|
172
|
+
const sig = headers["x-shopify-hmac-sha256"];
|
|
173
|
+
if (!sig) {
|
|
174
|
+
return { ok: false, reason: "missing_signature", message: "Shopify: X-Shopify-Hmac-Sha256 header missing" };
|
|
175
|
+
}
|
|
176
|
+
const expected = hmacBase64("sha256", secret, rawBody);
|
|
177
|
+
if (!safeEqualString(sig, expected)) {
|
|
178
|
+
return { ok: false, reason: "signature_mismatch", message: "Shopify: signature mismatch" };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
eventId: headers["x-shopify-webhook-id"] ?? "",
|
|
183
|
+
eventType: headers["x-shopify-topic"] ?? "unknown",
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
export const svixVerifier = {
|
|
188
|
+
verify({ headers, rawBody, parsedBody, secret, toleranceSec }) {
|
|
189
|
+
if (!secret)
|
|
190
|
+
return { ok: false, reason: "missing_secret", message: "Svix: secret not configured" };
|
|
191
|
+
const webhookId = headers["webhook-id"];
|
|
192
|
+
const webhookTs = headers["webhook-timestamp"];
|
|
193
|
+
const webhookSig = headers["webhook-signature"];
|
|
194
|
+
if (!webhookId || !webhookTs || !webhookSig) {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
reason: "missing_signature",
|
|
198
|
+
message: "Svix/Standard Webhooks: missing webhook-id, webhook-timestamp, or webhook-signature header",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const tsNum = Number.parseInt(webhookTs, 10);
|
|
202
|
+
if (!Number.isFinite(tsNum)) {
|
|
203
|
+
return { ok: false, reason: "bad_format", message: "Svix: webhook-timestamp is not numeric" };
|
|
204
|
+
}
|
|
205
|
+
if (!isWithinTolerance(tsNum, toleranceSec || DEFAULT_TOLERANCE_SEC)) {
|
|
206
|
+
return {
|
|
207
|
+
ok: false,
|
|
208
|
+
reason: "timestamp_drift",
|
|
209
|
+
message: `Svix: timestamp drift exceeds ${toleranceSec || DEFAULT_TOLERANCE_SEC}s tolerance`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const signed = `${webhookId}.${webhookTs}.${rawBody}`;
|
|
213
|
+
// Strip optional `whsec_` prefix Svix recommends for secrets.
|
|
214
|
+
const rawSecret = secret.startsWith("whsec_") ? secret.slice("whsec_".length) : secret;
|
|
215
|
+
// Svix encodes the secret as base64 — decode before HMAC.
|
|
216
|
+
const secretBuf = Buffer.from(rawSecret, "base64");
|
|
217
|
+
const expected = createHmac("sha256", secretBuf).update(signed).digest("base64");
|
|
218
|
+
// webhook-signature may include multiple versions: `v1,base64 v1,base64 ...`
|
|
219
|
+
const sigs = webhookSig.split(" ").map((s) => {
|
|
220
|
+
const idx = s.indexOf(",");
|
|
221
|
+
return idx === -1 ? s : s.slice(idx + 1);
|
|
222
|
+
});
|
|
223
|
+
const matched = sigs.some((s) => safeEqualString(s, expected));
|
|
224
|
+
if (!matched) {
|
|
225
|
+
return { ok: false, reason: "signature_mismatch", message: "Svix: signature mismatch" };
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
ok: true,
|
|
229
|
+
eventId: webhookId,
|
|
230
|
+
eventType: getEventTypeFromBody(parsedBody) ?? "unknown",
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
export function buildCustomVerifier(config) {
|
|
235
|
+
const algo = config.scheme === "hmac-sha1" ? "sha1" : config.scheme === "hmac-sha512" ? "sha512" : "sha256";
|
|
236
|
+
const headerLower = config.header.toLowerCase();
|
|
237
|
+
const tsHeaderLower = config.timestampHeader?.toLowerCase();
|
|
238
|
+
return {
|
|
239
|
+
verify({ headers, rawBody, parsedBody, secret, toleranceSec }) {
|
|
240
|
+
if (!secret)
|
|
241
|
+
return { ok: false, reason: "missing_secret", message: `${config.header}: secret not configured` };
|
|
242
|
+
const sig = headers[headerLower];
|
|
243
|
+
if (!sig) {
|
|
244
|
+
return { ok: false, reason: "missing_signature", message: `${config.header}: header missing` };
|
|
245
|
+
}
|
|
246
|
+
let signedString = rawBody;
|
|
247
|
+
if (tsHeaderLower) {
|
|
248
|
+
const ts = headers[tsHeaderLower];
|
|
249
|
+
if (!ts) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
reason: "missing_timestamp",
|
|
253
|
+
message: `${config.timestampHeader}: header missing`,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const tsNum = Number.parseInt(ts, 10);
|
|
257
|
+
if (!Number.isFinite(tsNum)) {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
reason: "bad_format",
|
|
261
|
+
message: `${config.timestampHeader}: not numeric`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (!isWithinTolerance(tsNum, toleranceSec || config.tolerance)) {
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
reason: "timestamp_drift",
|
|
268
|
+
message: `Timestamp drift exceeds ${toleranceSec || config.tolerance}s tolerance`,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
signedString = `${ts}.${rawBody}`;
|
|
272
|
+
}
|
|
273
|
+
const hex = hmacHex(algo, secret, signedString);
|
|
274
|
+
const base64 = hmacBase64(algo, secret, signedString);
|
|
275
|
+
const expected = config.format.replace("{hex}", hex).replace("{base64}", base64);
|
|
276
|
+
if (!safeEqualString(sig, expected)) {
|
|
277
|
+
return { ok: false, reason: "signature_mismatch", message: `${config.header}: signature mismatch` };
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
ok: true,
|
|
281
|
+
eventId: "",
|
|
282
|
+
eventType: getEventTypeFromBody(parsedBody) ?? "unknown",
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export const BUILTIN_VERIFIERS = {
|
|
288
|
+
github: githubVerifier,
|
|
289
|
+
stripe: stripeVerifier,
|
|
290
|
+
slack: slackVerifier,
|
|
291
|
+
shopify: shopifyVerifier,
|
|
292
|
+
svix: svixVerifier,
|
|
293
|
+
};
|
|
294
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyaWZpZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3ZlcmlmaWVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBb0NHO0FBRUgsT0FBTyxFQUFFLFVBQVUsRUFBRSxlQUFlLEVBQUUsTUFBTSxhQUFhLENBQUM7QUF5QzFELE1BQU0scUJBQXFCLEdBQUcsR0FBRyxDQUFDO0FBRWxDLFNBQVMsZUFBZSxDQUFDLENBQVMsRUFBRSxDQUFTO0lBQzVDLE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDNUIsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM1QixJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssSUFBSSxDQUFDLE1BQU07UUFBRSxPQUFPLEtBQUssQ0FBQztJQUM5QyxPQUFPLGVBQWUsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDcEMsQ0FBQztBQUVELFNBQVMsT0FBTyxDQUFDLElBQWtDLEVBQUUsTUFBYyxFQUFFLElBQVk7SUFDaEYsT0FBTyxVQUFVLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDNUQsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLElBQWtDLEVBQUUsTUFBYyxFQUFFLElBQVk7SUFDbkYsT0FBTyxVQUFVLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDL0QsQ0FBQztBQUVELFNBQVMsaUJBQWlCLENBQUMsWUFBb0IsRUFBRSxZQUFvQjtJQUNwRSxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUks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QUFJLENBQUMsZUFBZSxDQUFDLEdBQUcsRUFBRSxRQUFRLENBQUMsRUFBRSxDQUFDO2dCQUNyQyxPQUFPLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsb0JBQW9CLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEVBQUUsQ0FBQztZQUNyRyxDQUFDO1lBQ0QsT0FBTztnQkFDTixFQUFFLEVBQUUsSUFBSTtnQkFDUixPQUFPLEVBQUUsRUFBRTtnQkFDWCxTQUFTLEVBQUUsb0JBQW9CLENBQUMsVUFBVSxDQUFDLElBQUksU0FBUzthQUN4RCxDQUFDO1FBQ0gsQ0FBQztLQUNELENBQUM7QUFDSCxDQUFDO0FBRUQsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQTZCO0lBQzFELE1BQU0sRUFBRSxjQUFjO0lBQ3RCLE1BQU0sRUFBRSxjQUFjO0lBQ3RCLEtBQUssRUFBRSxhQUFhO0lBQ3BCLE9BQU8sRUFBRSxlQUFlO0lBQ3hCLElBQUksRUFBRSxZQUFZO0NBQ2xCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFdlYmhvb2sgc2lnbmF0dXJlIHZlcmlmaWVycyDigJQgb25lIHN0cmF0ZWd5IHBlciBzdXBwb3J0ZWQgcHJvdmlkZXIuXG4gKlxuICogRWFjaCB2ZXJpZmllciByZWFkcyB0aGUgcmF3IHJlcXVlc3QgYnl0ZXMgKE5PVCB0aGUgSlNPTi1wYXJzZWQgYm9keSlcbiAqIGFuZCB0aGUgcHJvdmlkZXItc3BlY2lmaWMgc2lnbmF0dXJlIGhlYWRlciwgY29tcHV0ZXMgdGhlIGV4cGVjdGVkXG4gKiBITUFDIGFnYWluc3QgYSBzaGFyZWQgc2VjcmV0LCBhbmQgY29uc3RhbnQtdGltZSBjb21wYXJlcy4gT24gbWF0Y2gsXG4gKiByZXR1cm5zIGB7IG9rOiB0cnVlLCBldmVudElkLCBldmVudFR5cGUgfWAuIE9uIG1pc21hdGNoIC8gbWlzc2luZ1xuICogaGVhZGVyIC8gZHJpZnQsIHJldHVybnMgYHsgb2s6IGZhbHNlLCByZWFzb24sIG1lc3NhZ2UgfWAuXG4gKlxuICogQ29uc3RhbnQtdGltZSBjb21wYXJpc29uIHZpYSBgY3J5cHRvLnRpbWluZ1NhZmVFcXVhbGAgaXMgbWFuZGF0b3J5IOKAlFxuICogYSBuYWl2ZSBgPT09YCBjb21wYXJlIGxlYWtzIHRoZSBleHBlY3RlZCBITUFDIGJ5dGUgYnkgYnl0ZSB0aHJvdWdoXG4gKiB0aW1pbmcgdmFyaWFuY2UgYW5kIGEgbmV0d29yay1hZGphY2VudCBhdHRhY2tlciBjYW4gcmVjb3ZlciB0aGVcbiAqIHNlY3JldCBpbiB+MjU2IHJlcXVlc3RzIHBlciBieXRlLlxuICpcbiAqIEJ1aWx0LWluIHByb3ZpZGVycyArIHRoZWlyIHNpZ25hdHVyZSBzaGFwZXM6XG4gKlxuICogICAtICoqZ2l0aHViKio6ICAgIGBYLUh1Yi1TaWduYXR1cmUtMjU2OiBzaGEyNTY9PGhleD5gIG92ZXIgcmF3Qm9keS5cbiAqICAgICAgICAgICAgICAgICAgICBFdmVudCBpZCBmcm9tIGBYLUdpdEh1Yi1EZWxpdmVyeWA7IGV2ZW50IHR5cGVcbiAqICAgICAgICAgICAgICAgICAgICBmcm9tIGBYLUdpdEh1Yi1FdmVudGAgaGVhZGVyLlxuICogICAtICoqc3RyaXBlKio6ICAgIGBTdHJpcGUtU2lnbmF0dXJlOiB0PTx0cz4sdjE9PGhleD5gIG92ZXJcbiAqICAgICAgICAgICAgICAgICAgICBgPHRzPi48cmF3Qm9keT5gIHdpdGggYSA1LW1pbnV0ZSBkcmlmdCB3aW5kb3cuXG4gKiAgICAgICAgICAgICAgICAgICAgRXZlbnQgaWQgKyB0eXBlIGZyb20gYm9keS5pZCAvIGJvZHkudHlwZS5cbiAqICAgLSAqKnNsYWNrKio6ICAgICBgWC1TbGFjay1TaWduYXR1cmU6IHYwPTxoZXg+YCBvdmVyXG4gKiAgICAgICAgICAgICAgICAgICAgYHYwOjxYLVNsYWNrLVJlcXVlc3QtVGltZXN0YW1wPjo8cmF3Qm9keT5gIHdpdGggYVxuICogICAgICAgICAgICAgICAgICAgIDUtbWludXRlIGRyaWZ0IHdpbmRvdy4gRXZlbnQgdHlwZSBmcm9tXG4gKiAgICAgICAgICAgICAgICAgICAgYm9keS5ldmVudC50eXBlOyBldmVudCBpZCBmcm9tIGJvZHkuZXZlbnRfaWQuXG4gKiAgIC0gKipzaG9waWZ5Kio6ICAgYFgtU2hvcGlmeS1IbWFjLVNoYTI1NjogPGJhc2U2ND5gIG92ZXIgcmF3Qm9keS5cbiAqICAgICAgICAgICAgICAgICAgICBFdmVudCB0eXBlIGZyb20gYFgtU2hvcGlmeS1Ub3BpY2A7IGV2ZW50IGlkIGZyb21cbiAqICAgICAgICAgICAgICAgICAgICBgWC1TaG9waWZ5LVdlYmhvb2stSWRgLlxuICogICAtICoqc3ZpeCoqOiAgICAgIFN0YW5kYXJkIFdlYmhvb2tzLiBgd2ViaG9vay1zaWduYXR1cmU6XG4gKiAgICAgICAgICAgICAgICAgICAgdjEsPGJhc2U2ND5gIG92ZXIgYDx3ZWJob29rLWlkPi48d2ViaG9vay10aW1lc3RhbXA+LjxyYXdCb2R5PmBcbiAqICAgICAgICAgICAgICAgICAgICB3aXRoIGEgNS1taW51dGUgZHJpZnQgd2luZG93LiBFdmVudCBpZCBmcm9tXG4gKiAgICAgICAgICAgICAgICAgICAgYHdlYmhvb2staWRgOyBldmVudCB0eXBlIGZyb20gYm9keS50eXBlLlxuICpcbiAqIEN1c3RvbSAodW5rbm93biBwcm92aWRlcikgdmVyaWZpZXIgaXMgYnVpbHQgZHluYW1pY2FsbHkgYnlcbiAqIGBidWlsZEN1c3RvbVZlcmlmaWVyKClgIGZyb20gdGhlIHdvcmtmbG93J3MgYHNpZ25hdHVyZWAgY29uZmlnLlxuICovXG5cbmltcG9ydCB7IGNyZWF0ZUhtYWMsIHRpbWluZ1NhZmVFcXVhbCB9IGZyb20gXCJub2RlOmNyeXB0b1wiO1xuXG4vKiogU3VjY2Vzc2Z1bCB2ZXJpZmljYXRpb24gcmVzdWx0IOKAlCB3b3JrZmxvdyBtYXkgcHJvY2VlZC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVmVyaWZ5T2sge1xuXHRvazogdHJ1ZTtcblx0LyoqIFByb3ZpZGVyLXNwZWNpZmljIGV2ZW50IGlkICh1c2VkIGZvciByZXBsYXktcHJvdGVjdGlvbiBjYWNoZSBrZXkpLiAqL1xuXHRldmVudElkOiBzdHJpbmc7XG5cdC8qKiBQcm92aWRlci1zcGVjaWZpYyBldmVudCB0eXBlICh1c2VkIGZvciB0aGUgYWxsb3dsaXN0IGNoZWNrKS4gKi9cblx0ZXZlbnRUeXBlOiBzdHJpbmc7XG59XG5cbi8qKiBWZXJpZmljYXRpb24gZmFpbHVyZSDigJQgdHJpZ2dlciByZXR1cm5zIDQwMSB3aXRoIHN0cnVjdHVyZWQgcmVhc29uLiAqL1xuZXhwb3J0IGludGVyZmFjZSBWZXJpZnlFcnJvciB7XG5cdG9rOiBmYWxzZTtcblx0LyoqIFN0YWJsZSBkaXNjcmltaW5hdG9yIOKAlCBsb2cvYWxlcnQgZGFzaGJvYXJkcyBicmFuY2ggb24gdGhpcy4gKi9cblx0cmVhc29uOlxuXHRcdHwgXCJtaXNzaW5nX3NpZ25hdHVyZVwiXG5cdFx0fCBcIm1pc3NpbmdfdGltZXN0YW1wXCJcblx0XHR8IFwibWlzc2luZ19zZWNyZXRcIlxuXHRcdHwgXCJiYWRfZm9ybWF0XCJcblx0XHR8IFwidGltZXN0YW1wX2RyaWZ0XCJcblx0XHR8IFwic2lnbmF0dXJlX21pc21hdGNoXCI7XG5cdC8qKiBIdW1hbi1yZWFkYWJsZSBlcnJvciBtZXNzYWdlLiBTYWZlIHRvIHN1cmZhY2UgdG8gdGhlIHNlbmRlci4gKi9cblx0bWVzc2FnZTogc3RyaW5nO1xufVxuXG5leHBvcnQgdHlwZSBWZXJpZnlSZXN1bHQgPSBWZXJpZnlPayB8IFZlcmlmeUVycm9yO1xuXG4vKiogSW5wdXRzIGV2ZXJ5IHZlcmlmaWVyIHJlY2VpdmVzLiAqL1xuZXhwb3J0IGludGVyZmFjZSBWZXJpZnlJbnB1dCB7XG5cdGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG5cdHJhd0JvZHk6IHN0cmluZztcblx0cGFyc2VkQm9keTogdW5rbm93bjtcblx0c2VjcmV0OiBzdHJpbmc7XG5cdHRvbGVyYW5jZVNlYzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFZlcmlmaWVyIHtcblx0dmVyaWZ5KGlucHV0OiBWZXJpZnlJbnB1dCk6IFZlcmlmeVJlc3VsdDtcbn1cblxuY29uc3QgREVGQVVMVF9UT0xFUkFOQ0VfU0VDID0gMzAwO1xuXG5mdW5jdGlvbiBzYWZlRXF1YWxTdHJpbmcoYTogc3RyaW5nLCBiOiBzdHJpbmcpOiBib29sZWFuIHtcblx0Y29uc3QgYUJ1ZiA9IEJ1ZmZlci5mcm9tKGEpO1xuXHRjb25zdCBiQnVmID0gQnVmZmVyLmZyb20oYik7XG5cdGlmIChhQnVmLmxlbmd0aCAhPT0gYkJ1Zi5sZW5ndGgpIHJldHVybiBmYWxzZTtcblx0cmV0dXJuIHRpbWluZ1NhZmVFcXVhbChhQnVmLCBiQnVmKTtcbn1cblxuZnVuY3Rpb24gaG1hY0hleChhbGdvOiBcInNoYTI1NlwiIHwgXCJzaGExXCIgfCBcInNoYTUxMlwiLCBzZWNyZXQ6IHN0cmluZywgZGF0YTogc3RyaW5nKTogc3RyaW5nIHtcblx0cmV0dXJuIGNyZWF0ZUhtYWMoYWxnbywgc2VjcmV0KS51cGRhdGUoZGF0YSkuZGlnZXN0KFwiaGV4XCIpO1xufVxuXG5mdW5jdGlvbiBobWFjQmFzZTY0KGFsZ286IFwic2hhMjU2XCIgfCBcInNoYTFcIiB8IFwic2hhNTEyXCIsIHNlY3JldDogc3RyaW5nLCBkYXRhOiBzdHJpbmcpOiBzdHJpbmcge1xuXHRyZXR1cm4gY3JlYXRlSG1hYyhhbGdvLCBzZWNyZXQpLnVwZGF0ZShkYXRhKS5kaWdlc3QoXCJiYXNlNjRcIik7XG59XG5cbmZ1bmN0aW9uIGlzV2l0aGluVG9sZXJhbmNlKHRpbWVzdGFtcFNlYzogbnVtYmVyLCB0b2xlcmFuY2VTZWM6IG51bWJlcik6IGJvb2xlYW4ge1xuXHRjb25zdCBub3dTZWMgPSBNYXRoLmZsb29yKERhdGUubm93KCkgLyAxMDAwKTtcblx0cmV0dXJuIE1hdGguYWJzKG5vd1NlYyAtIHRpbWVzdGFtcFNlYykgPD0gdG9sZXJhbmNlU2VjO1xufVxuXG5mdW5jdGlvbiBnZXRFdmVudFR5cGVGcm9tQm9keShwYXJzZWRCb2R5OiB1bmtub3duLCBrZXkgPSBcInR5cGVcIik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG5cdGlmICghcGFyc2VkQm9keSB8fCB0eXBlb2YgcGFyc2VkQm9keSAhPT0gXCJvYmplY3RcIikgcmV0dXJuIHVuZGVmaW5lZDtcblx0Y29uc3QgdmFsdWUgPSAocGFyc2VkQm9keSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPilba2V5XTtcblx0cmV0dXJuIHR5cGVvZiB2YWx1ZSA9PT0gXCJzdHJpbmdcIiA/IHZhbHVlIDogdW5kZWZpbmVkO1xufVxuXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuLy8gQnVpbHQtaW4gcHJvdmlkZXJzXG4vLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuXG5leHBvcnQgY29uc3QgZ2l0aHViVmVyaWZpZXI6IFZlcmlmaWVyID0ge1xuXHR2ZXJpZnkoeyBoZWFkZXJzLCByYXdCb2R5LCBzZWNyZXQgfSkge1xuXHRcdGlmICghc2VjcmV0KSByZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJtaXNzaW5nX3NlY3JldFwiLCBtZXNzYWdlOiBcIkdpdEh1Yjogc2VjcmV0IG5vdCBjb25maWd1cmVkXCIgfTtcblx0XHRjb25zdCBzaWcgPSBoZWFkZXJzW1wieC1odWItc2lnbmF0dXJlLTI1NlwiXTtcblx0XHRpZiAoIXNpZykgcmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246IFwibWlzc2luZ19zaWduYXR1cmVcIiwgbWVzc2FnZTogXCJHaXRIdWI6IFgtSHViLVNpZ25hdHVyZS0yNTYgaGVhZGVyIG1pc3NpbmdcIiB9O1xuXHRcdGNvbnN0IGV4cGVjdGVkID0gYHNoYTI1Nj0ke2htYWNIZXgoXCJzaGEyNTZcIiwgc2VjcmV0LCByYXdCb2R5KX1gO1xuXHRcdGlmICghc2FmZUVxdWFsU3RyaW5nKHNpZywgZXhwZWN0ZWQpKSB7XG5cdFx0XHRyZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJzaWduYXR1cmVfbWlzbWF0Y2hcIiwgbWVzc2FnZTogXCJHaXRIdWI6IHNpZ25hdHVyZSBtaXNtYXRjaFwiIH07XG5cdFx0fVxuXHRcdHJldHVybiB7XG5cdFx0XHRvazogdHJ1ZSxcblx0XHRcdGV2ZW50SWQ6IGhlYWRlcnNbXCJ4LWdpdGh1Yi1kZWxpdmVyeVwiXSA/PyBcIlwiLFxuXHRcdFx0ZXZlbnRUeXBlOiBoZWFkZXJzW1wieC1naXRodWItZXZlbnRcIl0gPz8gXCJ1bmtub3duXCIsXG5cdFx0fTtcblx0fSxcbn07XG5cbmV4cG9ydCBjb25zdCBzdHJpcGVWZXJpZmllcjogVmVyaWZpZXIgPSB7XG5cdHZlcmlmeSh7IGhlYWRlcnMsIHJhd0JvZHksIHBhcnNlZEJvZHksIHNlY3JldCwgdG9sZXJhbmNlU2VjIH0pIHtcblx0XHRpZiAoIXNlY3JldCkgcmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246IFwibWlzc2luZ19zZWNyZXRcIiwgbWVzc2FnZTogXCJTdHJpcGU6IHNlY3JldCBub3QgY29uZmlndXJlZFwiIH07XG5cdFx0Y29uc3Qgc2lnID0gaGVhZGVyc1tcInN0cmlwZS1zaWduYXR1cmVcIl07XG5cdFx0aWYgKCFzaWcpIHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiBcIm1pc3Npbmdfc2lnbmF0dXJlXCIsIG1lc3NhZ2U6IFwiU3RyaXBlOiBTdHJpcGUtU2lnbmF0dXJlIGhlYWRlciBtaXNzaW5nXCIgfTtcblxuXHRcdC8vIEZvcm1hdDogdD0xMjM0NTY3ODkwLHYxPTxoZXg+LHYwPTxoZXg+LC4uLlxuXHRcdGNvbnN0IHBhcnRzID0gT2JqZWN0LmZyb21FbnRyaWVzKFxuXHRcdFx0c2lnXG5cdFx0XHRcdC5zcGxpdChcIixcIilcblx0XHRcdFx0Lm1hcCgocCkgPT4gcC50cmltKCkpXG5cdFx0XHRcdC5tYXAoKHApID0+IHtcblx0XHRcdFx0XHRjb25zdCBpZHggPSBwLmluZGV4T2YoXCI9XCIpO1xuXHRcdFx0XHRcdHJldHVybiBpZHggPT09IC0xID8gW3AsIFwiXCJdIDogW3Auc2xpY2UoMCwgaWR4KSwgcC5zbGljZShpZHggKyAxKV07XG5cdFx0XHRcdH0pLFxuXHRcdCk7XG5cdFx0Y29uc3QgdHMgPSBwYXJ0cy50O1xuXHRcdGNvbnN0IHYxID0gcGFydHMudjE7XG5cdFx0aWYgKCF0cyB8fCAhdjEpIHtcblx0XHRcdHJldHVybiB7XG5cdFx0XHRcdG9rOiBmYWxzZSxcblx0XHRcdFx0cmVhc29uOiBcImJhZF9mb3JtYXRcIixcblx0XHRcdFx0bWVzc2FnZTogXCJTdHJpcGU6IHNpZ25hdHVyZSBtaXNzaW5nIHQ9IG9yIHYxPSBjb21wb25lbnRcIixcblx0XHRcdH07XG5cdFx0fVxuXHRcdGNvbnN0IHRzTnVtID0gTnVtYmVyLnBhcnNlSW50KHRzLCAxMCk7XG5cdFx0aWYgKCFOdW1iZXIuaXNGaW5pdGUodHNOdW0pKSB7XG5cdFx0XHRyZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJiYWRfZm9ybWF0XCIsIG1lc3NhZ2U6IFwiU3RyaXBlOiB0PSBpcyBub3QgbnVtZXJpY1wiIH07XG5cdFx0fVxuXHRcdGlmICghaXNXaXRoaW5Ub2xlcmFuY2UodHNOdW0sIHRvbGVyYW5jZVNlYyB8fCBERUZBVUxUX1RPTEVSQU5DRV9TRUMpKSB7XG5cdFx0XHRyZXR1cm4ge1xuXHRcdFx0XHRvazogZmFsc2UsXG5cdFx0XHRcdHJlYXNvbjogXCJ0aW1lc3RhbXBfZHJpZnRcIixcblx0XHRcdFx0bWVzc2FnZTogYFN0cmlwZTogdGltZXN0YW1wIGRyaWZ0IGV4Y2VlZHMgJHt0b2xlcmFuY2VTZWMgfHwgREVGQVVMVF9UT0xFUkFOQ0VfU0VDfXMgdG9sZXJhbmNlYCxcblx0XHRcdH07XG5cdFx0fVxuXHRcdGNvbnN0IGV4cGVjdGVkID0gaG1hY0hleChcInNoYTI1NlwiLCBzZWNyZXQsIGAke3RzfS4ke3Jhd0JvZHl9YCk7XG5cdFx0aWYgKCFzYWZlRXF1YWxTdHJpbmcodjEsIGV4cGVjdGVkKSkge1xuXHRcdFx0cmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246IFwic2lnbmF0dXJlX21pc21hdGNoXCIsIG1lc3NhZ2U6IFwiU3RyaXBlOiBzaWduYXR1cmUgbWlzbWF0Y2hcIiB9O1xuXHRcdH1cblx0XHRjb25zdCBldmVudElkID0gKHBhcnNlZEJvZHkgYXMgeyBpZD86IHVua25vd24gfSB8IG51bGwpPy5pZDtcblx0XHRjb25zdCBldmVudFR5cGUgPSBnZXRFdmVudFR5cGVGcm9tQm9keShwYXJzZWRCb2R5KTtcblx0XHRyZXR1cm4ge1xuXHRcdFx0b2s6IHRydWUsXG5cdFx0XHRldmVudElkOiB0eXBlb2YgZXZlbnRJZCA9PT0gXCJzdHJpbmdcIiA/IGV2ZW50SWQgOiBcIlwiLFxuXHRcdFx0ZXZlbnRUeXBlOiBldmVudFR5cGUgPz8gXCJ1bmtub3duXCIsXG5cdFx0fTtcblx0fSxcbn07XG5cbmV4cG9ydCBjb25zdCBzbGFja1ZlcmlmaWVyOiBWZXJpZmllciA9IHtcblx0dmVyaWZ5KHsgaGVhZGVycywgcmF3Qm9keSwgcGFyc2VkQm9keSwgc2VjcmV0LCB0b2xlcmFuY2VTZWMgfSkge1xuXHRcdGlmICghc2VjcmV0KSByZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJtaXNzaW5nX3NlY3JldFwiLCBtZXNzYWdlOiBcIlNsYWNrOiBzZWNyZXQgbm90IGNvbmZpZ3VyZWRcIiB9O1xuXHRcdGNvbnN0IHNpZyA9IGhlYWRlcnNbXCJ4LXNsYWNrLXNpZ25hdHVyZVwiXTtcblx0XHRpZiAoIXNpZykgcmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246IFwibWlzc2luZ19zaWduYXR1cmVcIiwgbWVzc2FnZTogXCJTbGFjazogWC1TbGFjay1TaWduYXR1cmUgaGVhZGVyIG1pc3NpbmdcIiB9O1xuXHRcdGNvbnN0IHRzID0gaGVhZGVyc1tcIngtc2xhY2stcmVxdWVzdC10aW1lc3RhbXBcIl07XG5cdFx0aWYgKCF0cykge1xuXHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0b2s6IGZhbHNlLFxuXHRcdFx0XHRyZWFzb246IFwibWlzc2luZ190aW1lc3RhbXBcIixcblx0XHRcdFx0bWVzc2FnZTogXCJTbGFjazogWC1TbGFjay1SZXF1ZXN0LVRpbWVzdGFtcCBoZWFkZXIgbWlzc2luZ1wiLFxuXHRcdFx0fTtcblx0XHR9XG5cdFx0Y29uc3QgdHNOdW0gPSBOdW1iZXIucGFyc2VJbnQodHMsIDEwKTtcblx0XHRpZiAoIU51bWJlci5pc0Zpbml0ZSh0c051bSkpIHtcblx0XHRcdHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiBcImJhZF9mb3JtYXRcIiwgbWVzc2FnZTogXCJTbGFjazogdGltZXN0YW1wIGlzIG5vdCBudW1lcmljXCIgfTtcblx0XHR9XG5cdFx0aWYgKCFpc1dpdGhpblRvbGVyYW5jZSh0c051bSwgdG9sZXJhbmNlU2VjIHx8IERFRkFVTFRfVE9MRVJBTkNFX1NFQykpIHtcblx0XHRcdHJldHVybiB7XG5cdFx0XHRcdG9rOiBmYWxzZSxcblx0XHRcdFx0cmVhc29uOiBcInRpbWVzdGFtcF9kcmlmdFwiLFxuXHRcdFx0XHRtZXNzYWdlOiBgU2xhY2s6IHRpbWVzdGFtcCBkcmlmdCBleGNlZWRzICR7dG9sZXJhbmNlU2VjIHx8IERFRkFVTFRfVE9MRVJBTkNFX1NFQ31zIHRvbGVyYW5jZWAsXG5cdFx0XHR9O1xuXHRcdH1cblx0XHRjb25zdCBleHBlY3RlZCA9IGB2MD0ke2htYWNIZXgoXCJzaGEyNTZcIiwgc2VjcmV0LCBgdjA6JHt0c306JHtyYXdCb2R5fWApfWA7XG5cdFx0aWYgKCFzYWZlRXF1YWxTdHJpbmcoc2lnLCBleHBlY3RlZCkpIHtcblx0XHRcdHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiBcInNpZ25hdHVyZV9taXNtYXRjaFwiLCBtZXNzYWdlOiBcIlNsYWNrOiBzaWduYXR1cmUgbWlzbWF0Y2hcIiB9O1xuXHRcdH1cblx0XHRjb25zdCBldmVudCA9IChwYXJzZWRCb2R5IGFzIHsgZXZlbnQ/OiB7IHR5cGU/OiB1bmtub3duIH07IGV2ZW50X2lkPzogdW5rbm93biB9IHwgbnVsbCkgPz8ge307XG5cdFx0Y29uc3QgZXZlbnRUeXBlID0gdHlwZW9mIGV2ZW50LmV2ZW50Py50eXBlID09PSBcInN0cmluZ1wiID8gZXZlbnQuZXZlbnQudHlwZSA6IFwidW5rbm93blwiO1xuXHRcdGNvbnN0IGV2ZW50SWQgPSB0eXBlb2YgZXZlbnQuZXZlbnRfaWQgPT09IFwic3RyaW5nXCIgPyBldmVudC5ldmVudF9pZCA6IFwiXCI7XG5cdFx0cmV0dXJuIHsgb2s6IHRydWUsIGV2ZW50SWQsIGV2ZW50VHlwZSB9O1xuXHR9LFxufTtcblxuZXhwb3J0IGNvbnN0IHNob3BpZnlWZXJpZmllcjogVmVyaWZpZXIgPSB7XG5cdHZlcmlmeSh7IGhlYWRlcnMsIHJhd0JvZHksIHNlY3JldCB9KSB7XG5cdFx0aWYgKCFzZWNyZXQpIHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiBcIm1pc3Npbmdfc2VjcmV0XCIsIG1lc3NhZ2U6IFwiU2hvcGlmeTogc2VjcmV0IG5vdCBjb25maWd1cmVkXCIgfTtcblx0XHRjb25zdCBzaWcgPSBoZWFkZXJzW1wieC1zaG9waWZ5LWhtYWMtc2hhMjU2XCJdO1xuXHRcdGlmICghc2lnKSB7XG5cdFx0XHRyZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJtaXNzaW5nX3NpZ25hdHVyZVwiLCBtZXNzYWdlOiBcIlNob3BpZnk6IFgtU2hvcGlmeS1IbWFjLVNoYTI1NiBoZWFkZXIgbWlzc2luZ1wiIH07XG5cdFx0fVxuXHRcdGNvbnN0IGV4cGVjdGVkID0gaG1hY0Jhc2U2NChcInNoYTI1NlwiLCBzZWNyZXQsIHJhd0JvZHkpO1xuXHRcdGlmICghc2FmZUVxdWFsU3RyaW5nKHNpZywgZXhwZWN0ZWQpKSB7XG5cdFx0XHRyZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJzaWduYXR1cmVfbWlzbWF0Y2hcIiwgbWVzc2FnZTogXCJTaG9waWZ5OiBzaWduYXR1cmUgbWlzbWF0Y2hcIiB9O1xuXHRcdH1cblx0XHRyZXR1cm4ge1xuXHRcdFx0b2s6IHRydWUsXG5cdFx0XHRldmVudElkOiBoZWFkZXJzW1wieC1zaG9waWZ5LXdlYmhvb2staWRcIl0gPz8gXCJcIixcblx0XHRcdGV2ZW50VHlwZTogaGVhZGVyc1tcIngtc2hvcGlmeS10b3BpY1wiXSA/PyBcInVua25vd25cIixcblx0XHR9O1xuXHR9LFxufTtcblxuZXhwb3J0IGNvbnN0IHN2aXhWZXJpZmllcjogVmVyaWZpZXIgPSB7XG5cdHZlcmlmeSh7IGhlYWRlcnMsIHJhd0JvZHksIHBhcnNlZEJvZHksIHNlY3JldCwgdG9sZXJhbmNlU2VjIH0pIHtcblx0XHRpZiAoIXNlY3JldCkgcmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246IFwibWlzc2luZ19zZWNyZXRcIiwgbWVzc2FnZTogXCJTdml4OiBzZWNyZXQgbm90IGNvbmZpZ3VyZWRcIiB9O1xuXHRcdGNvbnN0IHdlYmhvb2tJZCA9IGhlYWRlcnNbXCJ3ZWJob29rLWlkXCJdO1xuXHRcdGNvbnN0IHdlYmhvb2tUcyA9IGhlYWRlcnNbXCJ3ZWJob29rLXRpbWVzdGFtcFwiXTtcblx0XHRjb25zdCB3ZWJob29rU2lnID0gaGVhZGVyc1tcIndlYmhvb2stc2lnbmF0dXJlXCJdO1xuXHRcdGlmICghd2ViaG9va0lkIHx8ICF3ZWJob29rVHMgfHwgIXdlYmhvb2tTaWcpIHtcblx0XHRcdHJldHVybiB7XG5cdFx0XHRcdG9rOiBmYWxzZSxcblx0XHRcdFx0cmVhc29uOiBcIm1pc3Npbmdfc2lnbmF0dXJlXCIsXG5cdFx0XHRcdG1lc3NhZ2U6IFwiU3ZpeC9TdGFuZGFyZCBXZWJob29rczogbWlzc2luZyB3ZWJob29rLWlkLCB3ZWJob29rLXRpbWVzdGFtcCwgb3Igd2ViaG9vay1zaWduYXR1cmUgaGVhZGVyXCIsXG5cdFx0XHR9O1xuXHRcdH1cblx0XHRjb25zdCB0c051bSA9IE51bWJlci5wYXJzZUludCh3ZWJob29rVHMsIDEwKTtcblx0XHRpZiAoIU51bWJlci5pc0Zpbml0ZSh0c051bSkpIHtcblx0XHRcdHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiBcImJhZF9mb3JtYXRcIiwgbWVzc2FnZTogXCJTdml4OiB3ZWJob29rLXRpbWVzdGFtcCBpcyBub3QgbnVtZXJpY1wiIH07XG5cdFx0fVxuXHRcdGlmICghaXNXaXRoaW5Ub2xlcmFuY2UodHNOdW0sIHRvbGVyYW5jZVNlYyB8fCBERUZBVUxUX1RPTEVSQU5DRV9TRUMpKSB7XG5cdFx0XHRyZXR1cm4ge1xuXHRcdFx0XHRvazogZmFsc2UsXG5cdFx0XHRcdHJlYXNvbjogXCJ0aW1lc3RhbXBfZHJpZnRcIixcblx0XHRcdFx0bWVzc2FnZTogYFN2aXg6IHRpbWVzdGFtcCBkcmlmdCBleGNlZWRzICR7dG9sZXJhbmNlU2VjIHx8IERFRkFVTFRfVE9MRVJBTkNFX1NFQ31zIHRvbGVyYW5jZWAsXG5cdFx0XHR9O1xuXHRcdH1cblx0XHRjb25zdCBzaWduZWQgPSBgJHt3ZWJob29rSWR9LiR7d2ViaG9va1RzfS4ke3Jhd0JvZHl9YDtcblx0XHQvLyBTdHJpcCBvcHRpb25hbCBgd2hzZWNfYCBwcmVmaXggU3ZpeCByZWNvbW1lbmRzIGZvciBzZWNyZXRzLlxuXHRcdGNvbnN0IHJhd1NlY3JldCA9IHNlY3JldC5zdGFydHNXaXRoKFwid2hzZWNfXCIpID8gc2VjcmV0LnNsaWNlKFwid2hzZWNfXCIubGVuZ3RoKSA6IHNlY3JldDtcblx0XHQvLyBTdml4IGVuY29kZXMgdGhlIHNlY3JldCBhcyBiYXNlNjQg4oCUIGRlY29kZSBiZWZvcmUgSE1BQy5cblx0XHRjb25zdCBzZWNyZXRCdWYgPSBCdWZmZXIuZnJvbShyYXdTZWNyZXQsIFwiYmFzZTY0XCIpO1xuXHRcdGNvbnN0IGV4cGVjdGVkID0gY3JlYXRlSG1hYyhcInNoYTI1NlwiLCBzZWNyZXRCdWYpLnVwZGF0ZShzaWduZWQpLmRpZ2VzdChcImJhc2U2NFwiKTtcblx0XHQvLyB3ZWJob29rLXNpZ25hdHVyZSBtYXkgaW5jbHVkZSBtdWx0aXBsZSB2ZXJzaW9uczogYHYxLGJhc2U2NCB2MSxiYXNlNjQgLi4uYFxuXHRcdGNvbnN0IHNpZ3MgPSB3ZWJob29rU2lnLnNwbGl0KFwiIFwiKS5tYXAoKHMpID0+IHtcblx0XHRcdGNvbnN0IGlkeCA9IHMuaW5kZXhPZihcIixcIik7XG5cdFx0XHRyZXR1cm4gaWR4ID09PSAtMSA/IHMgOiBzLnNsaWNlKGlkeCArIDEpO1xuXHRcdH0pO1xuXHRcdGNvbnN0IG1hdGNoZWQgPSBzaWdzLnNvbWUoKHMpID0+IHNhZmVFcXVhbFN0cmluZyhzLCBleHBlY3RlZCkpO1xuXHRcdGlmICghbWF0Y2hlZCkge1xuXHRcdFx0cmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246IFwic2lnbmF0dXJlX21pc21hdGNoXCIsIG1lc3NhZ2U6IFwiU3ZpeDogc2lnbmF0dXJlIG1pc21hdGNoXCIgfTtcblx0XHR9XG5cdFx0cmV0dXJuIHtcblx0XHRcdG9rOiB0cnVlLFxuXHRcdFx0ZXZlbnRJZDogd2ViaG9va0lkLFxuXHRcdFx0ZXZlbnRUeXBlOiBnZXRFdmVudFR5cGVGcm9tQm9keShwYXJzZWRCb2R5KSA/PyBcInVua25vd25cIixcblx0XHR9O1xuXHR9LFxufTtcblxuLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbi8vIEN1c3RvbSB2ZXJpZmllciDigJQgYnVpbHQgZnJvbSB0aGUgd29ya2Zsb3cncyBgc2lnbmF0dXJlOiB7IC4uLiB9YCBjb25maWdcbi8vID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ3VzdG9tU2lnbmF0dXJlQ29uZmlnIHtcblx0c2NoZW1lOiBcImhtYWMtc2hhMjU2XCIgfCBcImhtYWMtc2hhMVwiIHwgXCJobWFjLXNoYTUxMlwiO1xuXHRoZWFkZXI6IHN0cmluZztcblx0Zm9ybWF0OiBzdHJpbmc7XG5cdHNlY3JldEVudjogc3RyaW5nO1xuXHR0b2xlcmFuY2U6IG51bWJlcjtcblx0dGltZXN0YW1wSGVhZGVyPzogc3RyaW5nO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRDdXN0b21WZXJpZmllcihjb25maWc6IEN1c3RvbVNpZ25hdHVyZUNvbmZpZyk6IFZlcmlmaWVyIHtcblx0Y29uc3QgYWxnbzogXCJzaGEyNTZcIiB8IFwic2hhMVwiIHwgXCJzaGE1MTJcIiA9XG5cdFx0Y29uZmlnLnNjaGVtZSA9PT0gXCJobWFjLXNoYTFcIiA/IFwic2hhMVwiIDogY29uZmlnLnNjaGVtZSA9PT0gXCJobWFjLXNoYTUxMlwiID8gXCJzaGE1MTJcIiA6IFwic2hhMjU2XCI7XG5cdGNvbnN0IGhlYWRlckxvd2VyID0gY29uZmlnLmhlYWRlci50b0xvd2VyQ2FzZSgpO1xuXHRjb25zdCB0c0hlYWRlckxvd2VyID0gY29uZmlnLnRpbWVzdGFtcEhlYWRlcj8udG9Mb3dlckNhc2UoKTtcblxuXHRyZXR1cm4ge1xuXHRcdHZlcmlmeSh7IGhlYWRlcnMsIHJhd0JvZHksIHBhcnNlZEJvZHksIHNlY3JldCwgdG9sZXJhbmNlU2VjIH0pIHtcblx0XHRcdGlmICghc2VjcmV0KSByZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJtaXNzaW5nX3NlY3JldFwiLCBtZXNzYWdlOiBgJHtjb25maWcuaGVhZGVyfTogc2VjcmV0IG5vdCBjb25maWd1cmVkYCB9O1xuXHRcdFx0Y29uc3Qgc2lnID0gaGVhZGVyc1toZWFkZXJMb3dlcl07XG5cdFx0XHRpZiAoIXNpZykge1xuXHRcdFx0XHRyZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogXCJtaXNzaW5nX3NpZ25hdHVyZVwiLCBtZXNzYWdlOiBgJHtjb25maWcuaGVhZGVyfTogaGVhZGVyIG1pc3NpbmdgIH07XG5cdFx0XHR9XG5cblx0XHRcdGxldCBzaWduZWRTdHJpbmcgPSByYXdCb2R5O1xuXHRcdFx0aWYgKHRzSGVhZGVyTG93ZXIpIHtcblx0XHRcdFx0Y29uc3QgdHMgPSBoZWFkZXJzW3RzSGVhZGVyTG93ZXJdO1xuXHRcdFx0XHRpZiAoIXRzKSB7XG5cdFx0XHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0XHRcdG9rOiBmYWxzZSxcblx0XHRcdFx0XHRcdHJlYXNvbjogXCJtaXNzaW5nX3RpbWVzdGFtcFwiLFxuXHRcdFx0XHRcdFx0bWVzc2FnZTogYCR7Y29uZmlnLnRpbWVzdGFtcEhlYWRlcn06IGhlYWRlciBtaXNzaW5nYCxcblx0XHRcdFx0XHR9O1xuXHRcdFx0XHR9XG5cdFx0XHRcdGNvbnN0IHRzTnVtID0gTnVtYmVyLnBhcnNlSW50KHRzLCAxMCk7XG5cdFx0XHRcdGlmICghTnVtYmVyLmlzRmluaXRlKHRzTnVtKSkge1xuXHRcdFx0XHRcdHJldHVybiB7XG5cdFx0XHRcdFx0XHRvazogZmFsc2UsXG5cdFx0XHRcdFx0XHRyZWFzb246IFwiYmFkX2Zvcm1hdFwiLFxuXHRcdFx0XHRcdFx0bWVzc2FnZTogYCR7Y29uZmlnLnRpbWVzdGFtcEhlYWRlcn06IG5vdCBudW1lcmljYCxcblx0XHRcdFx0XHR9O1xuXHRcdFx0XHR9XG5cdFx0XHRcdGlmICghaXNXaXRoaW5Ub2xlcmFuY2UodHNOdW0sIHRvbGVyYW5jZVNlYyB8fCBjb25maWcudG9sZXJhbmNlKSkge1xuXHRcdFx0XHRcdHJldHVybiB7XG5cdFx0XHRcdFx0XHRvazogZmFsc2UsXG5cdFx0XHRcdFx0XHRyZWFzb246IFwidGltZXN0YW1wX2RyaWZ0XCIsXG5cdFx0XHRcdFx0XHRtZXNzYWdlOiBgVGltZXN0YW1wIGRyaWZ0IGV4Y2VlZHMgJHt0b2xlcmFuY2VTZWMgfHwgY29uZmlnLnRvbGVyYW5jZX1zIHRvbGVyYW5jZWAsXG5cdFx0XHRcdFx0fTtcblx0XHRcdFx0fVxuXHRcdFx0XHRzaWduZWRTdHJpbmcgPSBgJHt0c30uJHtyYXdCb2R5fWA7XG5cdFx0XHR9XG5cblx0XHRcdGNvbnN0IGhleCA9IGhtYWNIZXgoYWxnbywgc2VjcmV0LCBzaWduZWRTdHJpbmcpO1xuXHRcdFx0Y29uc3QgYmFzZTY0ID0gaG1hY0Jhc2U2NChhbGdvLCBzZWNyZXQsIHNpZ25lZFN0cmluZyk7XG5cdFx0XHRjb25zdCBleHBlY3RlZCA9IGNvbmZpZy5mb3JtYXQucmVwbGFjZShcIntoZXh9XCIsIGhleCkucmVwbGFjZShcIntiYXNlNjR9XCIsIGJhc2U2NCk7XG5cblx0XHRcdGlmICghc2FmZUVxdWFsU3RyaW5nKHNpZywgZXhwZWN0ZWQpKSB7XG5cdFx0XHRcdHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiBcInNpZ25hdHVyZV9taXNtYXRjaFwiLCBtZXNzYWdlOiBgJHtjb25maWcuaGVhZGVyfTogc2lnbmF0dXJlIG1pc21hdGNoYCB9O1xuXHRcdFx0fVxuXHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0b2s6IHRydWUsXG5cdFx0XHRcdGV2ZW50SWQ6IFwiXCIsXG5cdFx0XHRcdGV2ZW50VHlwZTogZ2V0RXZlbnRUeXBlRnJvbUJvZHkocGFyc2VkQm9keSkgPz8gXCJ1bmtub3duXCIsXG5cdFx0XHR9O1xuXHRcdH0sXG5cdH07XG59XG5cbmV4cG9ydCBjb25zdCBCVUlMVElOX1ZFUklGSUVSUzogUmVjb3JkPHN0cmluZywgVmVyaWZpZXI+ID0ge1xuXHRnaXRodWI6IGdpdGh1YlZlcmlmaWVyLFxuXHRzdHJpcGU6IHN0cmlwZVZlcmlmaWVyLFxuXHRzbGFjazogc2xhY2tWZXJpZmllcixcblx0c2hvcGlmeTogc2hvcGlmeVZlcmlmaWVyLFxuXHRzdml4OiBzdml4VmVyaWZpZXIsXG59O1xuIl19
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-webhook",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Webhook trigger for Blok workflows - supports GitHub, Stripe, Shopify, and custom webhooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
"author": "Deskree Technologies Inc.",
|
|
15
15
|
"license": "Apache-2.0",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@blokjs/helper": "^0.2
|
|
18
|
-
"@blokjs/runner": "^0.2
|
|
19
|
-
"@blokjs/shared": "^0.2
|
|
17
|
+
"@blokjs/helper": "^0.6.2",
|
|
18
|
+
"@blokjs/runner": "^0.6.2",
|
|
19
|
+
"@blokjs/shared": "^0.6.2",
|
|
20
20
|
"@opentelemetry/api": "^1.9.0",
|
|
21
|
+
"hono": "^4.11.7",
|
|
21
22
|
"uuid": "^11.1.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
25
|
+
"@hono/node-server": "^1.19.9",
|
|
24
26
|
"@types/node": "^22.15.21",
|
|
25
27
|
"@types/uuid": "^11.0.0",
|
|
26
28
|
"typescript": "^5.8.3",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.7 PR 4 — full end-to-end webhook trigger integration test.
|
|
3
|
+
*
|
|
4
|
+
* Spins up a real Hono app + `@hono/node-server` with a real
|
|
5
|
+
* WebhookTrigger configured for a GitHub-style provider. Sends a
|
|
6
|
+
* signed POST via native `fetch` and asserts the workflow ran. A
|
|
7
|
+
* second POST with the same delivery id exercises the replay-cache
|
|
8
|
+
* dedup path and expects `{ status: "duplicate" }`.
|
|
9
|
+
*
|
|
10
|
+
* Complements `WebhookTrigger.test.ts` + `verifiers.test.ts` (unit
|
|
11
|
+
* coverage of the public surface).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createHmac } from "node:crypto";
|
|
15
|
+
import type { Server } from "node:http";
|
|
16
|
+
import { NodeMap, RunTracker, WorkflowRegistry, defineNode } from "@blokjs/runner";
|
|
17
|
+
import { serve } from "@hono/node-server";
|
|
18
|
+
import { Hono } from "hono";
|
|
19
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
vi.mock("@opentelemetry/api", () => ({
|
|
23
|
+
trace: {
|
|
24
|
+
getTracer: () => ({
|
|
25
|
+
startActiveSpan: (_name: string, fn: (span: unknown) => unknown) =>
|
|
26
|
+
fn({ setAttribute: vi.fn(), setStatus: vi.fn(), recordException: vi.fn(), end: vi.fn() }),
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
metrics: {
|
|
30
|
+
getMeter: () => ({
|
|
31
|
+
createCounter: () => ({ add: vi.fn() }),
|
|
32
|
+
createHistogram: () => ({ record: vi.fn() }),
|
|
33
|
+
createGauge: () => ({ record: vi.fn() }),
|
|
34
|
+
createObservableGauge: () => ({ addCallback: vi.fn() }),
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
SpanStatusCode: { OK: 0, ERROR: 1 },
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
import WebhookTriggerClass, { _setActiveWebhookTrigger } from "./WebhookTrigger";
|
|
41
|
+
|
|
42
|
+
const TEST_PORT = 4903;
|
|
43
|
+
const SECRET = "shhh-its-a-secret-1234567890";
|
|
44
|
+
|
|
45
|
+
function hmacHex(body: string): string {
|
|
46
|
+
return createHmac("sha256", SECRET).update(body).digest("hex");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleNode = defineNode({
|
|
50
|
+
name: "handle-event",
|
|
51
|
+
description: "test fixture — record the event id + type",
|
|
52
|
+
input: z.object({}).passthrough(),
|
|
53
|
+
output: z.object({ handled: z.boolean(), eventId: z.string() }),
|
|
54
|
+
async execute(ctx) {
|
|
55
|
+
const body = (ctx.request?.body as { delivery_id?: string } | undefined) ?? {};
|
|
56
|
+
return { handled: true, eventId: body.delivery_id ?? "" };
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("WebhookTrigger — v0.7 PR 4 integration (real HTTP)", () => {
|
|
61
|
+
let app: Hono;
|
|
62
|
+
let trigger: InstanceType<typeof WebhookTriggerClass>;
|
|
63
|
+
let httpServer: Server | null = null;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
WorkflowRegistry.resetInstance();
|
|
67
|
+
_setActiveWebhookTrigger(null);
|
|
68
|
+
process.env.GH_SECRET = SECRET;
|
|
69
|
+
app = new Hono();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
afterEach(
|
|
73
|
+
() =>
|
|
74
|
+
new Promise<void>((resolve) => {
|
|
75
|
+
if (trigger) void trigger.stop();
|
|
76
|
+
if (httpServer) {
|
|
77
|
+
httpServer.close(() => {
|
|
78
|
+
httpServer = null;
|
|
79
|
+
WorkflowRegistry.resetInstance();
|
|
80
|
+
_setActiveWebhookTrigger(null);
|
|
81
|
+
process.env.GH_SECRET = undefined;
|
|
82
|
+
resolve();
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
WorkflowRegistry.resetInstance();
|
|
86
|
+
_setActiveWebhookTrigger(null);
|
|
87
|
+
process.env.GH_SECRET = undefined;
|
|
88
|
+
resolve();
|
|
89
|
+
}
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
it("verifies a signed GitHub-style POST, runs the workflow, and dedups replays", async () => {
|
|
94
|
+
const nodes = new NodeMap();
|
|
95
|
+
nodes.addNode("handle-event", handleNode);
|
|
96
|
+
|
|
97
|
+
WorkflowRegistry.getInstance().register({
|
|
98
|
+
name: "gh-events",
|
|
99
|
+
source: "/test/gh.json",
|
|
100
|
+
workflow: {
|
|
101
|
+
name: "gh-events",
|
|
102
|
+
version: "1.0.0",
|
|
103
|
+
trigger: {
|
|
104
|
+
webhook: {
|
|
105
|
+
provider: "github",
|
|
106
|
+
path: "/webhooks/github",
|
|
107
|
+
secretEnv: "GH_SECRET",
|
|
108
|
+
idempotencyKey: "js/ctx.request.headers['x-github-delivery']",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
steps: [{ id: "handle", node: "handle-event", type: "module", inputs: {} }],
|
|
112
|
+
nodes: { handle: { inputs: {} } },
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
trigger = new WebhookTriggerClass(app);
|
|
117
|
+
trigger.setNodeMap({ nodes });
|
|
118
|
+
await trigger.listen();
|
|
119
|
+
|
|
120
|
+
await new Promise<void>((resolve) => {
|
|
121
|
+
httpServer = serve({ fetch: app.fetch, port: TEST_PORT }, () => resolve()) as Server;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const body = JSON.stringify({ ref: "refs/heads/main", delivery_id: "delivery-uuid-9" });
|
|
125
|
+
const sig = `sha256=${hmacHex(body)}`;
|
|
126
|
+
const reqInit = {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
"content-type": "application/json",
|
|
130
|
+
"x-hub-signature-256": sig,
|
|
131
|
+
"x-github-event": "push",
|
|
132
|
+
"x-github-delivery": "delivery-uuid-9",
|
|
133
|
+
},
|
|
134
|
+
body,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// First delivery — should run the workflow.
|
|
138
|
+
const first = await fetch(`http://localhost:${TEST_PORT}/webhooks/github`, reqInit);
|
|
139
|
+
expect(first.status).toBe(200);
|
|
140
|
+
const firstJson = (await first.json()) as { status?: string; eventId?: string };
|
|
141
|
+
expect(firstJson.status).toBe("ok");
|
|
142
|
+
expect(firstJson.eventId).toBe("delivery-uuid-9");
|
|
143
|
+
|
|
144
|
+
// Second delivery with the same delivery id — replay cache should
|
|
145
|
+
// short-circuit with `duplicate` and NOT run the workflow.
|
|
146
|
+
const second = await fetch(`http://localhost:${TEST_PORT}/webhooks/github`, reqInit);
|
|
147
|
+
expect(second.status).toBe(200);
|
|
148
|
+
const secondJson = (await second.json()) as { status?: string; eventId?: string };
|
|
149
|
+
expect(secondJson.status).toBe("duplicate");
|
|
150
|
+
expect(secondJson.eventId).toBe("delivery-uuid-9");
|
|
151
|
+
}, 15_000);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Drain the per-process RunTracker after the suite to keep singletons
|
|
155
|
+
// from leaking into other tests in the same project.
|
|
156
|
+
afterEach(() => {
|
|
157
|
+
try {
|
|
158
|
+
RunTracker.resetInstance();
|
|
159
|
+
} catch {
|
|
160
|
+
/* ignore — older test orderings */
|
|
161
|
+
}
|
|
162
|
+
});
|