@voltro/plugin-webhooks 0.81.0 → 0.83.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 +1806 -6
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +228 -221
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
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 ee, u as te, v as ne, w as re, x as ie, y as ae } from "./signing-DeHObNv6.js";
|
|
2
|
-
import { WebhookDeliveryNotFound as
|
|
3
|
-
import { _voltroWebhookDeliveriesTable as
|
|
4
|
-
import { Context as
|
|
5
|
-
import { definePlugin as
|
|
6
|
-
import { randomBytes as
|
|
7
|
-
import { and as
|
|
8
|
-
import { assertPublicUrl as
|
|
9
|
-
import { createLogger as
|
|
10
|
-
import { Activity as
|
|
11
|
-
import { defineOutboxHandler as
|
|
2
|
+
import { WebhookDeliveryNotFound as oe, WebhookPayloadInvalid as se, WebhookPayloadUnrepresentable as b, WebhookPayloadVersionInvalid as ce, WebhookSubscribeInvalid as x, WebhookTargetNotFound as S } from "./errors.js";
|
|
3
|
+
import { _voltroWebhookDeliveriesTable as le, _voltroWebhookEventStatsTable as ue, _voltroWebhookRateWindowsTable as de, _voltroWebhookTargetsTable as fe, webhookTables as pe } from "./mixin.js";
|
|
4
|
+
import { Context as me, Effect as C, Either as he, Schema as w } from "effect";
|
|
5
|
+
import { definePlugin as ge, publishServerError as _e } from "@voltro/protocol";
|
|
6
|
+
import { randomBytes as ve, randomUUID as T } from "node:crypto";
|
|
7
|
+
import { and as E, eq as D } from "@voltro/database";
|
|
8
|
+
import { assertPublicUrl as ye } from "@voltro/integration-http";
|
|
9
|
+
import { createLogger as O } from "@voltro/logger";
|
|
10
|
+
import { Activity as k, DurableClock as A, Workflow as be } from "@effect/workflow";
|
|
11
|
+
import { defineOutboxHandler as xe } from "@voltro/runtime";
|
|
12
12
|
//#region src/plugin.ts
|
|
13
|
-
var
|
|
13
|
+
var j = ["network:outbound:*"], M = [{
|
|
14
|
+
destination: "runtime:registered webhook targets (_voltro_webhook_targets)",
|
|
15
|
+
purpose: "event deliveries, HMAC-signed",
|
|
16
|
+
data: ["content", "metadata"],
|
|
17
|
+
disable: "remove or pause the targets; an emit then delivers nowhere"
|
|
18
|
+
}], Se = (e = {}) => ge({
|
|
14
19
|
name: e.name ? `@voltro/plugin-webhooks#${e.name}` : "@voltro/plugin-webhooks",
|
|
15
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.",
|
|
16
|
-
permissions:
|
|
17
|
-
|
|
21
|
+
permissions: j,
|
|
22
|
+
egress: M,
|
|
23
|
+
onActivate: (e) => C.sync(() => {
|
|
18
24
|
e.logger.info("webhooks active", { outbound: "network:outbound:* (subscriber-configured target URLs — audit visibility, not a restriction)" });
|
|
19
25
|
})
|
|
20
|
-
}),
|
|
26
|
+
}), Ce = (e) => e.webhook !== void 0, we = (e) => {
|
|
21
27
|
let t = e.webhook;
|
|
22
28
|
if (t !== void 0) return {
|
|
23
29
|
_tag: "outgoingEvent",
|
|
@@ -27,8 +33,8 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
27
33
|
...t.version === void 0 ? {} : { version: t.version },
|
|
28
34
|
...t.rateLimit === void 0 ? {} : { globalRateLimit: t.rateLimit }
|
|
29
35
|
};
|
|
30
|
-
},
|
|
31
|
-
let t =
|
|
36
|
+
}, Te = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, N = (e) => {
|
|
37
|
+
let t = Te.exec(e);
|
|
32
38
|
if (!t) throw Error(`invalid duration literal: ${e}`);
|
|
33
39
|
let n = Number(t[1]), r = t[2] ?? "s";
|
|
34
40
|
switch (r) {
|
|
@@ -47,7 +53,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
47
53
|
502,
|
|
48
54
|
503,
|
|
49
55
|
504
|
|
50
|
-
],
|
|
56
|
+
], Ee = (e, t, n) => {
|
|
51
57
|
if (t >= e.maxAttempts || n?.status !== void 0 && !(e.retryOn ?? P).includes(n.status)) return null;
|
|
52
58
|
if (e.honourRetryAfter !== !1 && n?.retryAfterSeconds !== void 0) {
|
|
53
59
|
let t = n.retryAfterSeconds * 1e3, r = N(e.maxDelay), i = Math.min(t, r);
|
|
@@ -72,7 +78,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
72
78
|
delayMs: Math.max(1, Math.floor(o)),
|
|
73
79
|
baseDelayMs: a
|
|
74
80
|
};
|
|
75
|
-
},
|
|
81
|
+
}, De = () => ({
|
|
76
82
|
strategy: "exponential",
|
|
77
83
|
maxAttempts: 8,
|
|
78
84
|
initialDelay: "5s",
|
|
@@ -80,7 +86,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
80
86
|
retryOn: P,
|
|
81
87
|
honourRetryAfter: !0,
|
|
82
88
|
jitter: "full"
|
|
83
|
-
}),
|
|
89
|
+
}), Oe = () => ({
|
|
84
90
|
strategy: "fixed",
|
|
85
91
|
maxAttempts: 120,
|
|
86
92
|
initialDelay: "30s",
|
|
@@ -88,82 +94,82 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
88
94
|
retryOn: P,
|
|
89
95
|
honourRetryAfter: !0,
|
|
90
96
|
jitter: "full"
|
|
91
|
-
}),
|
|
92
|
-
if (e !== void 0 && t?._tag === "standardWebhooks" && !ne(e)) throw new
|
|
97
|
+
}), ke = class extends me.Tag("@voltro/webhooks/WebhooksService")() {}, Ae = 32, je = 32, Me = () => ve(Ae).toString("hex"), F = (e) => e.events ?? (e.event === void 0 ? [] : [e.event]), I = (e, t) => {
|
|
98
|
+
if (e !== void 0 && t?._tag === "standardWebhooks" && !ne(e)) throw new x({
|
|
93
99
|
field: "secret",
|
|
94
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"
|
|
95
101
|
});
|
|
96
|
-
},
|
|
102
|
+
}, L = (e) => {
|
|
97
103
|
let t;
|
|
98
104
|
try {
|
|
99
105
|
t = new URL(e.url);
|
|
100
106
|
} catch (e) {
|
|
101
|
-
throw new
|
|
107
|
+
throw new x({
|
|
102
108
|
field: "url",
|
|
103
109
|
reason: `invalid subscriber url: ${e.message}`
|
|
104
110
|
});
|
|
105
111
|
}
|
|
106
|
-
if (t.protocol !== "http:" && t.protocol !== "https:") throw new
|
|
112
|
+
if (t.protocol !== "http:" && t.protocol !== "https:") throw new x({
|
|
107
113
|
field: "url",
|
|
108
114
|
reason: `url protocol must be http or https, got ${t.protocol}`
|
|
109
115
|
});
|
|
110
116
|
if (process.env.NODE_ENV === "production") try {
|
|
111
|
-
|
|
117
|
+
ye(e.url);
|
|
112
118
|
} catch (e) {
|
|
113
|
-
throw new
|
|
119
|
+
throw new x({
|
|
114
120
|
field: "url",
|
|
115
121
|
reason: e.message
|
|
116
122
|
});
|
|
117
123
|
}
|
|
118
|
-
let n =
|
|
119
|
-
if (n.length === 0) throw new
|
|
124
|
+
let n = F(e);
|
|
125
|
+
if (n.length === 0) throw new x({
|
|
120
126
|
field: "event",
|
|
121
127
|
reason: "event id required"
|
|
122
128
|
});
|
|
123
|
-
if (e.event !== void 0 && e.events !== void 0) throw new
|
|
129
|
+
if (e.event !== void 0 && e.events !== void 0) throw new x({
|
|
124
130
|
field: "event",
|
|
125
131
|
reason: "pass `event` OR `events`, not both — the row set would be ambiguous"
|
|
126
132
|
});
|
|
127
|
-
if (n.some((e) => e.length === 0)) throw new
|
|
133
|
+
if (n.some((e) => e.length === 0)) throw new x({
|
|
128
134
|
field: "events",
|
|
129
135
|
reason: "event id required"
|
|
130
136
|
});
|
|
131
|
-
if (new Set(n).size !== n.length) throw new
|
|
137
|
+
if (new Set(n).size !== n.length) throw new x({
|
|
132
138
|
field: "events",
|
|
133
139
|
reason: "duplicate event — one row per event, so a repeat would deliver twice"
|
|
134
140
|
});
|
|
135
|
-
if (e.secret !== void 0 && e.secret.length <
|
|
141
|
+
if (e.secret !== void 0 && e.secret.length < je) throw new x({
|
|
136
142
|
field: "secret",
|
|
137
|
-
reason: `secret must be at least ${
|
|
143
|
+
reason: `secret must be at least ${je} chars`
|
|
138
144
|
});
|
|
139
|
-
if (
|
|
140
|
-
if (e.retry.maxAttempts < 1) throw new
|
|
145
|
+
if (I(e.secret, e.signing), e.retry !== void 0) {
|
|
146
|
+
if (e.retry.maxAttempts < 1) throw new x({
|
|
141
147
|
field: "retry",
|
|
142
148
|
reason: "retry.maxAttempts must be ≥ 1"
|
|
143
149
|
});
|
|
144
|
-
if (e.retry.maxAttempts > 1e3) throw new
|
|
150
|
+
if (e.retry.maxAttempts > 1e3) throw new x({
|
|
145
151
|
field: "retry",
|
|
146
152
|
reason: "retry.maxAttempts must be ≤ 1000"
|
|
147
153
|
});
|
|
148
154
|
}
|
|
149
|
-
if (e.rateLimitPerMinute !== void 0 && e.rateLimitPerMinute < 1) throw new
|
|
155
|
+
if (e.rateLimitPerMinute !== void 0 && e.rateLimitPerMinute < 1) throw new x({
|
|
150
156
|
field: "rateLimitPerMinute",
|
|
151
157
|
reason: "rateLimitPerMinute must be ≥ 1"
|
|
152
158
|
});
|
|
153
|
-
if (e.autoDisableAfter !== void 0 && (e.autoDisableAfter < 1 || !Number.isInteger(e.autoDisableAfter))) throw new
|
|
159
|
+
if (e.autoDisableAfter !== void 0 && (e.autoDisableAfter < 1 || !Number.isInteger(e.autoDisableAfter))) throw new x({
|
|
154
160
|
field: "autoDisableAfter",
|
|
155
161
|
reason: `autoDisableAfter must be a positive integer, got ${e.autoDisableAfter}`
|
|
156
162
|
});
|
|
157
|
-
},
|
|
158
|
-
let a = e.signing ?? t?.defaultSigning ?? ee(), o = a._tag === "standardWebhooks" ? i :
|
|
159
|
-
return
|
|
160
|
-
id:
|
|
163
|
+
}, R = (e, t, n, r) => {
|
|
164
|
+
let a = e.signing ?? t?.defaultSigning ?? ee(), o = a._tag === "standardWebhooks" ? i : Me, s = r ?? e.secret ?? o();
|
|
165
|
+
return I(s, a), {
|
|
166
|
+
id: T(),
|
|
161
167
|
tenantId: e.tenantId ?? null,
|
|
162
168
|
event: n ?? e.event ?? "",
|
|
163
169
|
url: e.url,
|
|
164
170
|
secret: s,
|
|
165
171
|
signing: a,
|
|
166
|
-
retry: e.retry ?? t?.defaultRetry ??
|
|
172
|
+
retry: e.retry ?? t?.defaultRetry ?? De(),
|
|
167
173
|
filter: e.filter ?? null,
|
|
168
174
|
scope: e.scope ?? null,
|
|
169
175
|
headers: e.headers ?? null,
|
|
@@ -177,14 +183,14 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
177
183
|
payloadVersion: e.payloadVersion ?? t?.version ?? 1,
|
|
178
184
|
description: e.description ?? null
|
|
179
185
|
};
|
|
180
|
-
},
|
|
186
|
+
}, Ne = (e) => e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : 0, z = (e) => e instanceof Date ? e.toISOString() : typeof e == "string" ? e : null, Pe = (e) => {
|
|
181
187
|
if (typeof e != "string") return e;
|
|
182
188
|
try {
|
|
183
189
|
return JSON.parse(e);
|
|
184
190
|
} catch {
|
|
185
191
|
return e;
|
|
186
192
|
}
|
|
187
|
-
},
|
|
193
|
+
}, B = (e) => ({
|
|
188
194
|
id: String(e.id),
|
|
189
195
|
deliveryId: String(e.deliveryId),
|
|
190
196
|
targetId: String(e.targetId),
|
|
@@ -195,28 +201,28 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
195
201
|
responseStatus: e.responseStatus ?? null,
|
|
196
202
|
errorMessage: e.errorMessage ?? null,
|
|
197
203
|
latencyMs: e.latencyMs ?? null,
|
|
198
|
-
scheduledAt:
|
|
199
|
-
nextAttemptAt:
|
|
200
|
-
createdAt:
|
|
201
|
-
}),
|
|
204
|
+
scheduledAt: z(e.scheduledAt),
|
|
205
|
+
nextAttemptAt: z(e.nextAttemptAt),
|
|
206
|
+
createdAt: z(e.createdAt)
|
|
207
|
+
}), Fe = (e, t) => e === t ? "current" : e < t ? "behind" : "ahead", V = (e, t) => {
|
|
202
208
|
if (e === null) return !0;
|
|
203
209
|
let n = { payload: t };
|
|
204
|
-
for (let [t, r] of Object.entries(e)) if (!
|
|
210
|
+
for (let [t, r] of Object.entries(e)) if (!Le(Ie(n, t), r)) return !1;
|
|
205
211
|
return !0;
|
|
206
|
-
},
|
|
212
|
+
}, Ie = (e, t) => {
|
|
207
213
|
let n = t.split("."), r = e;
|
|
208
214
|
for (let e of n) {
|
|
209
215
|
if (typeof r != "object" || !r) return;
|
|
210
216
|
r = r[e];
|
|
211
217
|
}
|
|
212
218
|
return r;
|
|
213
|
-
},
|
|
219
|
+
}, H = (e, t) => {
|
|
214
220
|
if (typeof e == "number" && typeof t == "number") return e - t;
|
|
215
221
|
if (typeof e == "string" && typeof t == "string") return e < t ? -1 : +(e > t);
|
|
216
|
-
},
|
|
222
|
+
}, Le = (e, t) => {
|
|
217
223
|
if (typeof t == "object" && t && !Array.isArray(t)) {
|
|
218
224
|
let n = t;
|
|
219
|
-
return "eq" in n && e !== n.eq || "in" in n && (!Array.isArray(n.in) || !n.in.includes(e)) || "gt" in n && !((
|
|
225
|
+
return "eq" in n && e !== n.eq || "in" in n && (!Array.isArray(n.in) || !n.in.includes(e)) || "gt" in n && !((H(e, n.gt) ?? 0) > 0) || "gte" in n && !((H(e, n.gte) ?? -1) >= 0) || "lt" in n && !((H(e, n.lt) ?? 0) < 0) || "lte" in n && !((H(e, n.lte) ?? 1) <= 0) ? !1 : [
|
|
220
226
|
"eq",
|
|
221
227
|
"in",
|
|
222
228
|
"gt",
|
|
@@ -226,9 +232,9 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
226
232
|
].some((e) => e in n);
|
|
227
233
|
}
|
|
228
234
|
return e === t;
|
|
229
|
-
},
|
|
235
|
+
}, U = (e, t) => e !== null && Object.entries(t).every(([t, n]) => e[t] === n), W = 500, Re = 200, G = async (e, t) => {
|
|
230
236
|
let n = [];
|
|
231
|
-
for (let r = 0; r <
|
|
237
|
+
for (let r = 0; r < Re; r += 1) {
|
|
232
238
|
let i = await e.store.query({
|
|
233
239
|
table: "_voltro_webhook_targets",
|
|
234
240
|
predicate: t,
|
|
@@ -242,18 +248,18 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
242
248
|
});
|
|
243
249
|
if (n.push(...i), i.length < W) return n;
|
|
244
250
|
}
|
|
245
|
-
throw Error(`webhooks: more than ${
|
|
246
|
-
},
|
|
247
|
-
let n = (await G(e)).filter((e) =>
|
|
251
|
+
throw Error(`webhooks: more than ${Re * W} 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
|
+
}, ze = async (e, t) => {
|
|
253
|
+
let n = (await G(e)).filter((e) => U(e.scope, t));
|
|
248
254
|
if (n.length === 0) return;
|
|
249
255
|
let r = new Set(n.map((e) => e.secret));
|
|
250
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.`);
|
|
251
257
|
return [...r][0];
|
|
252
|
-
},
|
|
258
|
+
}, Be = async (e, t) => {
|
|
253
259
|
try {
|
|
254
260
|
let n = await e.store.query({
|
|
255
261
|
table: "_voltro_webhook_event_stats",
|
|
256
|
-
predicate:
|
|
262
|
+
predicate: D("event", t),
|
|
257
263
|
order: [],
|
|
258
264
|
take: 1,
|
|
259
265
|
skip: void 0,
|
|
@@ -275,11 +281,11 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
275
281
|
updatedAt: r
|
|
276
282
|
});
|
|
277
283
|
} catch {}
|
|
278
|
-
},
|
|
284
|
+
}, Ve = (e, t, n = {}) => {
|
|
279
285
|
let r = new Map((n.events ?? []).map((e) => [e.id, e])), a = {
|
|
280
286
|
subscribe: async (t) => {
|
|
281
|
-
|
|
282
|
-
let n =
|
|
287
|
+
L(t);
|
|
288
|
+
let n = F(t), a = t.secret === void 0 && t.scope !== void 0 ? await ze(e, t.scope) : void 0, o = t.secret ?? a ?? i(), s = n.map((e) => R(t, r.get(e), e, o));
|
|
283
289
|
for (let t of s) await e.store.insert("_voltro_webhook_targets", {
|
|
284
290
|
...t,
|
|
285
291
|
createdAt: /* @__PURE__ */ new Date(),
|
|
@@ -300,19 +306,19 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
300
306
|
};
|
|
301
307
|
},
|
|
302
308
|
emit: async (n, i, a) => {
|
|
303
|
-
let o = typeof n == "object" && n.kind === "event" ?
|
|
309
|
+
let o = typeof n == "object" && n.kind === "event" ? we(n) : void 0, s = typeof n == "string" ? r.get(n) : o ?? n, c = typeof n == "string" ? n : n.id ?? n.name;
|
|
304
310
|
if (s !== void 0) {
|
|
305
|
-
let e =
|
|
306
|
-
if (
|
|
311
|
+
let e = w.decodeUnknownEither(s.payload)(i, { errors: "all" });
|
|
312
|
+
if (he.isLeft(e)) throw new se({
|
|
307
313
|
event: c,
|
|
308
314
|
issues: e.left.message
|
|
309
315
|
});
|
|
310
316
|
}
|
|
311
|
-
let l =
|
|
312
|
-
await
|
|
313
|
-
let f = d.filter((e) =>
|
|
317
|
+
let l = T(), u = a?.tenantId, d = await G(e, u == null ? D("event", c) : E(D("event", c), D("tenantId", u)));
|
|
318
|
+
await Be(e, c);
|
|
319
|
+
let f = d.filter((e) => V(e.filter, i)), p = [], m = JSON.stringify(i);
|
|
314
320
|
for (let n of f) {
|
|
315
|
-
let r =
|
|
321
|
+
let r = T();
|
|
316
322
|
n.active ? (await t({
|
|
317
323
|
deliveryId: r,
|
|
318
324
|
targetId: n.id,
|
|
@@ -350,7 +356,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
350
356
|
replay: async (n) => {
|
|
351
357
|
let r = (await e.store.query({
|
|
352
358
|
table: "_voltro_webhook_deliveries",
|
|
353
|
-
predicate:
|
|
359
|
+
predicate: D("deliveryId", n),
|
|
354
360
|
order: [{
|
|
355
361
|
column: "attempt",
|
|
356
362
|
direction: "asc"
|
|
@@ -359,12 +365,12 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
359
365
|
skip: void 0,
|
|
360
366
|
projection: void 0
|
|
361
367
|
}))[0];
|
|
362
|
-
if (r === void 0) throw new
|
|
368
|
+
if (r === void 0) throw new oe({ deliveryId: n });
|
|
363
369
|
await t({
|
|
364
370
|
deliveryId: r.deliveryId,
|
|
365
371
|
targetId: r.targetId,
|
|
366
372
|
event: r.event,
|
|
367
|
-
eventId: r.eventId ??
|
|
373
|
+
eventId: r.eventId ?? T(),
|
|
368
374
|
payloadJson: typeof r.payload == "string" ? r.payload : JSON.stringify(r.payload),
|
|
369
375
|
attemptEpoch: Date.now()
|
|
370
376
|
});
|
|
@@ -373,15 +379,15 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
373
379
|
if (typeof t == "string") {
|
|
374
380
|
if ((await e.store.query({
|
|
375
381
|
table: "_voltro_webhook_targets",
|
|
376
|
-
predicate:
|
|
382
|
+
predicate: D("id", t),
|
|
377
383
|
order: [],
|
|
378
384
|
take: 1,
|
|
379
385
|
skip: void 0,
|
|
380
386
|
projection: ["id"]
|
|
381
|
-
})).length === 0) throw new
|
|
387
|
+
})).length === 0) throw new S({ targetId: t });
|
|
382
388
|
return [t];
|
|
383
389
|
}
|
|
384
|
-
let n = (await G(e)).filter((e) =>
|
|
390
|
+
let n = (await G(e)).filter((e) => U(e.scope, t.scope)).map((e) => e.id);
|
|
385
391
|
if (n.length === 0) throw Error(`webhooks: no target matches scope ${JSON.stringify(t.scope)} — the operation would have silently done nothing`);
|
|
386
392
|
return n;
|
|
387
393
|
},
|
|
@@ -402,7 +408,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
402
408
|
});
|
|
403
409
|
let n = await e.store.query({
|
|
404
410
|
table: "_voltro_webhook_deliveries",
|
|
405
|
-
predicate: D(
|
|
411
|
+
predicate: E(D("targetId", r), D("status", "pending")),
|
|
406
412
|
order: [{
|
|
407
413
|
column: "createdAt",
|
|
408
414
|
direction: "asc"
|
|
@@ -415,14 +421,14 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
415
421
|
deliveryId: e.deliveryId,
|
|
416
422
|
targetId: r,
|
|
417
423
|
event: e.event,
|
|
418
|
-
eventId: e.eventId ??
|
|
424
|
+
eventId: e.eventId ?? T(),
|
|
419
425
|
payloadJson: typeof e.payload == "string" ? e.payload : JSON.stringify(e.payload),
|
|
420
426
|
attemptEpoch: i
|
|
421
427
|
});
|
|
422
428
|
}
|
|
423
429
|
},
|
|
424
430
|
deleteTarget: async (t) => {
|
|
425
|
-
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(
|
|
431
|
+
for (let n of await a.resolveTargets(t)) await e.store.delete("_voltro_webhook_targets", n), await e.store.deleteMany("_voltro_webhook_deliveries", { where: E(D("targetId", n), D("status", "pending")) });
|
|
426
432
|
},
|
|
427
433
|
rotateSecret: async (t) => {
|
|
428
434
|
let n = i();
|
|
@@ -433,7 +439,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
433
439
|
return { secret: n };
|
|
434
440
|
},
|
|
435
441
|
updateTarget: async (t, n) => {
|
|
436
|
-
if (n.url !== void 0 &&
|
|
442
|
+
if (n.url !== void 0 && L({
|
|
437
443
|
event: "x",
|
|
438
444
|
url: n.url
|
|
439
445
|
}), 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");
|
|
@@ -453,14 +459,14 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
453
459
|
testTarget: async (n, r) => {
|
|
454
460
|
let i = (await e.store.query({
|
|
455
461
|
table: "_voltro_webhook_targets",
|
|
456
|
-
predicate:
|
|
462
|
+
predicate: D("id", n),
|
|
457
463
|
order: [],
|
|
458
464
|
take: 1,
|
|
459
465
|
skip: void 0,
|
|
460
466
|
projection: void 0
|
|
461
467
|
}))[0];
|
|
462
|
-
if (i === void 0) throw new
|
|
463
|
-
let a =
|
|
468
|
+
if (i === void 0) throw new S({ targetId: n });
|
|
469
|
+
let a = T(), o = JSON.stringify(r ?? {
|
|
464
470
|
test: !0,
|
|
465
471
|
targetId: n,
|
|
466
472
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -469,7 +475,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
469
475
|
deliveryId: a,
|
|
470
476
|
targetId: i.id,
|
|
471
477
|
event: i.event,
|
|
472
|
-
eventId:
|
|
478
|
+
eventId: T(),
|
|
473
479
|
payloadJson: o
|
|
474
480
|
}) : await e.store.insert("_voltro_webhook_deliveries", {
|
|
475
481
|
id: `${a}:1`,
|
|
@@ -489,7 +495,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
489
495
|
listDeliveries: async (t) => {
|
|
490
496
|
let n = t?.scope === void 0 ? void 0 : new Set(await a.resolveTargets({ scope: t.scope })), r = await e.store.query({
|
|
491
497
|
table: "_voltro_webhook_deliveries",
|
|
492
|
-
predicate: t?.targetId === void 0 ? void 0 :
|
|
498
|
+
predicate: t?.targetId === void 0 ? void 0 : D("targetId", t.targetId),
|
|
493
499
|
order: [{
|
|
494
500
|
column: "createdAt",
|
|
495
501
|
direction: "desc"
|
|
@@ -498,25 +504,25 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
498
504
|
skip: void 0,
|
|
499
505
|
projection: void 0
|
|
500
506
|
}), i = t?.since?.getTime();
|
|
501
|
-
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 ||
|
|
507
|
+
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 || Ne(e.createdAt) >= i).slice(0, t?.limit ?? 200).map(B);
|
|
502
508
|
},
|
|
503
509
|
getDelivery: async (t) => {
|
|
504
510
|
let n = (await e.store.query({
|
|
505
511
|
table: "_voltro_webhook_deliveries",
|
|
506
|
-
predicate:
|
|
512
|
+
predicate: D("id", t),
|
|
507
513
|
order: [],
|
|
508
514
|
take: 1,
|
|
509
515
|
skip: void 0,
|
|
510
516
|
projection: void 0
|
|
511
517
|
}))[0];
|
|
512
518
|
return n === void 0 ? null : {
|
|
513
|
-
...
|
|
514
|
-
payload:
|
|
519
|
+
...B(n),
|
|
520
|
+
payload: Pe(n.payload),
|
|
515
521
|
responseBody: n.responseBody ?? null
|
|
516
522
|
};
|
|
517
523
|
},
|
|
518
524
|
updateTargetPayloadVersion: async (t, n) => {
|
|
519
|
-
if (n < 1 || !Number.isInteger(n)) throw new
|
|
525
|
+
if (n < 1 || !Number.isInteger(n)) throw new ce({ version: n });
|
|
520
526
|
await a.resolveTargets(t), await e.store.update("_voltro_webhook_targets", t, {
|
|
521
527
|
payloadVersion: n,
|
|
522
528
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -525,7 +531,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
525
531
|
listTargets: async (t, n) => {
|
|
526
532
|
let r = await e.store.query({
|
|
527
533
|
table: "_voltro_webhook_targets",
|
|
528
|
-
predicate: t === void 0 ? void 0 :
|
|
534
|
+
predicate: t === void 0 ? void 0 : D("event", t),
|
|
529
535
|
order: [{
|
|
530
536
|
column: "createdAt",
|
|
531
537
|
direction: "desc"
|
|
@@ -546,20 +552,21 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
546
552
|
consecutiveFailures: e.consecutiveFailures ?? 0,
|
|
547
553
|
autoDisabledAt: e.autoDisabledAt === null || e.autoDisabledAt === void 0 ? null : e.autoDisabledAt instanceof Date ? e.autoDisabledAt.toISOString() : String(e.autoDisabledAt),
|
|
548
554
|
autoDisableReason: e.autoDisableReason,
|
|
549
|
-
scope: e.scope
|
|
555
|
+
scope: e.scope,
|
|
556
|
+
filter: e.filter ?? null
|
|
550
557
|
}));
|
|
551
558
|
}
|
|
552
559
|
};
|
|
553
560
|
return a;
|
|
554
|
-
},
|
|
555
|
-
return yield*
|
|
556
|
-
}),
|
|
561
|
+
}, He = C.gen(function* () {
|
|
562
|
+
return yield* ke;
|
|
563
|
+
}), Ue = (e) => {
|
|
557
564
|
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.");
|
|
558
565
|
return e.webhooks;
|
|
559
|
-
},
|
|
566
|
+
}, We = 1e4, Ge = class {
|
|
560
567
|
capacity;
|
|
561
568
|
cache = /* @__PURE__ */ new Map();
|
|
562
|
-
constructor(e =
|
|
569
|
+
constructor(e = We) {
|
|
563
570
|
this.capacity = e;
|
|
564
571
|
}
|
|
565
572
|
evictExpired() {
|
|
@@ -594,36 +601,36 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
594
601
|
clear() {
|
|
595
602
|
this.cache.clear();
|
|
596
603
|
}
|
|
597
|
-
}, K = null,
|
|
604
|
+
}, K = null, Ke = () => (K === null && (K = new Ge()), K), q = {
|
|
598
605
|
"5m": 3e5,
|
|
599
606
|
"1h": 36e5,
|
|
600
607
|
"6h": 216e5,
|
|
601
608
|
"1d": 864e5,
|
|
602
609
|
"7d": 6048e5,
|
|
603
610
|
"30d": 2592e6
|
|
604
|
-
},
|
|
611
|
+
}, qe = (e) => e === void 0 ? q["7d"] : q[e] ?? q["7d"], J = (e, t) => ({
|
|
605
612
|
status: e,
|
|
606
613
|
contentType: "application/json; charset=utf-8",
|
|
607
614
|
body: JSON.stringify(t)
|
|
608
|
-
}),
|
|
615
|
+
}), Je = "voltroWebhookVerification", Ye = class extends Error {
|
|
609
616
|
webhookId;
|
|
610
617
|
name = "UnverifiedIncomingWebhook";
|
|
611
618
|
constructor(e) {
|
|
612
619
|
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;
|
|
613
620
|
}
|
|
614
|
-
},
|
|
621
|
+
}, Xe = (e) => `VOLTRO_WEBHOOK_SECRET_${e.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`, Ze = (e) => {
|
|
615
622
|
let t = e.signature ?? e.provider?.signature ?? null;
|
|
616
623
|
return (e.verification === void 0 || e.verification === "signature") && t === null ? null : e.verification ?? "signature";
|
|
617
|
-
},
|
|
618
|
-
let n = e.provider, r = e.signature ?? n?.signature ?? null, i = e.idempotency ?? n?.idempotency ?? null, a = e.bodyType ?? n?.bodyType ?? "json", o =
|
|
619
|
-
if (o === null) throw new
|
|
624
|
+
}, Qe = (e, t) => {
|
|
625
|
+
let n = e.provider, r = e.signature ?? n?.signature ?? null, i = e.idempotency ?? n?.idempotency ?? null, a = e.bodyType ?? n?.bodyType ?? "json", o = Ze(e);
|
|
626
|
+
if (o === null) throw new Ye(e.id);
|
|
620
627
|
let c = o;
|
|
621
|
-
c === "none" &&
|
|
628
|
+
c === "none" && O({ scope: `webhook:${e.id}` }).warn("incoming webhook mounted WITHOUT verification — anyone who can reach this URL can run its handler", {
|
|
622
629
|
webhookId: e.id,
|
|
623
630
|
verification: "none"
|
|
624
631
|
});
|
|
625
|
-
let l = t.idempotencyCache ??
|
|
626
|
-
|
|
632
|
+
let l = t.idempotencyCache ?? Ke(), u = qe(i?.ttl), d = t.log ?? ((e) => {
|
|
633
|
+
O({ scope: `webhook:${e.webhookId}` }).debug("incoming webhook", {
|
|
627
634
|
status: e.status,
|
|
628
635
|
signatureOk: e.signatureOk,
|
|
629
636
|
idempotency: e.idempotency,
|
|
@@ -650,7 +657,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
650
657
|
if (r !== null && c === "signature") {
|
|
651
658
|
let i = await t.resolveSecret(e.id);
|
|
652
659
|
if (i === null) {
|
|
653
|
-
let t = `no signing secret configured — set ${
|
|
660
|
+
let t = `no signing secret configured — set ${Xe(e.id)}`, n = J(503, {
|
|
654
661
|
error: "webhook not configured",
|
|
655
662
|
reason: t
|
|
656
663
|
});
|
|
@@ -711,7 +718,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
711
718
|
}
|
|
712
719
|
let m;
|
|
713
720
|
try {
|
|
714
|
-
m =
|
|
721
|
+
m = w.decodeUnknownSync(e.payload)(p);
|
|
715
722
|
} catch (t) {
|
|
716
723
|
let n = J(422, {
|
|
717
724
|
error: "schema validation failed",
|
|
@@ -789,28 +796,28 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
789
796
|
idempotency: g,
|
|
790
797
|
durationMs: Date.now() - o,
|
|
791
798
|
errorMessage: t.message
|
|
792
|
-
}),
|
|
799
|
+
}), _e({
|
|
793
800
|
error: t,
|
|
794
801
|
source: "webhook",
|
|
795
802
|
name: e.id,
|
|
796
803
|
fields: { status: 500 }
|
|
797
804
|
}), n;
|
|
798
805
|
}
|
|
799
|
-
}, { [
|
|
800
|
-
},
|
|
806
|
+
}, { [Je]: c });
|
|
807
|
+
}, $e = (e) => `/webhooks/${e}`, et = "_voltro_webhook_rate_windows", tt = 6e4, nt = (e, t) => `${e}@${t}`, rt = async (e, t, n) => (await e.query({
|
|
801
808
|
table: "_voltro_webhook_rate_windows",
|
|
802
|
-
predicate: D(
|
|
809
|
+
predicate: E(D("scope", t), D("bucket", n)),
|
|
803
810
|
order: [],
|
|
804
811
|
take: 1,
|
|
805
812
|
skip: void 0,
|
|
806
813
|
projection: void 0
|
|
807
|
-
}))[0] ?? null, Y = 32,
|
|
814
|
+
}))[0] ?? null, Y = 32, X = async (e, t, n) => {
|
|
808
815
|
for (let r = 0; r < Y; r++) {
|
|
809
816
|
r > 0 && await new Promise((e) => setTimeout(e, r));
|
|
810
|
-
let i = await
|
|
817
|
+
let i = await rt(e, t.key, n);
|
|
811
818
|
if (i === null) {
|
|
812
|
-
await e.insertIgnore(
|
|
813
|
-
id:
|
|
819
|
+
await e.insertIgnore(et, {
|
|
820
|
+
id: nt(t.key, n),
|
|
814
821
|
scope: t.key,
|
|
815
822
|
bucket: n,
|
|
816
823
|
count: 0,
|
|
@@ -823,27 +830,27 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
823
830
|
if (await e.updateMany("_voltro_webhook_rate_windows", {
|
|
824
831
|
count: i.count + 1,
|
|
825
832
|
updatedAt: /* @__PURE__ */ new Date()
|
|
826
|
-
}, { where: D(
|
|
833
|
+
}, { where: E(D("id", i.id), D("count", i.count)) }) === 1) return !0;
|
|
827
834
|
}
|
|
828
835
|
throw Error(`webhook rate window: contention on (${t.key}, ${n}) exceeded ${Y} attempts`);
|
|
829
836
|
}, it = async (e, t, n) => {
|
|
830
837
|
for (let r = 0; r < Y; r++) {
|
|
831
838
|
r > 0 && await new Promise((e) => setTimeout(e, r));
|
|
832
|
-
let i = await
|
|
839
|
+
let i = await rt(e, t, n);
|
|
833
840
|
if (i === null || i.count <= 0 || await e.updateMany("_voltro_webhook_rate_windows", {
|
|
834
841
|
count: i.count - 1,
|
|
835
842
|
updatedAt: /* @__PURE__ */ new Date()
|
|
836
|
-
}, { where: D(
|
|
843
|
+
}, { where: E(D("id", i.id), D("count", i.count)) }) === 1) return;
|
|
837
844
|
}
|
|
838
845
|
throw Error(`webhook rate window: release contention on (${t}) exceeded ${Y} attempts`);
|
|
839
|
-
}, at = async (e, t, n, r =
|
|
846
|
+
}, at = async (e, t, n, r = tt) => {
|
|
840
847
|
if (t.length === 0) return {
|
|
841
848
|
acquired: !0,
|
|
842
849
|
retryInMs: 0
|
|
843
850
|
};
|
|
844
851
|
let i = Math.floor(n / r), a = [];
|
|
845
852
|
for (let o of t) {
|
|
846
|
-
if (await
|
|
853
|
+
if (await X(e, o, i)) {
|
|
847
854
|
a.push(o);
|
|
848
855
|
continue;
|
|
849
856
|
}
|
|
@@ -866,7 +873,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
866
873
|
try {
|
|
867
874
|
n = JSON.parse(t);
|
|
868
875
|
} catch (t) {
|
|
869
|
-
throw new
|
|
876
|
+
throw new b({
|
|
870
877
|
format: e,
|
|
871
878
|
reason: `payload is not valid JSON: ${t.message}`
|
|
872
879
|
});
|
|
@@ -879,34 +886,34 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
879
886
|
bytes: Buffer.from(lt(n), "utf8")
|
|
880
887
|
};
|
|
881
888
|
}, st = (e) => {
|
|
882
|
-
if (typeof e != "object" || !e || Array.isArray(e)) throw new
|
|
889
|
+
if (typeof e != "object" || !e || Array.isArray(e)) throw new b({
|
|
883
890
|
format: "form",
|
|
884
891
|
reason: `form encoding requires a JSON object at the top level, got ${ft(e)}`
|
|
885
892
|
});
|
|
886
893
|
let t = new URLSearchParams();
|
|
887
|
-
for (let [n, r] of Object.entries(e))
|
|
894
|
+
for (let [n, r] of Object.entries(e)) Z(t, n, r);
|
|
888
895
|
return t.toString();
|
|
889
|
-
},
|
|
896
|
+
}, Z = (e, t, n) => {
|
|
890
897
|
if (n === null) {
|
|
891
898
|
e.append(t, "");
|
|
892
899
|
return;
|
|
893
900
|
}
|
|
894
901
|
if (Array.isArray(n)) {
|
|
895
|
-
n.forEach((n, r) =>
|
|
902
|
+
n.forEach((n, r) => Z(e, `${t}[${r}]`, n));
|
|
896
903
|
return;
|
|
897
904
|
}
|
|
898
905
|
if (typeof n == "object") {
|
|
899
|
-
for (let [r, i] of Object.entries(n))
|
|
906
|
+
for (let [r, i] of Object.entries(n)) Z(e, `${t}[${r}]`, i);
|
|
900
907
|
return;
|
|
901
908
|
}
|
|
902
909
|
e.append(t, String(n));
|
|
903
|
-
}, ct = /^[A-Za-z_][A-Za-z0-9_.-]*$/, lt = (e) => `<?xml version="1.0" encoding="UTF-8"?>${
|
|
904
|
-
if (!ct.test(e)) throw new
|
|
910
|
+
}, ct = /^[A-Za-z_][A-Za-z0-9_.-]*$/, lt = (e) => `<?xml version="1.0" encoding="UTF-8"?>${Q("webhook", e)}`, Q = (e, t) => {
|
|
911
|
+
if (!ct.test(e)) throw new b({
|
|
905
912
|
format: "xml",
|
|
906
913
|
reason: `object key ${JSON.stringify(e)} is not a valid XML element name`
|
|
907
914
|
});
|
|
908
915
|
return `<${e}>${ut(e, t)}</${e}>`;
|
|
909
|
-
}, ut = (e, t) => t == null ? "" : Array.isArray(t) ? t.map((e) =>
|
|
916
|
+
}, 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) => {
|
|
910
917
|
switch (e) {
|
|
911
918
|
case "&": return "&";
|
|
912
919
|
case "<": return "<";
|
|
@@ -916,12 +923,12 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
916
923
|
}
|
|
917
924
|
}), ft = (e) => e === null ? "null" : Array.isArray(e) ? "array" : typeof e, pt = "_voltro_webhook_targets", mt = 32, ht = async (e, t) => (await e.query({
|
|
918
925
|
table: "_voltro_webhook_targets",
|
|
919
|
-
predicate:
|
|
926
|
+
predicate: D("id", t),
|
|
920
927
|
order: [],
|
|
921
928
|
take: 1,
|
|
922
929
|
skip: void 0,
|
|
923
930
|
projection: void 0
|
|
924
|
-
}))[0] ?? null,
|
|
931
|
+
}))[0] ?? null, gt = async (e, t, n, r, i = /* @__PURE__ */ new Date(), a = !1) => {
|
|
925
932
|
for (let o = 0; o < mt; o++) {
|
|
926
933
|
o > 0 && await new Promise((e) => setTimeout(e, o));
|
|
927
934
|
let s = await ht(e, t);
|
|
@@ -934,7 +941,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
934
941
|
if (c === 0 || await e.updateMany("_voltro_webhook_targets", {
|
|
935
942
|
consecutiveFailures: 0,
|
|
936
943
|
updatedAt: i
|
|
937
|
-
}, { where: D(
|
|
944
|
+
}, { where: E(D("id", t), D("consecutiveFailures", c)) }) === 1) return {
|
|
938
945
|
consecutiveFailures: 0,
|
|
939
946
|
autoDisabled: !1
|
|
940
947
|
};
|
|
@@ -944,72 +951,72 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
944
951
|
consecutiveFailures: l,
|
|
945
952
|
updatedAt: i
|
|
946
953
|
};
|
|
947
|
-
if (d && (f.active = !1, f.autoDisabledAt = i, f.autoDisableReason = r.slice(0, 500)), await e.updateMany("_voltro_webhook_targets", f, { where: D(
|
|
954
|
+
if (d && (f.active = !1, f.autoDisabledAt = i, f.autoDisableReason = r.slice(0, 500)), await e.updateMany("_voltro_webhook_targets", f, { where: E(D("id", t), D("consecutiveFailures", c)) }) === 1) return {
|
|
948
955
|
consecutiveFailures: l,
|
|
949
956
|
autoDisabled: d
|
|
950
957
|
};
|
|
951
958
|
}
|
|
952
959
|
throw Error(`webhook auto-disable: streak CAS contention on target ${t} exceeded ${mt} attempts`);
|
|
953
|
-
},
|
|
954
|
-
status:
|
|
955
|
-
body:
|
|
956
|
-
latencyMs:
|
|
957
|
-
retryAfterSec:
|
|
958
|
-
transportError:
|
|
959
|
-
encodeError:
|
|
960
|
-
}),
|
|
961
|
-
outcome:
|
|
962
|
-
delayMs:
|
|
963
|
-
reason:
|
|
964
|
-
autoDisabled:
|
|
965
|
-
consecutiveFailures:
|
|
966
|
-
}),
|
|
967
|
-
acquired:
|
|
968
|
-
retryInMs:
|
|
969
|
-
}),
|
|
970
|
-
deliveryId:
|
|
971
|
-
targetId:
|
|
972
|
-
event:
|
|
973
|
-
eventId:
|
|
974
|
-
payloadJson:
|
|
975
|
-
attemptEpoch:
|
|
976
|
-
},
|
|
960
|
+
}, _t = w.Struct({
|
|
961
|
+
status: w.Number,
|
|
962
|
+
body: w.String,
|
|
963
|
+
latencyMs: w.Number,
|
|
964
|
+
retryAfterSec: w.Union(w.Number, w.Undefined),
|
|
965
|
+
transportError: w.Union(w.String, w.Undefined),
|
|
966
|
+
encodeError: w.Union(w.String, w.Undefined)
|
|
967
|
+
}), vt = w.Struct({
|
|
968
|
+
outcome: w.Literal("succeeded", "failed", "retry"),
|
|
969
|
+
delayMs: w.Union(w.Number, w.Undefined),
|
|
970
|
+
reason: w.Union(w.String, w.Undefined),
|
|
971
|
+
autoDisabled: w.Union(w.Boolean, w.Undefined),
|
|
972
|
+
consecutiveFailures: w.Union(w.Number, w.Undefined)
|
|
973
|
+
}), yt = w.Struct({
|
|
974
|
+
acquired: w.Boolean,
|
|
975
|
+
retryInMs: w.Number
|
|
976
|
+
}), bt = {
|
|
977
|
+
deliveryId: w.String,
|
|
978
|
+
targetId: w.String,
|
|
979
|
+
event: w.String,
|
|
980
|
+
eventId: w.String,
|
|
981
|
+
payloadJson: w.String,
|
|
982
|
+
attemptEpoch: w.optionalWith(w.Number, { default: () => 0 })
|
|
983
|
+
}, xt = be.make({
|
|
977
984
|
name: "voltro.deliverWebhook",
|
|
978
|
-
payload:
|
|
979
|
-
success:
|
|
980
|
-
finalStatus:
|
|
981
|
-
attempts:
|
|
985
|
+
payload: bt,
|
|
986
|
+
success: w.Struct({
|
|
987
|
+
finalStatus: w.Literal("succeeded", "failed", "deferred"),
|
|
988
|
+
attempts: w.Number
|
|
982
989
|
}),
|
|
983
990
|
idempotencyKey: ({ deliveryId: e, attemptEpoch: t }) => `voltro.deliverWebhook:${e}:${t ?? 0}`
|
|
984
|
-
}),
|
|
991
|
+
}), St = 3e4, Ct = (e, t) => {
|
|
985
992
|
if (e !== void 0) return e;
|
|
986
993
|
let n = Number(t.VOLTRO_WEBHOOK_TIMEOUT_MS);
|
|
987
|
-
return Number.isFinite(n) && n > 0 ? n :
|
|
988
|
-
},
|
|
989
|
-
let { deliveryId: i, targetId: a, event: o, eventId: s, payloadJson: c } = n, l =
|
|
990
|
-
return
|
|
991
|
-
let n = yield*
|
|
994
|
+
return Number.isFinite(n) && n > 0 ? n : St;
|
|
995
|
+
}, wt = (e, t = {}) => (n, r) => {
|
|
996
|
+
let { deliveryId: i, targetId: a, event: o, eventId: s, payloadJson: c } = n, l = O({ scope: `webhook:${i}` });
|
|
997
|
+
return C.gen(function* () {
|
|
998
|
+
let n = yield* k.make({
|
|
992
999
|
name: "fetch-target",
|
|
993
|
-
success:
|
|
994
|
-
id:
|
|
995
|
-
event:
|
|
996
|
-
url:
|
|
997
|
-
secret:
|
|
998
|
-
signing:
|
|
999
|
-
retry:
|
|
1000
|
-
headers:
|
|
1001
|
-
key:
|
|
1002
|
-
value:
|
|
1003
|
-
}),
|
|
1004
|
-
rateLimitPerMinute:
|
|
1005
|
-
active:
|
|
1006
|
-
format:
|
|
1007
|
-
tenantId:
|
|
1000
|
+
success: w.Union(w.Null, w.Struct({
|
|
1001
|
+
id: w.String,
|
|
1002
|
+
event: w.String,
|
|
1003
|
+
url: w.String,
|
|
1004
|
+
secret: w.String,
|
|
1005
|
+
signing: w.Any,
|
|
1006
|
+
retry: w.Any,
|
|
1007
|
+
headers: w.Union(w.Record({
|
|
1008
|
+
key: w.String,
|
|
1009
|
+
value: w.String
|
|
1010
|
+
}), w.Null),
|
|
1011
|
+
rateLimitPerMinute: w.Union(w.Number, w.Null),
|
|
1012
|
+
active: w.Boolean,
|
|
1013
|
+
format: w.Literal("json", "form", "xml"),
|
|
1014
|
+
tenantId: w.Union(w.String, w.Null)
|
|
1008
1015
|
})),
|
|
1009
|
-
execute:
|
|
1016
|
+
execute: C.tryPromise({
|
|
1010
1017
|
try: async () => (await e.store.query({
|
|
1011
1018
|
table: "_voltro_webhook_targets",
|
|
1012
|
-
predicate:
|
|
1019
|
+
predicate: D("id", a),
|
|
1013
1020
|
order: [{
|
|
1014
1021
|
column: "createdAt",
|
|
1015
1022
|
direction: "asc"
|
|
@@ -1023,7 +1030,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1023
1030
|
event: o,
|
|
1024
1031
|
cause: String(e)
|
|
1025
1032
|
}), /* @__PURE__ */ Error(`fetch-target failed: ${e.message}`))
|
|
1026
|
-
}).pipe(
|
|
1033
|
+
}).pipe(C.orDie)
|
|
1027
1034
|
});
|
|
1028
1035
|
if (n === null) return {
|
|
1029
1036
|
finalStatus: "failed",
|
|
@@ -1048,18 +1055,18 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1048
1055
|
...l
|
|
1049
1056
|
});
|
|
1050
1057
|
};
|
|
1051
|
-
if (n.active !== !0) return yield*
|
|
1058
|
+
if (n.active !== !0) return yield* k.make({
|
|
1052
1059
|
name: "record-paused-pending",
|
|
1053
|
-
success:
|
|
1054
|
-
execute:
|
|
1060
|
+
success: w.Void,
|
|
1061
|
+
execute: C.tryPromise({
|
|
1055
1062
|
try: () => r(`${i}:1`, 1, { status: "pending" }),
|
|
1056
1063
|
catch: (e) => /* @__PURE__ */ Error(`record-paused-pending failed: ${e.message}`)
|
|
1057
|
-
}).pipe(
|
|
1064
|
+
}).pipe(C.orDie)
|
|
1058
1065
|
}), {
|
|
1059
1066
|
finalStatus: "deferred",
|
|
1060
1067
|
attempts: 0
|
|
1061
1068
|
};
|
|
1062
|
-
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 =
|
|
1069
|
+
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 ? [{
|
|
1063
1070
|
key: `target:${n.id}`,
|
|
1064
1071
|
limit: n.rateLimitPerMinute
|
|
1065
1072
|
}] : [], ..._ !== void 0 && _.perMinute > 0 ? [{
|
|
@@ -1069,10 +1076,10 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1069
1076
|
for (let t = 1; t <= d.maxAttempts; t++) {
|
|
1070
1077
|
let u = `${i}:${t}`;
|
|
1071
1078
|
if (v.length > 0) for (let n = 0;; n++) {
|
|
1072
|
-
let i = yield*
|
|
1079
|
+
let i = yield* k.make({
|
|
1073
1080
|
name: `rate-acquire-${t}-${n}`,
|
|
1074
|
-
success:
|
|
1075
|
-
execute:
|
|
1081
|
+
success: yt,
|
|
1082
|
+
execute: C.tryPromise({
|
|
1076
1083
|
try: () => at(e.store, v, Date.now(), h),
|
|
1077
1084
|
catch: (e) => (l.debug("rate-acquire raw error", {
|
|
1078
1085
|
targetId: a,
|
|
@@ -1080,45 +1087,45 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1080
1087
|
attempt: t,
|
|
1081
1088
|
cause: String(e)
|
|
1082
1089
|
}), /* @__PURE__ */ Error(`rate-acquire failed: ${e.message}`))
|
|
1083
|
-
}).pipe(
|
|
1090
|
+
}).pipe(C.orDie)
|
|
1084
1091
|
});
|
|
1085
1092
|
if (i.acquired) break;
|
|
1086
|
-
yield*
|
|
1093
|
+
yield* k.make({
|
|
1087
1094
|
name: `record-rate-deferral-${t}-${n}`,
|
|
1088
|
-
success:
|
|
1089
|
-
execute:
|
|
1095
|
+
success: w.Void,
|
|
1096
|
+
execute: C.tryPromise({
|
|
1090
1097
|
try: () => r(u, t, {
|
|
1091
1098
|
status: "pending",
|
|
1092
1099
|
nextAttemptAt: new Date(Date.now() + i.retryInMs)
|
|
1093
1100
|
}),
|
|
1094
1101
|
catch: (e) => /* @__PURE__ */ Error(`record-rate-deferral failed: ${e.message}`)
|
|
1095
|
-
}).pipe(
|
|
1096
|
-
}), yield*
|
|
1102
|
+
}).pipe(C.orDie)
|
|
1103
|
+
}), yield* A.sleep({
|
|
1097
1104
|
name: `rate-window-sleep-${t}-${n}`,
|
|
1098
1105
|
duration: `${i.retryInMs} millis`
|
|
1099
1106
|
});
|
|
1100
1107
|
}
|
|
1101
|
-
yield*
|
|
1108
|
+
yield* k.make({
|
|
1102
1109
|
name: `record-attempt-start-${t}`,
|
|
1103
|
-
success:
|
|
1104
|
-
execute:
|
|
1110
|
+
success: w.Void,
|
|
1111
|
+
execute: C.tryPromise({
|
|
1105
1112
|
try: () => r(u, t, {
|
|
1106
1113
|
status: "inFlight",
|
|
1107
1114
|
nextAttemptAt: null,
|
|
1108
1115
|
scheduledAt: /* @__PURE__ */ new Date()
|
|
1109
1116
|
}),
|
|
1110
1117
|
catch: (e) => /* @__PURE__ */ Error(`record-attempt-start failed: ${e.message}`)
|
|
1111
|
-
}).pipe(
|
|
1118
|
+
}).pipe(C.orDie)
|
|
1112
1119
|
});
|
|
1113
|
-
let _ = yield*
|
|
1120
|
+
let _ = yield* k.make({
|
|
1114
1121
|
name: `sign-and-post-${t}`,
|
|
1115
|
-
success:
|
|
1116
|
-
execute:
|
|
1122
|
+
success: _t,
|
|
1123
|
+
execute: C.promise(async () => {
|
|
1117
1124
|
let e = Date.now(), r;
|
|
1118
1125
|
try {
|
|
1119
1126
|
r = ot(n.format, c);
|
|
1120
1127
|
} catch (t) {
|
|
1121
|
-
if (t instanceof
|
|
1128
|
+
if (t instanceof b) return {
|
|
1122
1129
|
status: 0,
|
|
1123
1130
|
body: "",
|
|
1124
1131
|
latencyMs: Date.now() - e,
|
|
@@ -1133,7 +1140,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1133
1140
|
secret: n.secret,
|
|
1134
1141
|
messageId: i
|
|
1135
1142
|
});
|
|
1136
|
-
process.env.NODE_ENV === "production" &&
|
|
1143
|
+
process.env.NODE_ENV === "production" && ye(n.url);
|
|
1137
1144
|
try {
|
|
1138
1145
|
let c = await fetch(n.url, {
|
|
1139
1146
|
method: "POST",
|
|
@@ -1170,12 +1177,12 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1170
1177
|
};
|
|
1171
1178
|
}
|
|
1172
1179
|
})
|
|
1173
|
-
}), y = yield*
|
|
1180
|
+
}), y = yield* k.make({
|
|
1174
1181
|
name: `classify-and-record-${t}`,
|
|
1175
|
-
success:
|
|
1176
|
-
execute:
|
|
1182
|
+
success: vt,
|
|
1183
|
+
execute: C.tryPromise({
|
|
1177
1184
|
try: async () => {
|
|
1178
|
-
let r = _.status >= 200 && _.status < 300, i = _.transportError !== void 0, o = _.encodeError !== void 0, s = _.status === 410, c = (t, n) =>
|
|
1185
|
+
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);
|
|
1179
1186
|
if (r) {
|
|
1180
1187
|
await e.store.update("_voltro_webhook_deliveries", u, {
|
|
1181
1188
|
status: "succeeded",
|
|
@@ -1193,7 +1200,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1193
1200
|
consecutiveFailures: t.consecutiveFailures
|
|
1194
1201
|
};
|
|
1195
1202
|
}
|
|
1196
|
-
let l = o ? null :
|
|
1203
|
+
let l = o ? null : Ee(d, t, {
|
|
1197
1204
|
...i ? {} : { status: _.status },
|
|
1198
1205
|
..._.retryAfterSec === void 0 ? {} : { retryAfterSeconds: _.retryAfterSec }
|
|
1199
1206
|
});
|
|
@@ -1238,7 +1245,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1238
1245
|
attempt: t,
|
|
1239
1246
|
cause: String(e)
|
|
1240
1247
|
}), /* @__PURE__ */ Error(`classify-and-record failed: ${e.message}`))
|
|
1241
|
-
}).pipe(
|
|
1248
|
+
}).pipe(C.orDie)
|
|
1242
1249
|
});
|
|
1243
1250
|
if (y.autoDisabled === !0 && l.warn("target auto-disabled after consecutive failures", {
|
|
1244
1251
|
targetId: a,
|
|
@@ -1253,7 +1260,7 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1253
1260
|
finalStatus: "failed",
|
|
1254
1261
|
attempts: t
|
|
1255
1262
|
};
|
|
1256
|
-
y.delayMs !== void 0 && (yield*
|
|
1263
|
+
y.delayMs !== void 0 && (yield* A.sleep({
|
|
1257
1264
|
name: `retry-sleep-${t}`,
|
|
1258
1265
|
duration: `${y.delayMs} millis`
|
|
1259
1266
|
}));
|
|
@@ -1263,15 +1270,15 @@ var M = ["network:outbound:*"], xe = (e = {}) => he({
|
|
|
1263
1270
|
attempts: d.maxAttempts
|
|
1264
1271
|
};
|
|
1265
1272
|
});
|
|
1266
|
-
}, $ = "voltro.webhook.emit",
|
|
1273
|
+
}, $ = "voltro.webhook.emit", Tt = (e) => typeof e.event == "string", Et = (e) => xe({
|
|
1267
1274
|
effect: $,
|
|
1268
1275
|
maxAttempts: 1,
|
|
1269
1276
|
handler: async ({ payload: t }) => {
|
|
1270
|
-
if (!
|
|
1277
|
+
if (!Tt(t)) throw Error(`${$}: row payload has no \`event\` name — cannot resolve a descriptor. Got keys: ${Object.keys(t).join(", ") || "(none)"}`);
|
|
1271
1278
|
let n = e();
|
|
1272
1279
|
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.`);
|
|
1273
1280
|
await n.emit(t.event, t.payload, { tenantId: t.tenantId });
|
|
1274
1281
|
}
|
|
1275
1282
|
});
|
|
1276
1283
|
//#endregion
|
|
1277
|
-
export {
|
|
1284
|
+
export { Ge as IdempotencyCache, tt as RATE_WINDOW_MS, et 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, Ye as UnverifiedIncomingWebhook, M as WEBHOOKS_EGRESS, j as WEBHOOKS_PLUGIN_PERMISSIONS, $ as WEBHOOK_EMIT_EFFECT, Je as WEBHOOK_VERIFICATION_PROPERTY, oe as WebhookDeliveryNotFound, se as WebhookPayloadInvalid, b as WebhookPayloadUnrepresentable, ce as WebhookPayloadVersionInvalid, x as WebhookSubscribeInvalid, S as WebhookTargetNotFound, ke as WebhooksService, le as _voltroWebhookDeliveriesTable, ue as _voltroWebhookEventStatsTable, de as _voltroWebhookRateWindowsTable, fe as _voltroWebhookTargetsTable, at as acquireRateSlots, we as asOutgoingEvent, wt as buildDeliverWebhookExecute, Ve as buildWebhooksService, Fe as compareVersions, X as consumeRateSlot, $e as defaultIncomingPath, ee as defaultOutgoingSignature, De as defaultRetryPolicy, re as defineIncomingWebhook, r as defineWebhookProvider, xt as deliverWebhookWorkflow, ot as encodePayload, F as eventsOf, Oe as fastRetryPolicy, Me as generateSecret, i as generateStandardWebhooksSecret, h as genericHmacSignature, Ke as getIdempotencyCache, v as githubSignature, Ce as hasWebhookAudience, Ze as incomingWebhookVerification, ne as isStandardWebhooksSecret, t as isWebhookDescriptor, Et as makeDeferredEmitHandler, V as matchesFilter, Qe as mountIncomingWebhook, Ee as nextRetry, N as parseDuration, qe as parseTtl, gt as recordDeliveryOutcome, it as releaseRateSlot, R 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, Ue as useWebhooks, He as useWebhooksEffect, L as validateSubscribe, s as verifyRequest, e as verifyStandardWebhooks, pe as webhookTables, Se as webhooksPlugin };
|