@voltro/plugin-webhooks 0.85.0 → 0.86.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/CHANGELOG.md +666 -0
- package/THIRD-PARTY-NOTICES.md +5 -33
- package/dist/autoDisable.js +41 -0
- package/dist/deferredEmit.js +17 -0
- package/dist/deliverWorkflow.js +336 -0
- package/dist/dsl.js +14 -0
- package/dist/encode.js +53 -0
- package/dist/eventAudience.js +14 -0
- package/dist/idempotency.js +49 -0
- package/dist/incoming.js +205 -0
- package/dist/index.js +17 -1304
- package/dist/plugin.js +19 -0
- package/dist/providers/generic.js +19 -0
- package/dist/providers/github.js +16 -0
- package/dist/providers/index.js +5 -86
- package/dist/providers/slack.js +39 -0
- package/dist/providers/stripe.js +30 -0
- package/dist/rateLimit.js +61 -0
- package/dist/retry.js +65 -0
- package/dist/service.js +491 -0
- package/dist/signing.js +145 -0
- package/dist/standardWebhooks.js +88 -0
- package/package.json +7 -7
- package/dist/signing-DeHObNv6.js +0 -234
package/dist/index.js
CHANGED
|
@@ -1,1304 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}], Se = (e = {}) => he({
|
|
19
|
-
name: e.name ? `@voltro/plugin-webhooks#${e.name}` : "@voltro/plugin-webhooks",
|
|
20
|
-
description: "Outgoing webhooks (multi-target, durable-workflow delivery, HMAC signing, retry policies, rate limits) + incoming (signature verification, idempotency, provider templates). Declares wildcard outbound network access because targets are user-configured URLs.",
|
|
21
|
-
permissions: xe,
|
|
22
|
-
egress: M,
|
|
23
|
-
onActivate: (e) => w.sync(() => {
|
|
24
|
-
e.logger.info("webhooks active", { outbound: "network:outbound:* (subscriber-configured target URLs — audit visibility, not a restriction)" });
|
|
25
|
-
})
|
|
26
|
-
}), Ce = (e) => e.webhook !== void 0, N = (e) => {
|
|
27
|
-
let t = e.webhook;
|
|
28
|
-
if (t !== void 0) return {
|
|
29
|
-
_tag: "outgoingEvent",
|
|
30
|
-
id: e.name,
|
|
31
|
-
payload: e.payload,
|
|
32
|
-
...t.description === void 0 ? {} : { description: t.description },
|
|
33
|
-
...t.version === void 0 ? {} : { version: t.version },
|
|
34
|
-
...t.rateLimit === void 0 ? {} : { globalRateLimit: t.rateLimit }
|
|
35
|
-
};
|
|
36
|
-
}, we = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, P = (e) => {
|
|
37
|
-
let t = we.exec(e);
|
|
38
|
-
if (!t) throw Error(`invalid duration literal: ${e}`);
|
|
39
|
-
let n = Number(t[1]), r = t[2] ?? "s";
|
|
40
|
-
switch (r) {
|
|
41
|
-
case "ms": return n;
|
|
42
|
-
case "s": return n * 1e3;
|
|
43
|
-
case "m": return n * 6e4;
|
|
44
|
-
case "h": return n * 36e5;
|
|
45
|
-
case "d": return n * 864e5;
|
|
46
|
-
default: throw Error(`unreachable unit: ${r}`);
|
|
47
|
-
}
|
|
48
|
-
}, F = [
|
|
49
|
-
408,
|
|
50
|
-
425,
|
|
51
|
-
429,
|
|
52
|
-
500,
|
|
53
|
-
502,
|
|
54
|
-
503,
|
|
55
|
-
504
|
|
56
|
-
], Te = (e, t, n) => {
|
|
57
|
-
if (t >= e.maxAttempts || n?.status !== void 0 && !(e.retryOn ?? F).includes(n.status)) return null;
|
|
58
|
-
if (e.honourRetryAfter !== !1 && n?.retryAfterSeconds !== void 0) {
|
|
59
|
-
let t = n.retryAfterSeconds * 1e3, r = P(e.maxDelay), i = Math.min(t, r);
|
|
60
|
-
return {
|
|
61
|
-
delayMs: i,
|
|
62
|
-
baseDelayMs: i
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
let r = P(e.initialDelay), i = P(e.maxDelay), a;
|
|
66
|
-
switch (e.strategy) {
|
|
67
|
-
case "fixed":
|
|
68
|
-
a = r;
|
|
69
|
-
break;
|
|
70
|
-
case "linear":
|
|
71
|
-
a = r * t;
|
|
72
|
-
break;
|
|
73
|
-
case "exponential": a = r * 2 ** (t - 1);
|
|
74
|
-
}
|
|
75
|
-
a = Math.min(a, i);
|
|
76
|
-
let o = a;
|
|
77
|
-
return e.jitter === "full" && (o = Math.random() * a), {
|
|
78
|
-
delayMs: Math.max(1, Math.floor(o)),
|
|
79
|
-
baseDelayMs: a
|
|
80
|
-
};
|
|
81
|
-
}, Ee = () => ({
|
|
82
|
-
strategy: "exponential",
|
|
83
|
-
maxAttempts: 8,
|
|
84
|
-
initialDelay: "5s",
|
|
85
|
-
maxDelay: "1h",
|
|
86
|
-
retryOn: F,
|
|
87
|
-
honourRetryAfter: !0,
|
|
88
|
-
jitter: "full"
|
|
89
|
-
}), De = () => ({
|
|
90
|
-
strategy: "fixed",
|
|
91
|
-
maxAttempts: 120,
|
|
92
|
-
initialDelay: "30s",
|
|
93
|
-
maxDelay: "30s",
|
|
94
|
-
retryOn: F,
|
|
95
|
-
honourRetryAfter: !0,
|
|
96
|
-
jitter: "full"
|
|
97
|
-
}), Oe = class extends pe.Service()("@voltro/webhooks/WebhooksService") {}, ke = 32, Ae = 32, je = () => _e(ke).toString("hex"), I = (e) => e.events ?? (e.event === void 0 ? [] : [e.event]), L = (e, t) => {
|
|
98
|
-
if (e !== void 0 && t?._tag === "standardWebhooks" && !ne(e)) throw new S({
|
|
99
|
-
field: "secret",
|
|
100
|
-
reason: "a standard-webhooks target needs the spec's secret serialization (\"whsec_\" + base64 of 24..64 random bytes) — mint one with generateStandardWebhooksSecret(), or omit `secret` and let subscribe mint it. If the incompatible key is inherited, rotate the endpoint secret and update the receiver before adding the event"
|
|
101
|
-
});
|
|
102
|
-
}, R = (e) => {
|
|
103
|
-
let t;
|
|
104
|
-
try {
|
|
105
|
-
t = new URL(e.url);
|
|
106
|
-
} catch (e) {
|
|
107
|
-
throw new S({
|
|
108
|
-
field: "url",
|
|
109
|
-
reason: `invalid subscriber url: ${e.message}`
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
if (t.protocol !== "http:" && t.protocol !== "https:") throw new S({
|
|
113
|
-
field: "url",
|
|
114
|
-
reason: `url protocol must be http or https, got ${t.protocol}`
|
|
115
|
-
});
|
|
116
|
-
if (process.env.NODE_ENV === "production") try {
|
|
117
|
-
k(e.url);
|
|
118
|
-
} catch (e) {
|
|
119
|
-
throw new S({
|
|
120
|
-
field: "url",
|
|
121
|
-
reason: e.message
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
let n = I(e);
|
|
125
|
-
if (n.length === 0) throw new S({
|
|
126
|
-
field: "event",
|
|
127
|
-
reason: "event id required"
|
|
128
|
-
});
|
|
129
|
-
if (e.event !== void 0 && e.events !== void 0) throw new S({
|
|
130
|
-
field: "event",
|
|
131
|
-
reason: "pass `event` OR `events`, not both — the row set would be ambiguous"
|
|
132
|
-
});
|
|
133
|
-
if (n.some((e) => e.length === 0)) throw new S({
|
|
134
|
-
field: "events",
|
|
135
|
-
reason: "event id required"
|
|
136
|
-
});
|
|
137
|
-
if (new Set(n).size !== n.length) throw new S({
|
|
138
|
-
field: "events",
|
|
139
|
-
reason: "duplicate event — one row per event, so a repeat would deliver twice"
|
|
140
|
-
});
|
|
141
|
-
if (e.secret !== void 0 && e.secret.length < Ae) throw new S({
|
|
142
|
-
field: "secret",
|
|
143
|
-
reason: `secret must be at least ${Ae} chars`
|
|
144
|
-
});
|
|
145
|
-
if (L(e.secret, e.signing), e.retry !== void 0) {
|
|
146
|
-
if (e.retry.maxAttempts < 1) throw new S({
|
|
147
|
-
field: "retry",
|
|
148
|
-
reason: "retry.maxAttempts must be ≥ 1"
|
|
149
|
-
});
|
|
150
|
-
if (e.retry.maxAttempts > 1e3) throw new S({
|
|
151
|
-
field: "retry",
|
|
152
|
-
reason: "retry.maxAttempts must be ≤ 1000"
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
if (e.rateLimitPerMinute !== void 0 && e.rateLimitPerMinute < 1) throw new S({
|
|
156
|
-
field: "rateLimitPerMinute",
|
|
157
|
-
reason: "rateLimitPerMinute must be ≥ 1"
|
|
158
|
-
});
|
|
159
|
-
if (e.autoDisableAfter !== void 0 && (e.autoDisableAfter < 1 || !Number.isInteger(e.autoDisableAfter))) throw new S({
|
|
160
|
-
field: "autoDisableAfter",
|
|
161
|
-
reason: `autoDisableAfter must be a positive integer, got ${e.autoDisableAfter}`
|
|
162
|
-
});
|
|
163
|
-
}, z = (e, t, n, r) => {
|
|
164
|
-
let a = e.signing ?? t?.defaultSigning ?? ee(), o = a._tag === "standardWebhooks" ? i : je, s = r ?? e.secret ?? o();
|
|
165
|
-
return L(s, a), {
|
|
166
|
-
id: E(),
|
|
167
|
-
tenantId: e.tenantId ?? null,
|
|
168
|
-
event: n ?? e.event ?? "",
|
|
169
|
-
url: e.url,
|
|
170
|
-
secret: s,
|
|
171
|
-
signing: a,
|
|
172
|
-
retry: e.retry ?? t?.defaultRetry ?? Ee(),
|
|
173
|
-
filter: e.filter ?? null,
|
|
174
|
-
scope: e.scope ?? null,
|
|
175
|
-
headers: e.headers ?? null,
|
|
176
|
-
rateLimitPerMinute: e.rateLimitPerMinute ?? null,
|
|
177
|
-
active: !0,
|
|
178
|
-
format: e.format ?? "json",
|
|
179
|
-
autoDisableAfter: e.autoDisableAfter ?? null,
|
|
180
|
-
consecutiveFailures: 0,
|
|
181
|
-
autoDisabledAt: null,
|
|
182
|
-
autoDisableReason: null,
|
|
183
|
-
payloadVersion: e.payloadVersion ?? t?.version ?? 1,
|
|
184
|
-
description: e.description ?? null
|
|
185
|
-
};
|
|
186
|
-
}, Me = (e) => e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : 0, B = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : null, Ne = (e) => {
|
|
187
|
-
if (typeof e != "string") return e;
|
|
188
|
-
try {
|
|
189
|
-
return JSON.parse(e);
|
|
190
|
-
} catch {
|
|
191
|
-
return e;
|
|
192
|
-
}
|
|
193
|
-
}, V = (e) => ({
|
|
194
|
-
id: String(e.id),
|
|
195
|
-
deliveryId: String(e.deliveryId),
|
|
196
|
-
targetId: String(e.targetId),
|
|
197
|
-
event: String(e.event),
|
|
198
|
-
eventId: e.eventId ?? null,
|
|
199
|
-
attempt: Number(e.attempt ?? 0),
|
|
200
|
-
status: e.status,
|
|
201
|
-
responseStatus: e.responseStatus ?? null,
|
|
202
|
-
errorMessage: e.errorMessage ?? null,
|
|
203
|
-
latencyMs: e.latencyMs ?? null,
|
|
204
|
-
scheduledAt: B(e.scheduledAt),
|
|
205
|
-
nextAttemptAt: B(e.nextAttemptAt),
|
|
206
|
-
createdAt: B(e.createdAt)
|
|
207
|
-
}), Pe = (e, t) => e === t ? "current" : e < t ? "behind" : "ahead", H = (e, t) => {
|
|
208
|
-
if (e === null) return !0;
|
|
209
|
-
let n = { payload: t };
|
|
210
|
-
for (let [t, r] of Object.entries(e)) if (!Ie(Fe(n, t), r)) return !1;
|
|
211
|
-
return !0;
|
|
212
|
-
}, Fe = (e, t) => {
|
|
213
|
-
let n = t.split("."), r = e;
|
|
214
|
-
for (let e of n) {
|
|
215
|
-
if (typeof r != "object" || !r) return;
|
|
216
|
-
r = r[e];
|
|
217
|
-
}
|
|
218
|
-
return r;
|
|
219
|
-
}, U = (e, t) => {
|
|
220
|
-
if (typeof e == "number" && typeof t == "number") return e - t;
|
|
221
|
-
if (typeof e == "string" && typeof t == "string") return e < t ? -1 : +(e > t);
|
|
222
|
-
}, Ie = (e, t) => {
|
|
223
|
-
if (typeof t == "object" && t && !Array.isArray(t)) {
|
|
224
|
-
let n = t;
|
|
225
|
-
return "eq" in n && e !== n.eq || "in" in n && (!Array.isArray(n.in) || !n.in.includes(e)) || "gt" in n && !((U(e, n.gt) ?? 0) > 0) || "gte" in n && !((U(e, n.gte) ?? -1) >= 0) || "lt" in n && !((U(e, n.lt) ?? 0) < 0) || "lte" in n && !((U(e, n.lte) ?? 1) <= 0) ? !1 : [
|
|
226
|
-
"eq",
|
|
227
|
-
"in",
|
|
228
|
-
"gt",
|
|
229
|
-
"gte",
|
|
230
|
-
"lt",
|
|
231
|
-
"lte"
|
|
232
|
-
].some((e) => e in n);
|
|
233
|
-
}
|
|
234
|
-
return e === t;
|
|
235
|
-
}, W = (e, t) => e !== null && Object.entries(t).every(([t, n]) => e[t] === n), G = 500, Le = 200, K = async (e, t) => {
|
|
236
|
-
let n = [];
|
|
237
|
-
for (let r = 0; r < Le; r += 1) {
|
|
238
|
-
let i = await e.store.query({
|
|
239
|
-
table: "_voltro_webhook_targets",
|
|
240
|
-
predicate: t,
|
|
241
|
-
order: [{
|
|
242
|
-
column: "id",
|
|
243
|
-
direction: "asc"
|
|
244
|
-
}],
|
|
245
|
-
take: G,
|
|
246
|
-
skip: r === 0 ? void 0 : r * G,
|
|
247
|
-
projection: void 0
|
|
248
|
-
});
|
|
249
|
-
if (n.push(...i), i.length < G) return n;
|
|
250
|
-
}
|
|
251
|
-
throw Error(`webhooks: more than ${Le * G} rows in \`_voltro_webhook_targets\` — refusing to answer a scope lookup from a partial scan. A truncated scan mints a fresh secret for a live endpoint (breaking its signatures) or reports that a real scope matches nothing.`);
|
|
252
|
-
}, Re = async (e, t) => {
|
|
253
|
-
let n = (await K(e)).filter((e) => W(e.scope, t));
|
|
254
|
-
if (n.length === 0) return;
|
|
255
|
-
let r = new Set(n.map((e) => e.secret));
|
|
256
|
-
if (r.size > 1) throw Error(`webhooks.subscribe: the scope ${JSON.stringify(t)} matches ${n.length} targets with ${r.size} different secrets, so it is not one endpoint. Pass \`secret\` explicitly, or narrow the scope.`);
|
|
257
|
-
return [...r][0];
|
|
258
|
-
}, ze = async (e, t) => {
|
|
259
|
-
try {
|
|
260
|
-
let n = await e.store.query({
|
|
261
|
-
table: "_voltro_webhook_event_stats",
|
|
262
|
-
predicate: O("event", t),
|
|
263
|
-
order: [],
|
|
264
|
-
take: 1,
|
|
265
|
-
skip: void 0,
|
|
266
|
-
projection: void 0
|
|
267
|
-
}), r = /* @__PURE__ */ new Date(), i = n[0];
|
|
268
|
-
if (i === void 0) {
|
|
269
|
-
await e.store.insert("_voltro_webhook_event_stats", {
|
|
270
|
-
event: t,
|
|
271
|
-
emitCount: 1,
|
|
272
|
-
lastEmitAt: r,
|
|
273
|
-
createdAt: r,
|
|
274
|
-
updatedAt: r
|
|
275
|
-
});
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
await e.store.update("_voltro_webhook_event_stats", i.id, {
|
|
279
|
-
emitCount: (i.emitCount ?? 0) + 1,
|
|
280
|
-
lastEmitAt: r,
|
|
281
|
-
updatedAt: r
|
|
282
|
-
});
|
|
283
|
-
} catch {}
|
|
284
|
-
}, Be = (e, t, n = {}) => {
|
|
285
|
-
let r = new Map((n.events ?? []).map((e) => [e.id, e])), a = {
|
|
286
|
-
subscribe: async (t) => {
|
|
287
|
-
R(t);
|
|
288
|
-
let n = I(t), a = t.secret === void 0 && t.scope !== void 0 ? await Re(e, t.scope) : void 0, o = t.secret ?? a ?? i(), s = n.map((e) => z(t, r.get(e), e, o));
|
|
289
|
-
for (let t of s) await e.store.insert("_voltro_webhook_targets", {
|
|
290
|
-
...t,
|
|
291
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
292
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
293
|
-
});
|
|
294
|
-
let c = s[0];
|
|
295
|
-
return {
|
|
296
|
-
id: c.id,
|
|
297
|
-
event: c.event,
|
|
298
|
-
url: c.url,
|
|
299
|
-
secret: c.secret,
|
|
300
|
-
signing: c.signing,
|
|
301
|
-
retry: c.retry,
|
|
302
|
-
...s.length > 1 ? { targets: s.map((e) => ({
|
|
303
|
-
id: e.id,
|
|
304
|
-
event: e.event
|
|
305
|
-
})) } : {}
|
|
306
|
-
};
|
|
307
|
-
},
|
|
308
|
-
emit: async (n, i, a) => {
|
|
309
|
-
let o = typeof n == "object" && n.kind === "event" ? N(n) : void 0, s = typeof n == "string" ? r.get(n) : o ?? n, c = typeof n == "string" ? n : n.id ?? n.name;
|
|
310
|
-
if (s !== void 0) {
|
|
311
|
-
let e = T.decodeUnknownResult(s.payload)(i, { errors: "all" });
|
|
312
|
-
if (me.isFailure(e)) throw new se({
|
|
313
|
-
event: c,
|
|
314
|
-
issues: e.failure.message
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
let l = a?.eventId ?? E(), u = a?.tenantId, d = await K(e, u == null ? O("event", c) : D(O("event", c), O("tenantId", u)));
|
|
318
|
-
await ze(e, c);
|
|
319
|
-
let f = d.filter((e) => H(e.filter, i)), p = [], m = JSON.stringify(i), h = async (n, r) => {
|
|
320
|
-
let i = E();
|
|
321
|
-
n.active ? (await t({
|
|
322
|
-
deliveryId: i,
|
|
323
|
-
targetId: n.id,
|
|
324
|
-
event: c,
|
|
325
|
-
eventId: l,
|
|
326
|
-
payloadJson: m
|
|
327
|
-
}), p[r] = {
|
|
328
|
-
targetId: n.id,
|
|
329
|
-
deliveryId: i,
|
|
330
|
-
status: "dispatched"
|
|
331
|
-
}) : (await e.store.insert("_voltro_webhook_deliveries", {
|
|
332
|
-
id: `${i}:1`,
|
|
333
|
-
deliveryId: i,
|
|
334
|
-
targetId: n.id,
|
|
335
|
-
tenantId: n.tenantId ?? null,
|
|
336
|
-
event: c,
|
|
337
|
-
eventId: l,
|
|
338
|
-
attempt: 1,
|
|
339
|
-
status: "pending",
|
|
340
|
-
payload: m,
|
|
341
|
-
scheduledAt: /* @__PURE__ */ new Date(),
|
|
342
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
343
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
344
|
-
}), p[r] = {
|
|
345
|
-
targetId: n.id,
|
|
346
|
-
deliveryId: i,
|
|
347
|
-
status: "queued"
|
|
348
|
-
});
|
|
349
|
-
}, g = 0, _ = !1, v;
|
|
350
|
-
if (await Promise.all(Array.from({ length: Math.min(4, f.length) }, async () => {
|
|
351
|
-
for (; !_ && g < f.length;) {
|
|
352
|
-
let e = g++;
|
|
353
|
-
try {
|
|
354
|
-
await h(f[e], e);
|
|
355
|
-
} catch (e) {
|
|
356
|
-
_ || (_ = !0, v = e);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
})), _) throw v;
|
|
360
|
-
return {
|
|
361
|
-
eventId: l,
|
|
362
|
-
deliveries: p
|
|
363
|
-
};
|
|
364
|
-
},
|
|
365
|
-
replay: async (n) => {
|
|
366
|
-
let r = (await e.store.query({
|
|
367
|
-
table: "_voltro_webhook_deliveries",
|
|
368
|
-
predicate: O("deliveryId", n),
|
|
369
|
-
order: [{
|
|
370
|
-
column: "attempt",
|
|
371
|
-
direction: "asc"
|
|
372
|
-
}],
|
|
373
|
-
take: 1,
|
|
374
|
-
skip: void 0,
|
|
375
|
-
projection: void 0
|
|
376
|
-
}))[0];
|
|
377
|
-
if (r === void 0) throw new oe({ deliveryId: n });
|
|
378
|
-
await t({
|
|
379
|
-
deliveryId: r.deliveryId,
|
|
380
|
-
targetId: r.targetId,
|
|
381
|
-
event: r.event,
|
|
382
|
-
eventId: r.eventId ?? E(),
|
|
383
|
-
payloadJson: typeof r.payload == "string" ? r.payload : JSON.stringify(r.payload),
|
|
384
|
-
attemptEpoch: Date.now()
|
|
385
|
-
});
|
|
386
|
-
},
|
|
387
|
-
resolveTargets: async (t) => {
|
|
388
|
-
if (typeof t == "string") {
|
|
389
|
-
if ((await e.store.query({
|
|
390
|
-
table: "_voltro_webhook_targets",
|
|
391
|
-
predicate: O("id", t),
|
|
392
|
-
order: [],
|
|
393
|
-
take: 1,
|
|
394
|
-
skip: void 0,
|
|
395
|
-
projection: ["id"]
|
|
396
|
-
})).length === 0) throw new C({ targetId: t });
|
|
397
|
-
return [t];
|
|
398
|
-
}
|
|
399
|
-
let n = (await K(e)).filter((e) => W(e.scope, t.scope)).map((e) => e.id);
|
|
400
|
-
if (n.length === 0) throw Error(`webhooks: no target matches scope ${JSON.stringify(t.scope)} — the operation would have silently done nothing`);
|
|
401
|
-
return n;
|
|
402
|
-
},
|
|
403
|
-
pauseTarget: async (t) => {
|
|
404
|
-
for (let n of await a.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", n, {
|
|
405
|
-
active: !1,
|
|
406
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
407
|
-
});
|
|
408
|
-
},
|
|
409
|
-
resumeTarget: async (n) => {
|
|
410
|
-
for (let r of await a.resolveTargets(n)) {
|
|
411
|
-
await e.store.update("_voltro_webhook_targets", r, {
|
|
412
|
-
active: !0,
|
|
413
|
-
consecutiveFailures: 0,
|
|
414
|
-
autoDisabledAt: null,
|
|
415
|
-
autoDisableReason: null,
|
|
416
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
417
|
-
});
|
|
418
|
-
let n = await e.store.query({
|
|
419
|
-
table: "_voltro_webhook_deliveries",
|
|
420
|
-
predicate: D(O("targetId", r), O("status", "pending")),
|
|
421
|
-
order: [{
|
|
422
|
-
column: "createdAt",
|
|
423
|
-
direction: "asc"
|
|
424
|
-
}],
|
|
425
|
-
take: 1e4,
|
|
426
|
-
skip: void 0,
|
|
427
|
-
projection: void 0
|
|
428
|
-
}), i = Date.now();
|
|
429
|
-
for (let e of n) await t({
|
|
430
|
-
deliveryId: e.deliveryId,
|
|
431
|
-
targetId: r,
|
|
432
|
-
event: e.event,
|
|
433
|
-
eventId: e.eventId ?? E(),
|
|
434
|
-
payloadJson: typeof e.payload == "string" ? e.payload : JSON.stringify(e.payload),
|
|
435
|
-
attemptEpoch: i
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
deleteTarget: async (t) => {
|
|
440
|
-
for (let n of await a.resolveTargets(t)) await e.store.delete("_voltro_webhook_targets", n), await e.store.deleteMany("_voltro_webhook_deliveries", { where: D(O("targetId", n), O("status", "pending")) });
|
|
441
|
-
},
|
|
442
|
-
rotateSecret: async (t) => {
|
|
443
|
-
let n = i();
|
|
444
|
-
for (let r of await a.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", r, {
|
|
445
|
-
secret: n,
|
|
446
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
447
|
-
});
|
|
448
|
-
return { secret: n };
|
|
449
|
-
},
|
|
450
|
-
updateTarget: async (t, n) => {
|
|
451
|
-
if (n.url !== void 0 && R({
|
|
452
|
-
event: "x",
|
|
453
|
-
url: n.url
|
|
454
|
-
}), n.rateLimitPerMinute !== void 0 && n.rateLimitPerMinute !== null && (!Number.isInteger(n.rateLimitPerMinute) || n.rateLimitPerMinute < 1)) throw Error("updateTarget: rateLimitPerMinute must be a positive integer or null");
|
|
455
|
-
let r = { updatedAt: /* @__PURE__ */ new Date() };
|
|
456
|
-
for (let e of [
|
|
457
|
-
"url",
|
|
458
|
-
"description",
|
|
459
|
-
"filter",
|
|
460
|
-
"scope",
|
|
461
|
-
"headers",
|
|
462
|
-
"rateLimitPerMinute",
|
|
463
|
-
"autoDisableAfter",
|
|
464
|
-
"format"
|
|
465
|
-
]) n[e] !== void 0 && (r[e] = n[e]);
|
|
466
|
-
for (let n of await a.resolveTargets(t)) await e.store.update("_voltro_webhook_targets", n, r);
|
|
467
|
-
},
|
|
468
|
-
testTarget: async (n, r) => {
|
|
469
|
-
let i = (await e.store.query({
|
|
470
|
-
table: "_voltro_webhook_targets",
|
|
471
|
-
predicate: O("id", n),
|
|
472
|
-
order: [],
|
|
473
|
-
take: 1,
|
|
474
|
-
skip: void 0,
|
|
475
|
-
projection: void 0
|
|
476
|
-
}))[0];
|
|
477
|
-
if (i === void 0) throw new C({ targetId: n });
|
|
478
|
-
let a = E(), o = JSON.stringify(r ?? {
|
|
479
|
-
test: !0,
|
|
480
|
-
targetId: n,
|
|
481
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
482
|
-
});
|
|
483
|
-
return i.active ? await t({
|
|
484
|
-
deliveryId: a,
|
|
485
|
-
targetId: i.id,
|
|
486
|
-
event: i.event,
|
|
487
|
-
eventId: E(),
|
|
488
|
-
payloadJson: o
|
|
489
|
-
}) : await e.store.insert("_voltro_webhook_deliveries", {
|
|
490
|
-
id: `${a}:1`,
|
|
491
|
-
deliveryId: a,
|
|
492
|
-
targetId: i.id,
|
|
493
|
-
tenantId: i.tenantId ?? null,
|
|
494
|
-
event: i.event,
|
|
495
|
-
eventId: null,
|
|
496
|
-
attempt: 1,
|
|
497
|
-
status: "pending",
|
|
498
|
-
payload: o,
|
|
499
|
-
scheduledAt: /* @__PURE__ */ new Date(),
|
|
500
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
501
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
502
|
-
}), { deliveryId: a };
|
|
503
|
-
},
|
|
504
|
-
listDeliveries: async (t) => {
|
|
505
|
-
let n = t?.scope === void 0 ? void 0 : new Set(await a.resolveTargets({ scope: t.scope })), r = await e.store.query({
|
|
506
|
-
table: "_voltro_webhook_deliveries",
|
|
507
|
-
predicate: t?.targetId === void 0 ? void 0 : O("targetId", t.targetId),
|
|
508
|
-
order: [{
|
|
509
|
-
column: "createdAt",
|
|
510
|
-
direction: "desc"
|
|
511
|
-
}],
|
|
512
|
-
take: Math.min((t?.limit ?? 200) * (t?.status !== void 0 || t?.since !== void 0 ? 10 : 1), 5e3),
|
|
513
|
-
skip: void 0,
|
|
514
|
-
projection: void 0
|
|
515
|
-
}), i = t?.since?.getTime();
|
|
516
|
-
return r.filter((e) => n === void 0 || n.has(String(e.targetId))).filter((e) => t?.status === void 0 || e.status === t.status).filter((e) => i === void 0 || Me(e.createdAt) >= i).slice(0, t?.limit ?? 200).map(V);
|
|
517
|
-
},
|
|
518
|
-
getDelivery: async (t) => {
|
|
519
|
-
let n = (await e.store.query({
|
|
520
|
-
table: "_voltro_webhook_deliveries",
|
|
521
|
-
predicate: O("id", t),
|
|
522
|
-
order: [],
|
|
523
|
-
take: 1,
|
|
524
|
-
skip: void 0,
|
|
525
|
-
projection: void 0
|
|
526
|
-
}))[0];
|
|
527
|
-
return n === void 0 ? null : {
|
|
528
|
-
...V(n),
|
|
529
|
-
payload: Ne(n.payload),
|
|
530
|
-
responseBody: n.responseBody ?? null
|
|
531
|
-
};
|
|
532
|
-
},
|
|
533
|
-
updateTargetPayloadVersion: async (t, n) => {
|
|
534
|
-
if (n < 1 || !Number.isInteger(n)) throw new x({ version: n });
|
|
535
|
-
await a.resolveTargets(t), await e.store.update("_voltro_webhook_targets", t, {
|
|
536
|
-
payloadVersion: n,
|
|
537
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
538
|
-
});
|
|
539
|
-
},
|
|
540
|
-
listTargets: async (t, n) => {
|
|
541
|
-
let r = await e.store.query({
|
|
542
|
-
table: "_voltro_webhook_targets",
|
|
543
|
-
predicate: t === void 0 ? void 0 : O("event", t),
|
|
544
|
-
order: [{
|
|
545
|
-
column: "createdAt",
|
|
546
|
-
direction: "desc"
|
|
547
|
-
}],
|
|
548
|
-
take: 500,
|
|
549
|
-
skip: void 0,
|
|
550
|
-
projection: void 0
|
|
551
|
-
});
|
|
552
|
-
return (n === void 0 ? r : r.filter((e) => e.scope !== null && Object.entries(n).every(([t, n]) => e.scope?.[t] === n))).map((e) => ({
|
|
553
|
-
id: e.id,
|
|
554
|
-
event: e.event,
|
|
555
|
-
url: e.url,
|
|
556
|
-
active: e.active,
|
|
557
|
-
rateLimitPerMinute: e.rateLimitPerMinute,
|
|
558
|
-
description: e.description,
|
|
559
|
-
payloadVersion: e.payloadVersion,
|
|
560
|
-
autoDisableAfter: e.autoDisableAfter,
|
|
561
|
-
consecutiveFailures: e.consecutiveFailures ?? 0,
|
|
562
|
-
autoDisabledAt: e.autoDisabledAt === null || e.autoDisabledAt === void 0 ? null : e.autoDisabledAt instanceof Date ? e.autoDisabledAt.toISOString() : String(e.autoDisabledAt),
|
|
563
|
-
autoDisableReason: e.autoDisableReason,
|
|
564
|
-
scope: e.scope,
|
|
565
|
-
filter: e.filter ?? null
|
|
566
|
-
}));
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
return a;
|
|
570
|
-
}, Ve = w.gen(function* () {
|
|
571
|
-
return yield* Oe;
|
|
572
|
-
}), He = (e) => {
|
|
573
|
-
if (e.webhooks === void 0) throw Error("useWebhooks: `ctx.webhooks` is not set. Declare an outgoing event with a `webhook:` block in a *.event.ts file, or an incoming endpoint in a *.webhook.tsx file — the service is wired the moment the app has either.");
|
|
574
|
-
return e.webhooks;
|
|
575
|
-
}, Ue = 1e4, We = class {
|
|
576
|
-
capacity;
|
|
577
|
-
cache = /* @__PURE__ */ new Map();
|
|
578
|
-
constructor(e = Ue) {
|
|
579
|
-
this.capacity = e;
|
|
580
|
-
}
|
|
581
|
-
evictExpired() {
|
|
582
|
-
let e = Date.now();
|
|
583
|
-
for (let [t, n] of this.cache) n.expiresAt < e && this.cache.delete(t);
|
|
584
|
-
}
|
|
585
|
-
claim(e, t) {
|
|
586
|
-
let n = this.cache.get(e);
|
|
587
|
-
if (n !== void 0 && n.expiresAt > Date.now()) return this.cache.delete(e), this.cache.set(e, n), n.value === "inflight" ? "inflight" : "duplicate";
|
|
588
|
-
if (this.cache.set(e, {
|
|
589
|
-
value: "inflight",
|
|
590
|
-
expiresAt: Date.now() + t
|
|
591
|
-
}), this.cache.size > this.capacity) {
|
|
592
|
-
let e = this.cache.keys().next().value;
|
|
593
|
-
e !== void 0 && this.cache.delete(e);
|
|
594
|
-
}
|
|
595
|
-
return "fresh";
|
|
596
|
-
}
|
|
597
|
-
commit(e) {
|
|
598
|
-
let t = this.cache.get(e);
|
|
599
|
-
t !== void 0 && this.cache.set(e, {
|
|
600
|
-
value: "processed",
|
|
601
|
-
expiresAt: t.expiresAt
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
release(e) {
|
|
605
|
-
this.cache.delete(e);
|
|
606
|
-
}
|
|
607
|
-
size() {
|
|
608
|
-
return this.evictExpired(), this.cache.size;
|
|
609
|
-
}
|
|
610
|
-
clear() {
|
|
611
|
-
this.cache.clear();
|
|
612
|
-
}
|
|
613
|
-
}, q = null, Ge = () => (q === null && (q = new We()), q), J = {
|
|
614
|
-
"5m": 3e5,
|
|
615
|
-
"1h": 36e5,
|
|
616
|
-
"6h": 216e5,
|
|
617
|
-
"1d": 864e5,
|
|
618
|
-
"7d": 6048e5,
|
|
619
|
-
"30d": 2592e6
|
|
620
|
-
}, Ke = (e) => e === void 0 ? J["7d"] : J[e] ?? J["7d"], Y = (e, t) => ({
|
|
621
|
-
status: e,
|
|
622
|
-
contentType: "application/json; charset=utf-8",
|
|
623
|
-
body: JSON.stringify(t)
|
|
624
|
-
}), qe = "voltroWebhookVerification", Je = class extends Error {
|
|
625
|
-
webhookId;
|
|
626
|
-
name = "UnverifiedIncomingWebhook";
|
|
627
|
-
constructor(e) {
|
|
628
|
-
super(`incoming webhook '${e}' declares no verification. It would be mounted as a public, unauthenticated POST that runs your application code, so the framework refuses to mount it. Pick one on the descriptor:\n · provider: stripeWebhookProvider() — a preset (HMAC scheme + idempotency + body type)\n · signature: { _tag: 'hmac', algorithm: 'hmacSha256', header: 'X-Signature', encoding: 'hex', includeTimestamp: true }\n · verification: 'provider' — the handler verifies with the provider's own SDK\n · verification: 'none' — deliberately public; a gateway / IP allow-list owns it\nA signature-verified webhook reads its shared secret from VOLTRO_WEBHOOK_SECRET_${e.toUpperCase().replace(/[^A-Z0-9]/g, "_")}. The framework never invents that value — the sender holds the other half of it.`), this.webhookId = e;
|
|
629
|
-
}
|
|
630
|
-
}, Ye = (e) => `VOLTRO_WEBHOOK_SECRET_${e.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`, Xe = (e) => {
|
|
631
|
-
let t = e.signature ?? e.provider?.signature ?? null;
|
|
632
|
-
return (e.verification === void 0 || e.verification === "signature") && t === null ? null : e.verification ?? "signature";
|
|
633
|
-
}, Ze = (e, t) => {
|
|
634
|
-
let n = e.provider, r = e.signature ?? n?.signature ?? null, i = e.idempotency ?? n?.idempotency ?? null, a = e.bodyType ?? n?.bodyType ?? "json", o = Xe(e);
|
|
635
|
-
if (o === null) throw new Je(e.id);
|
|
636
|
-
let c = o;
|
|
637
|
-
c === "none" && A({ scope: `webhook:${e.id}` }).warn("incoming webhook mounted WITHOUT verification — anyone who can reach this URL can run its handler", {
|
|
638
|
-
webhookId: e.id,
|
|
639
|
-
verification: "none"
|
|
640
|
-
});
|
|
641
|
-
let l = t.idempotencyCache ?? Ge(), u = Ke(i?.ttl), d = t.log ?? ((e) => {
|
|
642
|
-
A({ scope: `webhook:${e.webhookId}` }).debug("incoming webhook", {
|
|
643
|
-
status: e.status,
|
|
644
|
-
signatureOk: e.signatureOk,
|
|
645
|
-
idempotency: e.idempotency,
|
|
646
|
-
durationMs: e.durationMs,
|
|
647
|
-
...e.errorMessage === void 0 ? {} : { errorMessage: e.errorMessage }
|
|
648
|
-
});
|
|
649
|
-
});
|
|
650
|
-
return Object.assign(async (n) => {
|
|
651
|
-
let o = Date.now();
|
|
652
|
-
if (n.method !== "POST") {
|
|
653
|
-
let t = Y(405, {
|
|
654
|
-
error: "method not allowed",
|
|
655
|
-
expected: "POST"
|
|
656
|
-
});
|
|
657
|
-
return d({
|
|
658
|
-
webhookId: e.id,
|
|
659
|
-
status: 405,
|
|
660
|
-
signatureOk: "skipped",
|
|
661
|
-
idempotency: "skipped",
|
|
662
|
-
durationMs: Date.now() - o
|
|
663
|
-
}), t;
|
|
664
|
-
}
|
|
665
|
-
let f = "skipped";
|
|
666
|
-
if (r !== null && c === "signature") {
|
|
667
|
-
let i = await t.resolveSecret(e.id);
|
|
668
|
-
if (i === null) {
|
|
669
|
-
let t = `no signing secret configured — set ${Ye(e.id)}`, n = Y(503, {
|
|
670
|
-
error: "webhook not configured",
|
|
671
|
-
reason: t
|
|
672
|
-
});
|
|
673
|
-
return d({
|
|
674
|
-
webhookId: e.id,
|
|
675
|
-
status: 503,
|
|
676
|
-
signatureOk: !1,
|
|
677
|
-
idempotency: "skipped",
|
|
678
|
-
durationMs: Date.now() - o,
|
|
679
|
-
errorMessage: t
|
|
680
|
-
}), n;
|
|
681
|
-
}
|
|
682
|
-
{
|
|
683
|
-
let t = s(r, {
|
|
684
|
-
rawBody: n.rawBody,
|
|
685
|
-
secret: i,
|
|
686
|
-
headers: n.headers
|
|
687
|
-
});
|
|
688
|
-
if (f = t.ok, !t.ok) {
|
|
689
|
-
let n = Y(401, {
|
|
690
|
-
error: "invalid signature",
|
|
691
|
-
reason: t.reason
|
|
692
|
-
});
|
|
693
|
-
return d({
|
|
694
|
-
webhookId: e.id,
|
|
695
|
-
status: 401,
|
|
696
|
-
signatureOk: !1,
|
|
697
|
-
idempotency: "skipped",
|
|
698
|
-
durationMs: Date.now() - o,
|
|
699
|
-
errorMessage: t.reason
|
|
700
|
-
}), n;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
let p;
|
|
705
|
-
try {
|
|
706
|
-
if (a === "json") {
|
|
707
|
-
let e = new TextDecoder().decode(n.rawBody);
|
|
708
|
-
p = e.length === 0 ? null : JSON.parse(e);
|
|
709
|
-
} else if (a === "form") {
|
|
710
|
-
let e = new TextDecoder().decode(n.rawBody), t = new URLSearchParams(e), r = {};
|
|
711
|
-
for (let [e, n] of t.entries()) r[e] = n;
|
|
712
|
-
p = r;
|
|
713
|
-
} else p = n.rawBody;
|
|
714
|
-
} catch (t) {
|
|
715
|
-
let n = Y(400, {
|
|
716
|
-
error: "malformed body",
|
|
717
|
-
reason: t.message
|
|
718
|
-
});
|
|
719
|
-
return d({
|
|
720
|
-
webhookId: e.id,
|
|
721
|
-
status: 400,
|
|
722
|
-
signatureOk: f,
|
|
723
|
-
idempotency: "skipped",
|
|
724
|
-
durationMs: Date.now() - o,
|
|
725
|
-
errorMessage: t.message
|
|
726
|
-
}), n;
|
|
727
|
-
}
|
|
728
|
-
let m;
|
|
729
|
-
try {
|
|
730
|
-
m = T.decodeUnknownSync(e.payload)(p);
|
|
731
|
-
} catch (t) {
|
|
732
|
-
let n = Y(422, {
|
|
733
|
-
error: "schema validation failed",
|
|
734
|
-
reason: t.message
|
|
735
|
-
});
|
|
736
|
-
return d({
|
|
737
|
-
webhookId: e.id,
|
|
738
|
-
status: 422,
|
|
739
|
-
signatureOk: f,
|
|
740
|
-
idempotency: "skipped",
|
|
741
|
-
durationMs: Date.now() - o,
|
|
742
|
-
errorMessage: t.message
|
|
743
|
-
}), n;
|
|
744
|
-
}
|
|
745
|
-
let h, g = "skipped";
|
|
746
|
-
if (i !== null) {
|
|
747
|
-
let t = i.from;
|
|
748
|
-
if (h = typeof t == "string" ? n.headers[t.toLowerCase()] : t(n.headers, n.rawBody), h !== void 0 && h.length > 0) {
|
|
749
|
-
let t = l.claim(h, u);
|
|
750
|
-
if (g = t, t === "duplicate") {
|
|
751
|
-
let t = Y(200, {
|
|
752
|
-
received: !0,
|
|
753
|
-
duplicate: !0
|
|
754
|
-
});
|
|
755
|
-
return d({
|
|
756
|
-
webhookId: e.id,
|
|
757
|
-
status: 200,
|
|
758
|
-
signatureOk: f,
|
|
759
|
-
idempotency: "duplicate",
|
|
760
|
-
durationMs: Date.now() - o
|
|
761
|
-
}), t;
|
|
762
|
-
}
|
|
763
|
-
if (t === "inflight") {
|
|
764
|
-
let t = Y(409, {
|
|
765
|
-
error: "request already in flight",
|
|
766
|
-
idempotencyKey: h
|
|
767
|
-
});
|
|
768
|
-
return d({
|
|
769
|
-
webhookId: e.id,
|
|
770
|
-
status: 409,
|
|
771
|
-
signatureOk: f,
|
|
772
|
-
idempotency: "inflight",
|
|
773
|
-
durationMs: Date.now() - o
|
|
774
|
-
}), t;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
try {
|
|
779
|
-
let r = t.resolveWorkflows?.();
|
|
780
|
-
await e.handler({
|
|
781
|
-
body: m,
|
|
782
|
-
rawBody: n.rawBody,
|
|
783
|
-
headers: n.headers,
|
|
784
|
-
idempotencyKey: h ?? "",
|
|
785
|
-
...r ? { workflows: r } : {}
|
|
786
|
-
}), h !== void 0 && l.commit(h);
|
|
787
|
-
let i = Y(200, { received: !0 });
|
|
788
|
-
return d({
|
|
789
|
-
webhookId: e.id,
|
|
790
|
-
status: 200,
|
|
791
|
-
signatureOk: f,
|
|
792
|
-
idempotency: g,
|
|
793
|
-
durationMs: Date.now() - o
|
|
794
|
-
}), i;
|
|
795
|
-
} catch (t) {
|
|
796
|
-
h !== void 0 && l.release(h);
|
|
797
|
-
let n = Y(500, {
|
|
798
|
-
error: "handler threw",
|
|
799
|
-
reason: t.message
|
|
800
|
-
});
|
|
801
|
-
return d({
|
|
802
|
-
webhookId: e.id,
|
|
803
|
-
status: 500,
|
|
804
|
-
signatureOk: f,
|
|
805
|
-
idempotency: g,
|
|
806
|
-
durationMs: Date.now() - o,
|
|
807
|
-
errorMessage: t.message
|
|
808
|
-
}), ge({
|
|
809
|
-
error: t,
|
|
810
|
-
source: "webhook",
|
|
811
|
-
name: e.id,
|
|
812
|
-
fields: { status: 500 }
|
|
813
|
-
}), n;
|
|
814
|
-
}
|
|
815
|
-
}, { [qe]: c });
|
|
816
|
-
}, Qe = (e) => `/webhooks/${e}`, $e = "_voltro_webhook_rate_windows", et = 6e4, tt = (e, t) => `${e}@${t}`, nt = async (e, t, n) => (await e.query({
|
|
817
|
-
table: "_voltro_webhook_rate_windows",
|
|
818
|
-
predicate: D(O("scope", t), O("bucket", n)),
|
|
819
|
-
order: [],
|
|
820
|
-
take: 1,
|
|
821
|
-
skip: void 0,
|
|
822
|
-
projection: void 0
|
|
823
|
-
}))[0] ?? null, X = 32, rt = async (e, t, n) => {
|
|
824
|
-
for (let r = 0; r < X; r++) {
|
|
825
|
-
r > 0 && await new Promise((e) => setTimeout(e, r));
|
|
826
|
-
let i = await nt(e, t.key, n);
|
|
827
|
-
if (i === null) {
|
|
828
|
-
await e.insertIgnore($e, {
|
|
829
|
-
id: tt(t.key, n),
|
|
830
|
-
scope: t.key,
|
|
831
|
-
bucket: n,
|
|
832
|
-
count: 0,
|
|
833
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
834
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
835
|
-
}, { conflictColumns: ["id"] });
|
|
836
|
-
continue;
|
|
837
|
-
}
|
|
838
|
-
if (i.count >= t.limit) return !1;
|
|
839
|
-
if (await e.updateMany("_voltro_webhook_rate_windows", {
|
|
840
|
-
count: i.count + 1,
|
|
841
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
842
|
-
}, { where: D(O("id", i.id), O("count", i.count)) }) === 1) return !0;
|
|
843
|
-
}
|
|
844
|
-
throw Error(`webhook rate window: contention on (${t.key}, ${n}) exceeded ${X} attempts`);
|
|
845
|
-
}, it = async (e, t, n) => {
|
|
846
|
-
for (let r = 0; r < X; r++) {
|
|
847
|
-
r > 0 && await new Promise((e) => setTimeout(e, r));
|
|
848
|
-
let i = await nt(e, t, n);
|
|
849
|
-
if (i === null || i.count <= 0 || await e.updateMany("_voltro_webhook_rate_windows", {
|
|
850
|
-
count: i.count - 1,
|
|
851
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
852
|
-
}, { where: D(O("id", i.id), O("count", i.count)) }) === 1) return;
|
|
853
|
-
}
|
|
854
|
-
throw Error(`webhook rate window: release contention on (${t}) exceeded ${X} attempts`);
|
|
855
|
-
}, at = async (e, t, n, r = et) => {
|
|
856
|
-
if (t.length === 0) return {
|
|
857
|
-
acquired: !0,
|
|
858
|
-
retryInMs: 0
|
|
859
|
-
};
|
|
860
|
-
let i = Math.floor(n / r), a = [];
|
|
861
|
-
for (let o of t) {
|
|
862
|
-
if (await rt(e, o, i)) {
|
|
863
|
-
a.push(o);
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
for (let t of a) await it(e, t.key, i);
|
|
867
|
-
return {
|
|
868
|
-
acquired: !1,
|
|
869
|
-
retryInMs: (i + 1) * r - n
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
return {
|
|
873
|
-
acquired: !0,
|
|
874
|
-
retryInMs: 0
|
|
875
|
-
};
|
|
876
|
-
}, ot = (e, t) => {
|
|
877
|
-
if (e === "json") return {
|
|
878
|
-
contentType: "application/json",
|
|
879
|
-
bytes: Buffer.from(t, "utf8")
|
|
880
|
-
};
|
|
881
|
-
let n;
|
|
882
|
-
try {
|
|
883
|
-
n = JSON.parse(t);
|
|
884
|
-
} catch (t) {
|
|
885
|
-
throw new b({
|
|
886
|
-
format: e,
|
|
887
|
-
reason: `payload is not valid JSON: ${t.message}`
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
return e === "form" ? {
|
|
891
|
-
contentType: "application/x-www-form-urlencoded",
|
|
892
|
-
bytes: Buffer.from(st(n), "utf8")
|
|
893
|
-
} : {
|
|
894
|
-
contentType: "application/xml",
|
|
895
|
-
bytes: Buffer.from(lt(n), "utf8")
|
|
896
|
-
};
|
|
897
|
-
}, st = (e) => {
|
|
898
|
-
if (typeof e != "object" || !e || Array.isArray(e)) throw new b({
|
|
899
|
-
format: "form",
|
|
900
|
-
reason: `form encoding requires a JSON object at the top level, got ${ft(e)}`
|
|
901
|
-
});
|
|
902
|
-
let t = new URLSearchParams();
|
|
903
|
-
for (let [n, r] of Object.entries(e)) Z(t, n, r);
|
|
904
|
-
return t.toString();
|
|
905
|
-
}, Z = (e, t, n) => {
|
|
906
|
-
if (n === null) {
|
|
907
|
-
e.append(t, "");
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
if (Array.isArray(n)) {
|
|
911
|
-
n.forEach((n, r) => Z(e, `${t}[${r}]`, n));
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
if (typeof n == "object") {
|
|
915
|
-
for (let [r, i] of Object.entries(n)) Z(e, `${t}[${r}]`, i);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
e.append(t, String(n));
|
|
919
|
-
}, ct = /^[A-Za-z_][A-Za-z0-9_.-]*$/, lt = (e) => `<?xml version="1.0" encoding="UTF-8"?>${Q("webhook", e)}`, Q = (e, t) => {
|
|
920
|
-
if (!ct.test(e)) throw new b({
|
|
921
|
-
format: "xml",
|
|
922
|
-
reason: `object key ${JSON.stringify(e)} is not a valid XML element name`
|
|
923
|
-
});
|
|
924
|
-
return `<${e}>${ut(e, t)}</${e}>`;
|
|
925
|
-
}, ut = (e, t) => t == null ? "" : Array.isArray(t) ? t.map((e) => Q("item", e)).join("") : typeof t == "object" ? Object.entries(t).map(([e, t]) => Q(e, t)).join("") : dt(String(t)), dt = (e) => e.replace(/[&<>"']/g, (e) => {
|
|
926
|
-
switch (e) {
|
|
927
|
-
case "&": return "&";
|
|
928
|
-
case "<": return "<";
|
|
929
|
-
case ">": return ">";
|
|
930
|
-
case "\"": return """;
|
|
931
|
-
default: return "'";
|
|
932
|
-
}
|
|
933
|
-
}), ft = (e) => e === null ? "null" : Array.isArray(e) ? "array" : typeof e, pt = "_voltro_webhook_targets", mt = 32, ht = async (e, t) => (await e.query({
|
|
934
|
-
table: "_voltro_webhook_targets",
|
|
935
|
-
predicate: O("id", t),
|
|
936
|
-
order: [],
|
|
937
|
-
take: 1,
|
|
938
|
-
skip: void 0,
|
|
939
|
-
projection: void 0
|
|
940
|
-
}))[0] ?? null, gt = async (e, t, n, r, i = /* @__PURE__ */ new Date(), a = !1) => {
|
|
941
|
-
for (let o = 0; o < mt; o++) {
|
|
942
|
-
o > 0 && await new Promise((e) => setTimeout(e, o));
|
|
943
|
-
let s = await ht(e, t);
|
|
944
|
-
if (s === null) return {
|
|
945
|
-
consecutiveFailures: 0,
|
|
946
|
-
autoDisabled: !1
|
|
947
|
-
};
|
|
948
|
-
let c = s.consecutiveFailures ?? 0;
|
|
949
|
-
if (n) {
|
|
950
|
-
if (c === 0 || await e.updateMany("_voltro_webhook_targets", {
|
|
951
|
-
consecutiveFailures: 0,
|
|
952
|
-
updatedAt: i
|
|
953
|
-
}, { where: D(O("id", t), O("consecutiveFailures", c)) }) === 1) return {
|
|
954
|
-
consecutiveFailures: 0,
|
|
955
|
-
autoDisabled: !1
|
|
956
|
-
};
|
|
957
|
-
continue;
|
|
958
|
-
}
|
|
959
|
-
let l = c + 1, u = s.autoDisableAfter, d = s.active && (a || typeof u == "number" && u > 0 && l >= u), f = {
|
|
960
|
-
consecutiveFailures: l,
|
|
961
|
-
updatedAt: i
|
|
962
|
-
};
|
|
963
|
-
if (d && (f.active = !1, f.autoDisabledAt = i, f.autoDisableReason = r.slice(0, 500)), await e.updateMany("_voltro_webhook_targets", f, { where: D(O("id", t), O("consecutiveFailures", c)) }) === 1) return {
|
|
964
|
-
consecutiveFailures: l,
|
|
965
|
-
autoDisabled: d
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${mt} attempts`);
|
|
969
|
-
}, _t = T.Struct({
|
|
970
|
-
status: T.Number,
|
|
971
|
-
body: T.String,
|
|
972
|
-
latencyMs: T.Number,
|
|
973
|
-
retryAfterSec: T.Union([T.Number, T.Undefined]),
|
|
974
|
-
transportError: T.Union([T.String, T.Undefined]),
|
|
975
|
-
encodeError: T.Union([T.String, T.Undefined])
|
|
976
|
-
}), vt = T.Struct({
|
|
977
|
-
outcome: T.Literals([
|
|
978
|
-
"succeeded",
|
|
979
|
-
"failed",
|
|
980
|
-
"retry"
|
|
981
|
-
]),
|
|
982
|
-
delayMs: T.Union([T.Number, T.Undefined]),
|
|
983
|
-
reason: T.Union([T.String, T.Undefined]),
|
|
984
|
-
autoDisabled: T.Union([T.Boolean, T.Undefined]),
|
|
985
|
-
consecutiveFailures: T.Union([T.Number, T.Undefined])
|
|
986
|
-
}), yt = T.Struct({
|
|
987
|
-
acquired: T.Boolean,
|
|
988
|
-
retryInMs: T.Number
|
|
989
|
-
}), bt = {
|
|
990
|
-
deliveryId: T.String,
|
|
991
|
-
targetId: T.String,
|
|
992
|
-
event: T.String,
|
|
993
|
-
eventId: T.String,
|
|
994
|
-
payloadJson: T.String,
|
|
995
|
-
attemptEpoch: T.optional(T.Number)
|
|
996
|
-
}, xt = ye.make("voltro.deliverWebhook", {
|
|
997
|
-
payload: bt,
|
|
998
|
-
success: T.Struct({
|
|
999
|
-
finalStatus: T.Literals([
|
|
1000
|
-
"succeeded",
|
|
1001
|
-
"failed",
|
|
1002
|
-
"deferred"
|
|
1003
|
-
]),
|
|
1004
|
-
attempts: T.Number
|
|
1005
|
-
}),
|
|
1006
|
-
idempotencyKey: ({ deliveryId: e, attemptEpoch: t }) => `voltro.deliverWebhook:${e}:${t ?? 0}`
|
|
1007
|
-
}), St = 3e4, Ct = (e, t) => {
|
|
1008
|
-
if (e !== void 0) return e;
|
|
1009
|
-
let n = Number(t.VOLTRO_WEBHOOK_TIMEOUT_MS);
|
|
1010
|
-
return Number.isFinite(n) && n > 0 ? n : St;
|
|
1011
|
-
}, wt = (e, t = {}) => (n, r) => {
|
|
1012
|
-
let { deliveryId: i, targetId: a, event: o, eventId: s, payloadJson: c } = n, l = A({ scope: `webhook:${i}` });
|
|
1013
|
-
return w.gen(function* () {
|
|
1014
|
-
let n = yield* j.make({
|
|
1015
|
-
name: "fetch-target",
|
|
1016
|
-
success: T.Union([T.Null, T.Struct({
|
|
1017
|
-
id: T.String,
|
|
1018
|
-
event: T.String,
|
|
1019
|
-
url: T.String,
|
|
1020
|
-
secret: T.String,
|
|
1021
|
-
signing: T.Any,
|
|
1022
|
-
retry: T.Any,
|
|
1023
|
-
headers: T.Union([T.Record(T.String, T.String), T.Null]),
|
|
1024
|
-
rateLimitPerMinute: T.Union([T.Number, T.Null]),
|
|
1025
|
-
active: T.Boolean,
|
|
1026
|
-
format: T.Literals([
|
|
1027
|
-
"json",
|
|
1028
|
-
"form",
|
|
1029
|
-
"xml"
|
|
1030
|
-
]),
|
|
1031
|
-
tenantId: T.Union([T.String, T.Null])
|
|
1032
|
-
})]),
|
|
1033
|
-
execute: w.tryPromise({
|
|
1034
|
-
try: async () => (await e.store.query({
|
|
1035
|
-
table: "_voltro_webhook_targets",
|
|
1036
|
-
predicate: O("id", a),
|
|
1037
|
-
order: [{
|
|
1038
|
-
column: "createdAt",
|
|
1039
|
-
direction: "asc"
|
|
1040
|
-
}],
|
|
1041
|
-
take: 1,
|
|
1042
|
-
skip: void 0,
|
|
1043
|
-
projection: void 0
|
|
1044
|
-
}))[0] ?? null,
|
|
1045
|
-
catch: (e) => (l.debug("fetch-target raw error", {
|
|
1046
|
-
targetId: a,
|
|
1047
|
-
event: o,
|
|
1048
|
-
cause: String(e)
|
|
1049
|
-
}), /* @__PURE__ */ Error(`fetch-target failed: ${e.message}`))
|
|
1050
|
-
}).pipe(w.orDie)
|
|
1051
|
-
});
|
|
1052
|
-
if (n === null) return {
|
|
1053
|
-
finalStatus: "failed",
|
|
1054
|
-
attempts: 0
|
|
1055
|
-
};
|
|
1056
|
-
let r = async (t, r, l) => {
|
|
1057
|
-
await e.store.update("_voltro_webhook_deliveries", t, {
|
|
1058
|
-
...l,
|
|
1059
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1060
|
-
}) === null && await e.store.insert("_voltro_webhook_deliveries", {
|
|
1061
|
-
id: t,
|
|
1062
|
-
deliveryId: i,
|
|
1063
|
-
targetId: a,
|
|
1064
|
-
tenantId: n.tenantId ?? null,
|
|
1065
|
-
event: o,
|
|
1066
|
-
eventId: s,
|
|
1067
|
-
attempt: r,
|
|
1068
|
-
payload: c,
|
|
1069
|
-
scheduledAt: /* @__PURE__ */ new Date(),
|
|
1070
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1071
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
1072
|
-
...l
|
|
1073
|
-
});
|
|
1074
|
-
};
|
|
1075
|
-
if (n.active !== !0) return yield* j.make({
|
|
1076
|
-
name: "record-paused-pending",
|
|
1077
|
-
success: T.Void,
|
|
1078
|
-
execute: w.tryPromise({
|
|
1079
|
-
try: () => r(`${i}:1`, 1, { status: "pending" }),
|
|
1080
|
-
catch: (e) => /* @__PURE__ */ Error(`record-paused-pending failed: ${e.message}`)
|
|
1081
|
-
}).pipe(w.orDie)
|
|
1082
|
-
}), {
|
|
1083
|
-
finalStatus: "deferred",
|
|
1084
|
-
attempts: 0
|
|
1085
|
-
};
|
|
1086
|
-
let u = (e) => typeof e == "string" ? JSON.parse(e) : e, d = u(n.retry), p = u(n.signing), m = n.headers === null ? null : u(n.headers), h = t.rateWindowMs ?? 6e4, g = Ct(t.timeoutMs, process.env), _ = t.events?.find((e) => e.id === o)?.globalRateLimit, v = [...typeof n.rateLimitPerMinute == "number" && n.rateLimitPerMinute > 0 ? [{
|
|
1087
|
-
key: `target:${n.id}`,
|
|
1088
|
-
limit: n.rateLimitPerMinute
|
|
1089
|
-
}] : [], ..._ !== void 0 && _.perMinute > 0 ? [{
|
|
1090
|
-
key: `event:${o}`,
|
|
1091
|
-
limit: _.perMinute
|
|
1092
|
-
}] : []];
|
|
1093
|
-
for (let t = 1; t <= d.maxAttempts; t++) {
|
|
1094
|
-
let u = `${i}:${t}`;
|
|
1095
|
-
if (v.length > 0) for (let n = 0;; n++) {
|
|
1096
|
-
let i = yield* j.make({
|
|
1097
|
-
name: `rate-acquire-${t}-${n}`,
|
|
1098
|
-
success: yt,
|
|
1099
|
-
execute: w.tryPromise({
|
|
1100
|
-
try: () => at(e.store, v, Date.now(), h),
|
|
1101
|
-
catch: (e) => (l.debug("rate-acquire raw error", {
|
|
1102
|
-
targetId: a,
|
|
1103
|
-
event: o,
|
|
1104
|
-
attempt: t,
|
|
1105
|
-
cause: String(e)
|
|
1106
|
-
}), /* @__PURE__ */ Error(`rate-acquire failed: ${e.message}`))
|
|
1107
|
-
}).pipe(w.orDie)
|
|
1108
|
-
});
|
|
1109
|
-
if (i.acquired) break;
|
|
1110
|
-
yield* j.make({
|
|
1111
|
-
name: `record-rate-deferral-${t}-${n}`,
|
|
1112
|
-
success: T.Void,
|
|
1113
|
-
execute: w.tryPromise({
|
|
1114
|
-
try: () => r(u, t, {
|
|
1115
|
-
status: "pending",
|
|
1116
|
-
nextAttemptAt: new Date(Date.now() + i.retryInMs)
|
|
1117
|
-
}),
|
|
1118
|
-
catch: (e) => /* @__PURE__ */ Error(`record-rate-deferral failed: ${e.message}`)
|
|
1119
|
-
}).pipe(w.orDie)
|
|
1120
|
-
}), yield* ve.sleep({
|
|
1121
|
-
name: `rate-window-sleep-${t}-${n}`,
|
|
1122
|
-
duration: `${i.retryInMs} millis`
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
yield* j.make({
|
|
1126
|
-
name: `record-attempt-start-${t}`,
|
|
1127
|
-
success: T.Void,
|
|
1128
|
-
execute: w.tryPromise({
|
|
1129
|
-
try: () => r(u, t, {
|
|
1130
|
-
status: "inFlight",
|
|
1131
|
-
nextAttemptAt: null,
|
|
1132
|
-
scheduledAt: /* @__PURE__ */ new Date()
|
|
1133
|
-
}),
|
|
1134
|
-
catch: (e) => /* @__PURE__ */ Error(`record-attempt-start failed: ${e.message}`)
|
|
1135
|
-
}).pipe(w.orDie)
|
|
1136
|
-
});
|
|
1137
|
-
let _ = yield* j.make({
|
|
1138
|
-
name: `sign-and-post-${t}`,
|
|
1139
|
-
success: _t,
|
|
1140
|
-
execute: w.promise(async () => {
|
|
1141
|
-
let e = Date.now(), r;
|
|
1142
|
-
try {
|
|
1143
|
-
r = ot(n.format, c);
|
|
1144
|
-
} catch (t) {
|
|
1145
|
-
if (t instanceof b) return {
|
|
1146
|
-
status: 0,
|
|
1147
|
-
body: "",
|
|
1148
|
-
latencyMs: Date.now() - e,
|
|
1149
|
-
retryAfterSec: void 0,
|
|
1150
|
-
transportError: void 0,
|
|
1151
|
-
encodeError: t.reason
|
|
1152
|
-
};
|
|
1153
|
-
throw t;
|
|
1154
|
-
}
|
|
1155
|
-
let a = Buffer.from(r.bytes), { headers: l } = f(p, {
|
|
1156
|
-
rawBody: a,
|
|
1157
|
-
secret: n.secret,
|
|
1158
|
-
messageId: i
|
|
1159
|
-
});
|
|
1160
|
-
process.env.NODE_ENV === "production" && k(n.url);
|
|
1161
|
-
try {
|
|
1162
|
-
let c = await fetch(n.url, {
|
|
1163
|
-
method: "POST",
|
|
1164
|
-
headers: {
|
|
1165
|
-
"content-type": r.contentType,
|
|
1166
|
-
...l,
|
|
1167
|
-
"x-voltro-event": o,
|
|
1168
|
-
"x-voltro-event-id": s,
|
|
1169
|
-
"x-voltro-delivery-id": i,
|
|
1170
|
-
"x-voltro-attempt": String(t),
|
|
1171
|
-
"idempotency-key": i,
|
|
1172
|
-
...m ?? {}
|
|
1173
|
-
},
|
|
1174
|
-
body: a,
|
|
1175
|
-
redirect: "manual",
|
|
1176
|
-
signal: AbortSignal.timeout(g)
|
|
1177
|
-
}), u = (await c.text()).slice(0, 8192), d = c.headers.get("retry-after");
|
|
1178
|
-
return {
|
|
1179
|
-
status: c.status,
|
|
1180
|
-
body: u,
|
|
1181
|
-
latencyMs: Date.now() - e,
|
|
1182
|
-
retryAfterSec: d !== null && /^\d+$/.test(d) ? Number(d) : void 0,
|
|
1183
|
-
transportError: void 0,
|
|
1184
|
-
encodeError: void 0
|
|
1185
|
-
};
|
|
1186
|
-
} catch (t) {
|
|
1187
|
-
return {
|
|
1188
|
-
status: 0,
|
|
1189
|
-
body: "",
|
|
1190
|
-
latencyMs: Date.now() - e,
|
|
1191
|
-
retryAfterSec: void 0,
|
|
1192
|
-
transportError: t.message,
|
|
1193
|
-
encodeError: void 0
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
})
|
|
1197
|
-
}), y = yield* j.make({
|
|
1198
|
-
name: `classify-and-record-${t}`,
|
|
1199
|
-
success: vt,
|
|
1200
|
-
execute: w.tryPromise({
|
|
1201
|
-
try: async () => {
|
|
1202
|
-
let r = _.status >= 200 && _.status < 300, i = _.transportError !== void 0, o = _.encodeError !== void 0, s = _.status === 410, c = (t, n) => gt(e.store, a, t, n, /* @__PURE__ */ new Date(), !t && s);
|
|
1203
|
-
if (r) {
|
|
1204
|
-
await e.store.update("_voltro_webhook_deliveries", u, {
|
|
1205
|
-
status: "succeeded",
|
|
1206
|
-
responseStatus: _.status,
|
|
1207
|
-
responseBody: _.body,
|
|
1208
|
-
latencyMs: _.latencyMs,
|
|
1209
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1210
|
-
});
|
|
1211
|
-
let t = await c(!0, "");
|
|
1212
|
-
return {
|
|
1213
|
-
outcome: "succeeded",
|
|
1214
|
-
delayMs: void 0,
|
|
1215
|
-
reason: void 0,
|
|
1216
|
-
autoDisabled: t.autoDisabled,
|
|
1217
|
-
consecutiveFailures: t.consecutiveFailures
|
|
1218
|
-
};
|
|
1219
|
-
}
|
|
1220
|
-
let l = o ? null : Te(d, t, {
|
|
1221
|
-
...i ? {} : { status: _.status },
|
|
1222
|
-
..._.retryAfterSec === void 0 ? {} : { retryAfterSeconds: _.retryAfterSec }
|
|
1223
|
-
});
|
|
1224
|
-
if (l === null) {
|
|
1225
|
-
let t = o ? `encode (${n.format}): ${_.encodeError}` : i ? `transport: ${_.transportError}` : `http ${_.status}: ${_.body.slice(0, 200)}`;
|
|
1226
|
-
await e.store.update("_voltro_webhook_deliveries", u, {
|
|
1227
|
-
status: "failed",
|
|
1228
|
-
responseStatus: i || o ? null : _.status,
|
|
1229
|
-
responseBody: i || o ? null : _.body,
|
|
1230
|
-
errorMessage: o ? t : _.transportError ?? null,
|
|
1231
|
-
latencyMs: _.latencyMs,
|
|
1232
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1233
|
-
});
|
|
1234
|
-
let r = await c(!1, t);
|
|
1235
|
-
return {
|
|
1236
|
-
outcome: "failed",
|
|
1237
|
-
delayMs: void 0,
|
|
1238
|
-
reason: t,
|
|
1239
|
-
autoDisabled: r.autoDisabled,
|
|
1240
|
-
consecutiveFailures: r.consecutiveFailures
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
return await e.store.update("_voltro_webhook_deliveries", u, {
|
|
1244
|
-
status: "retryScheduled",
|
|
1245
|
-
responseStatus: i ? null : _.status,
|
|
1246
|
-
responseBody: i ? null : _.body,
|
|
1247
|
-
errorMessage: _.transportError ?? null,
|
|
1248
|
-
latencyMs: _.latencyMs,
|
|
1249
|
-
nextAttemptAt: new Date(Date.now() + l.delayMs),
|
|
1250
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1251
|
-
}), {
|
|
1252
|
-
outcome: "retry",
|
|
1253
|
-
delayMs: l.delayMs,
|
|
1254
|
-
reason: void 0,
|
|
1255
|
-
autoDisabled: void 0,
|
|
1256
|
-
consecutiveFailures: void 0
|
|
1257
|
-
};
|
|
1258
|
-
},
|
|
1259
|
-
catch: (e) => (l.debug("classify-and-record raw error", {
|
|
1260
|
-
targetId: a,
|
|
1261
|
-
event: o,
|
|
1262
|
-
attempt: t,
|
|
1263
|
-
cause: String(e)
|
|
1264
|
-
}), /* @__PURE__ */ Error(`classify-and-record failed: ${e.message}`))
|
|
1265
|
-
}).pipe(w.orDie)
|
|
1266
|
-
});
|
|
1267
|
-
if (y.autoDisabled === !0 && l.warn("target auto-disabled after consecutive failures", {
|
|
1268
|
-
targetId: a,
|
|
1269
|
-
event: o,
|
|
1270
|
-
consecutiveFailures: y.consecutiveFailures ?? null,
|
|
1271
|
-
reason: y.reason ?? null
|
|
1272
|
-
}), y.outcome === "succeeded") return {
|
|
1273
|
-
finalStatus: "succeeded",
|
|
1274
|
-
attempts: t
|
|
1275
|
-
};
|
|
1276
|
-
if (y.outcome === "failed") return {
|
|
1277
|
-
finalStatus: "failed",
|
|
1278
|
-
attempts: t
|
|
1279
|
-
};
|
|
1280
|
-
y.delayMs !== void 0 && (yield* ve.sleep({
|
|
1281
|
-
name: `retry-sleep-${t}`,
|
|
1282
|
-
duration: `${y.delayMs} millis`
|
|
1283
|
-
}));
|
|
1284
|
-
}
|
|
1285
|
-
return {
|
|
1286
|
-
finalStatus: "failed",
|
|
1287
|
-
attempts: d.maxAttempts
|
|
1288
|
-
};
|
|
1289
|
-
});
|
|
1290
|
-
}, $ = "voltro.webhook.emit", Tt = (e) => typeof e.event == "string", Et = (e) => be({
|
|
1291
|
-
effect: $,
|
|
1292
|
-
maxAttempts: 1,
|
|
1293
|
-
handler: async ({ payload: t, outboxId: n }) => {
|
|
1294
|
-
if (!Tt(t)) throw Error(`${$}: row payload has no \`event\` name — cannot resolve a descriptor. Got keys: ${Object.keys(t).join(", ") || "(none)"}`);
|
|
1295
|
-
let r = e();
|
|
1296
|
-
if (r === void 0) throw Error(`${$}: the outgoing-webhooks service is not available in this process. An emit was enqueued by a build that had the plugin registered; this one does not.`);
|
|
1297
|
-
await r.emit(t.event, t.payload, {
|
|
1298
|
-
tenantId: t.tenantId,
|
|
1299
|
-
eventId: n
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
});
|
|
1303
|
-
//#endregion
|
|
1304
|
-
export { We as IdempotencyCache, et as RATE_WINDOW_MS, $e as RATE_WINDOW_TABLE, p as STANDARD_WEBHOOKS_DEFAULT_TOLERANCE_SECONDS, te as STANDARD_WEBHOOKS_ID_HEADER, c as STANDARD_WEBHOOKS_MAX_KEY_BYTES, l as STANDARD_WEBHOOKS_MIN_KEY_BYTES, _ as STANDARD_WEBHOOKS_SECRET_PREFIX, m as STANDARD_WEBHOOKS_SIGNATURE_HEADER, d as STANDARD_WEBHOOKS_TIMESTAMP_HEADER, u as STANDARD_WEBHOOKS_VERSION, pt as TARGETS_TABLE, Je as UnverifiedIncomingWebhook, M as WEBHOOKS_EGRESS, xe as WEBHOOKS_PLUGIN_PERMISSIONS, $ as WEBHOOK_EMIT_EFFECT, qe as WEBHOOK_VERIFICATION_PROPERTY, oe as WebhookDeliveryNotFound, se as WebhookPayloadInvalid, b as WebhookPayloadUnrepresentable, x as WebhookPayloadVersionInvalid, S as WebhookSubscribeInvalid, C as WebhookTargetNotFound, Oe as WebhooksService, ce as _voltroWebhookDeliveriesTable, le as _voltroWebhookEventStatsTable, ue as _voltroWebhookRateWindowsTable, de as _voltroWebhookTargetsTable, at as acquireRateSlots, N as asOutgoingEvent, wt as buildDeliverWebhookExecute, Be as buildWebhooksService, Pe as compareVersions, rt as consumeRateSlot, Qe as defaultIncomingPath, ee as defaultOutgoingSignature, Ee as defaultRetryPolicy, re as defineIncomingWebhook, r as defineWebhookProvider, xt as deliverWebhookWorkflow, ot as encodePayload, I as eventsOf, De as fastRetryPolicy, je as generateSecret, i as generateStandardWebhooksSecret, h as genericHmacSignature, Ge as getIdempotencyCache, v as githubSignature, Ce as hasWebhookAudience, Xe as incomingWebhookVerification, ne as isStandardWebhooksSecret, t as isWebhookDescriptor, Et as makeDeferredEmitHandler, H as matchesFilter, Ze as mountIncomingWebhook, Te as nextRetry, P as parseDuration, Ke as parseTtl, gt as recordDeliveryOutcome, it as releaseRateSlot, z as resolveSubscribe, f as signRequest, a as slackSignature, ae as standardWebhooksHeaders, o as standardWebhooksKey, g as standardWebhooksSignature, ie as standardWebhooksSignatureToken, n as standardWebhooksSignedContent, y as stripeSignature, He as useWebhooks, Ve as useWebhooksEffect, R as validateSubscribe, s as verifyRequest, e as verifyStandardWebhooks, fe as webhookTables, Se as webhooksPlugin };
|
|
1
|
+
import { WEBHOOKS_EGRESS as e, WEBHOOKS_PLUGIN_PERMISSIONS as t, webhooksPlugin as n } from "./plugin.js";
|
|
2
|
+
import { defineIncomingWebhook as r, defineWebhookProvider as i, isWebhookDescriptor as a } from "./dsl.js";
|
|
3
|
+
import { asOutgoingEvent as o, hasWebhookAudience as s } from "./eventAudience.js";
|
|
4
|
+
import { STANDARD_WEBHOOKS_DEFAULT_TOLERANCE_SECONDS as c, STANDARD_WEBHOOKS_ID_HEADER as l, STANDARD_WEBHOOKS_MAX_KEY_BYTES as u, STANDARD_WEBHOOKS_MIN_KEY_BYTES as d, STANDARD_WEBHOOKS_SECRET_PREFIX as f, STANDARD_WEBHOOKS_SIGNATURE_HEADER as p, STANDARD_WEBHOOKS_TIMESTAMP_HEADER as m, STANDARD_WEBHOOKS_VERSION as h, generateStandardWebhooksSecret as g, isStandardWebhooksSecret as _, standardWebhooksHeaders as v, standardWebhooksKey as y, standardWebhooksSignatureToken as b, standardWebhooksSignedContent as x, verifyStandardWebhooks as S } from "./standardWebhooks.js";
|
|
5
|
+
import { defaultOutgoingSignature as C, genericHmacSignature as w, githubSignature as T, signRequest as E, slackSignature as D, standardWebhooksSignature as O, stripeSignature as k, verifyRequest as A } from "./signing.js";
|
|
6
|
+
import { defaultRetryPolicy as j, fastRetryPolicy as M, nextRetry as N, parseDuration as P } from "./retry.js";
|
|
7
|
+
import { WebhookDeliveryNotFound as F, WebhookPayloadInvalid as I, WebhookPayloadUnrepresentable as L, WebhookPayloadVersionInvalid as R, WebhookSubscribeInvalid as z, WebhookTargetNotFound as B } from "./errors.js";
|
|
8
|
+
import { WebhooksService as V, buildWebhooksService as H, compareVersions as U, eventsOf as W, generateSecret as G, matchesFilter as K, resolveSubscribe as q, useWebhooks as J, useWebhooksEffect as Y, validateSubscribe as X } from "./service.js";
|
|
9
|
+
import { IdempotencyCache as Z, getIdempotencyCache as Q, parseTtl as $ } from "./idempotency.js";
|
|
10
|
+
import { UnverifiedIncomingWebhook as ee, WEBHOOK_VERIFICATION_PROPERTY as te, defaultIncomingPath as ne, incomingWebhookVerification as re, mountIncomingWebhook as ie } from "./incoming.js";
|
|
11
|
+
import { RATE_WINDOW_MS as ae, RATE_WINDOW_TABLE as oe, acquireRateSlots as se, consumeRateSlot as ce, releaseRateSlot as le } from "./rateLimit.js";
|
|
12
|
+
import { encodePayload as ue } from "./encode.js";
|
|
13
|
+
import { TARGETS_TABLE as de, recordDeliveryOutcome as fe } from "./autoDisable.js";
|
|
14
|
+
import { buildDeliverWebhookExecute as pe, deliverWebhookWorkflow as me } from "./deliverWorkflow.js";
|
|
15
|
+
import { _voltroWebhookDeliveriesTable as he, _voltroWebhookEventStatsTable as ge, _voltroWebhookRateWindowsTable as _e, _voltroWebhookTargetsTable as ve, webhookTables as ye } from "./mixin.js";
|
|
16
|
+
import { WEBHOOK_EMIT_EFFECT as be, makeDeferredEmitHandler as xe } from "./deferredEmit.js";
|
|
17
|
+
export { Z as IdempotencyCache, ae as RATE_WINDOW_MS, oe as RATE_WINDOW_TABLE, c as STANDARD_WEBHOOKS_DEFAULT_TOLERANCE_SECONDS, l as STANDARD_WEBHOOKS_ID_HEADER, u as STANDARD_WEBHOOKS_MAX_KEY_BYTES, d as STANDARD_WEBHOOKS_MIN_KEY_BYTES, f as STANDARD_WEBHOOKS_SECRET_PREFIX, p as STANDARD_WEBHOOKS_SIGNATURE_HEADER, m as STANDARD_WEBHOOKS_TIMESTAMP_HEADER, h as STANDARD_WEBHOOKS_VERSION, de as TARGETS_TABLE, ee as UnverifiedIncomingWebhook, e as WEBHOOKS_EGRESS, t as WEBHOOKS_PLUGIN_PERMISSIONS, be as WEBHOOK_EMIT_EFFECT, te as WEBHOOK_VERIFICATION_PROPERTY, F as WebhookDeliveryNotFound, I as WebhookPayloadInvalid, L as WebhookPayloadUnrepresentable, R as WebhookPayloadVersionInvalid, z as WebhookSubscribeInvalid, B as WebhookTargetNotFound, V as WebhooksService, he as _voltroWebhookDeliveriesTable, ge as _voltroWebhookEventStatsTable, _e as _voltroWebhookRateWindowsTable, ve as _voltroWebhookTargetsTable, se as acquireRateSlots, o as asOutgoingEvent, pe as buildDeliverWebhookExecute, H as buildWebhooksService, U as compareVersions, ce as consumeRateSlot, ne as defaultIncomingPath, C as defaultOutgoingSignature, j as defaultRetryPolicy, r as defineIncomingWebhook, i as defineWebhookProvider, me as deliverWebhookWorkflow, ue as encodePayload, W as eventsOf, M as fastRetryPolicy, G as generateSecret, g as generateStandardWebhooksSecret, w as genericHmacSignature, Q as getIdempotencyCache, T as githubSignature, s as hasWebhookAudience, re as incomingWebhookVerification, _ as isStandardWebhooksSecret, a as isWebhookDescriptor, xe as makeDeferredEmitHandler, K as matchesFilter, ie as mountIncomingWebhook, N as nextRetry, P as parseDuration, $ as parseTtl, fe as recordDeliveryOutcome, le as releaseRateSlot, q as resolveSubscribe, E as signRequest, D as slackSignature, v as standardWebhooksHeaders, y as standardWebhooksKey, O as standardWebhooksSignature, b as standardWebhooksSignatureToken, x as standardWebhooksSignedContent, k as stripeSignature, J as useWebhooks, Y as useWebhooksEffect, X as validateSubscribe, A as verifyRequest, S as verifyStandardWebhooks, ye as webhookTables, n as webhooksPlugin };
|