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