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