@voltro/plugin-webhooks 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1215 -0
- package/THIRD-PARTY-NOTICES.md +118 -600
- package/dist/autoDisable.js +41 -0
- package/dist/deferredEmit.js +17 -0
- package/dist/deliverWorkflow.js +336 -0
- package/dist/dsl.js +14 -0
- package/dist/encode.js +53 -0
- package/dist/errors.d.ts +23 -33
- package/dist/errors.js +10 -2
- package/dist/eventAudience.js +14 -0
- package/dist/idempotency.js +49 -0
- package/dist/incoming.js +205 -0
- package/dist/index.d.ts +45 -49
- package/dist/index.js +17 -1284
- package/dist/plugin.js +19 -0
- package/dist/providers/generic.js +19 -0
- package/dist/providers/github.js +16 -0
- package/dist/providers/index.js +5 -86
- package/dist/providers/slack.js +39 -0
- package/dist/providers/stripe.js +30 -0
- package/dist/rateLimit.js +61 -0
- package/dist/retry.js +65 -0
- package/dist/service.js +491 -0
- package/dist/signing.js +145 -0
- package/dist/standardWebhooks.js +88 -0
- package/package.json +8 -9
- package/dist/signing-DeHObNv6.js +0 -234
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Effect as e } from "effect";
|
|
2
|
+
import { definePlugin as t } from "@voltro/protocol";
|
|
3
|
+
//#region src/plugin.ts
|
|
4
|
+
var n = ["network:outbound:*"], r = [{
|
|
5
|
+
destination: "runtime:registered webhook targets (_voltro_webhook_targets)",
|
|
6
|
+
purpose: "event deliveries, HMAC-signed",
|
|
7
|
+
data: ["content", "metadata"],
|
|
8
|
+
disable: "remove or pause the targets; an emit then delivers nowhere"
|
|
9
|
+
}], i = (i = {}) => t({
|
|
10
|
+
name: i.name ? `@voltro/plugin-webhooks#${i.name}` : "@voltro/plugin-webhooks",
|
|
11
|
+
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.",
|
|
12
|
+
permissions: n,
|
|
13
|
+
egress: r,
|
|
14
|
+
onActivate: (t) => e.sync(() => {
|
|
15
|
+
t.logger.info("webhooks active", { outbound: "network:outbound:* (subscriber-configured target URLs — audit visibility, not a restriction)" });
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
//#endregion
|
|
19
|
+
export { r as WEBHOOKS_EGRESS, n as WEBHOOKS_PLUGIN_PERMISSIONS, i as webhooksPlugin };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineWebhookProvider as e } from "../dsl.js";
|
|
2
|
+
import { genericHmacSignature as t } from "../signing.js";
|
|
3
|
+
//#region src/providers/generic.ts
|
|
4
|
+
var n = (n) => e({
|
|
5
|
+
id: "generic",
|
|
6
|
+
name: "Generic",
|
|
7
|
+
signature: {
|
|
8
|
+
...t(),
|
|
9
|
+
...n?.signatureHeader === void 0 ? {} : { header: n.signatureHeader },
|
|
10
|
+
...n?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: n.replayWindowSeconds }
|
|
11
|
+
},
|
|
12
|
+
idempotency: {
|
|
13
|
+
from: "idempotency-key",
|
|
14
|
+
ttl: "7d"
|
|
15
|
+
},
|
|
16
|
+
bodyType: "json"
|
|
17
|
+
});
|
|
18
|
+
//#endregion
|
|
19
|
+
export { n as genericProvider };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineWebhookProvider as e } from "../dsl.js";
|
|
2
|
+
import { githubSignature as t } from "../signing.js";
|
|
3
|
+
//#region src/providers/github.ts
|
|
4
|
+
var n = () => e({
|
|
5
|
+
id: "github",
|
|
6
|
+
name: "GitHub",
|
|
7
|
+
signature: t(),
|
|
8
|
+
idempotency: {
|
|
9
|
+
from: "x-github-delivery",
|
|
10
|
+
ttl: "7d"
|
|
11
|
+
},
|
|
12
|
+
bodyType: "json",
|
|
13
|
+
eventTypeFrom: (e) => {}
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { n as githubProvider };
|
package/dist/providers/index.js
CHANGED
|
@@ -1,86 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
name: "Stripe",
|
|
7
|
-
signature: {
|
|
8
|
-
...r(),
|
|
9
|
-
...t?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: t.replayWindowSeconds }
|
|
10
|
-
},
|
|
11
|
-
idempotency: {
|
|
12
|
-
from: (e, t) => {
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(new TextDecoder().decode(t)).id;
|
|
15
|
-
} catch {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
ttl: "30d"
|
|
20
|
-
},
|
|
21
|
-
bodyType: "json",
|
|
22
|
-
eventTypeFrom: (e) => {
|
|
23
|
-
if (typeof e == "object" && e && "type" in e) {
|
|
24
|
-
let t = e.type;
|
|
25
|
-
return typeof t == "string" ? t : void 0;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}), s = () => e({
|
|
29
|
-
id: "github",
|
|
30
|
-
name: "GitHub",
|
|
31
|
-
signature: n(),
|
|
32
|
-
idempotency: {
|
|
33
|
-
from: "x-github-delivery",
|
|
34
|
-
ttl: "7d"
|
|
35
|
-
},
|
|
36
|
-
bodyType: "json",
|
|
37
|
-
eventTypeFrom: (e) => {}
|
|
38
|
-
}), c = () => ({
|
|
39
|
-
_tag: "custom",
|
|
40
|
-
header: "X-Slack-Signature",
|
|
41
|
-
sign: (e, t) => {
|
|
42
|
-
let n = `v0:${Math.floor(Date.now() / 1e3)}:${new TextDecoder().decode(e)}`;
|
|
43
|
-
return `v0=${i("sha256", t).update(n).digest("hex")}`;
|
|
44
|
-
},
|
|
45
|
-
verify: (e, t, n) => {
|
|
46
|
-
let r = n["x-slack-signature"], o = n["x-slack-request-timestamp"];
|
|
47
|
-
if (!r || !o || !/^\d+$/.test(o)) return !1;
|
|
48
|
-
let s = Math.floor(Date.now() / 1e3);
|
|
49
|
-
if (Math.abs(s - Number(o)) > 300) return !1;
|
|
50
|
-
let c = `v0:${o}:${new TextDecoder().decode(e)}`, l = `v0=${i("sha256", t).update(c).digest("hex")}`, u = Buffer.from(l, "utf8"), d = Buffer.from(r, "utf8");
|
|
51
|
-
return u.length === d.length ? a(u, d) : (a(u, Buffer.alloc(u.length)), !1);
|
|
52
|
-
}
|
|
53
|
-
}), l = () => e({
|
|
54
|
-
id: "slack",
|
|
55
|
-
name: "Slack",
|
|
56
|
-
signature: c(),
|
|
57
|
-
idempotency: {
|
|
58
|
-
from: "x-slack-request-timestamp",
|
|
59
|
-
ttl: "5m"
|
|
60
|
-
},
|
|
61
|
-
bodyType: "json",
|
|
62
|
-
eventTypeFrom: (e) => {
|
|
63
|
-
if (typeof e == "object" && e && "event" in e) {
|
|
64
|
-
let t = e.event;
|
|
65
|
-
if (typeof t == "object" && t && "type" in t) {
|
|
66
|
-
let e = t.type;
|
|
67
|
-
return typeof e == "string" ? e : void 0;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}), u = (n) => e({
|
|
72
|
-
id: "generic",
|
|
73
|
-
name: "Generic",
|
|
74
|
-
signature: {
|
|
75
|
-
...t(),
|
|
76
|
-
...n?.signatureHeader === void 0 ? {} : { header: n.signatureHeader },
|
|
77
|
-
...n?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: n.replayWindowSeconds }
|
|
78
|
-
},
|
|
79
|
-
idempotency: {
|
|
80
|
-
from: "idempotency-key",
|
|
81
|
-
ttl: "7d"
|
|
82
|
-
},
|
|
83
|
-
bodyType: "json"
|
|
84
|
-
});
|
|
85
|
-
//#endregion
|
|
86
|
-
export { u as genericProvider, s as githubProvider, l as slackProvider, o as stripeProvider };
|
|
1
|
+
import { stripeProvider as e } from "./stripe.js";
|
|
2
|
+
import { githubProvider as t } from "./github.js";
|
|
3
|
+
import { slackProvider as n } from "./slack.js";
|
|
4
|
+
import { genericProvider as r } from "./generic.js";
|
|
5
|
+
export { r as genericProvider, t as githubProvider, n as slackProvider, e as stripeProvider };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineWebhookProvider as e } from "../dsl.js";
|
|
2
|
+
import { createHmac as t, timingSafeEqual as n } from "node:crypto";
|
|
3
|
+
//#region src/providers/slack.ts
|
|
4
|
+
var r = () => ({
|
|
5
|
+
_tag: "custom",
|
|
6
|
+
header: "X-Slack-Signature",
|
|
7
|
+
sign: (e, n) => {
|
|
8
|
+
let r = `v0:${Math.floor(Date.now() / 1e3)}:${new TextDecoder().decode(e)}`;
|
|
9
|
+
return `v0=${t("sha256", n).update(r).digest("hex")}`;
|
|
10
|
+
},
|
|
11
|
+
verify: (e, r, i) => {
|
|
12
|
+
let a = i["x-slack-signature"], o = i["x-slack-request-timestamp"];
|
|
13
|
+
if (!a || !o || !/^\d+$/.test(o)) return !1;
|
|
14
|
+
let s = Math.floor(Date.now() / 1e3);
|
|
15
|
+
if (Math.abs(s - Number(o)) > 300) return !1;
|
|
16
|
+
let c = `v0:${o}:${new TextDecoder().decode(e)}`, l = `v0=${t("sha256", r).update(c).digest("hex")}`, u = Buffer.from(l, "utf8"), d = Buffer.from(a, "utf8");
|
|
17
|
+
return u.length === d.length ? n(u, d) : (n(u, Buffer.alloc(u.length)), !1);
|
|
18
|
+
}
|
|
19
|
+
}), i = () => e({
|
|
20
|
+
id: "slack",
|
|
21
|
+
name: "Slack",
|
|
22
|
+
signature: r(),
|
|
23
|
+
idempotency: {
|
|
24
|
+
from: "x-slack-request-timestamp",
|
|
25
|
+
ttl: "5m"
|
|
26
|
+
},
|
|
27
|
+
bodyType: "json",
|
|
28
|
+
eventTypeFrom: (e) => {
|
|
29
|
+
if (typeof e == "object" && e && "event" in e) {
|
|
30
|
+
let t = e.event;
|
|
31
|
+
if (typeof t == "object" && t && "type" in t) {
|
|
32
|
+
let e = t.type;
|
|
33
|
+
return typeof e == "string" ? e : void 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
export { i as slackProvider };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineWebhookProvider as e } from "../dsl.js";
|
|
2
|
+
import { stripeSignature as t } from "../signing.js";
|
|
3
|
+
//#region src/providers/stripe.ts
|
|
4
|
+
var n = (n) => e({
|
|
5
|
+
id: "stripe",
|
|
6
|
+
name: "Stripe",
|
|
7
|
+
signature: {
|
|
8
|
+
...t(),
|
|
9
|
+
...n?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: n.replayWindowSeconds }
|
|
10
|
+
},
|
|
11
|
+
idempotency: {
|
|
12
|
+
from: (e, t) => {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(new TextDecoder().decode(t)).id;
|
|
15
|
+
} catch {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
ttl: "30d"
|
|
20
|
+
},
|
|
21
|
+
bodyType: "json",
|
|
22
|
+
eventTypeFrom: (e) => {
|
|
23
|
+
if (typeof e == "object" && e && "type" in e) {
|
|
24
|
+
let t = e.type;
|
|
25
|
+
return typeof t == "string" ? t : void 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { n as stripeProvider };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { and as e, eq as t } from "@voltro/database";
|
|
2
|
+
//#region src/rateLimit.ts
|
|
3
|
+
var n = "_voltro_webhook_rate_windows", r = 6e4, i = (e, t) => `${e}@${t}`, a = async (n, r, i) => (await n.query({
|
|
4
|
+
table: "_voltro_webhook_rate_windows",
|
|
5
|
+
predicate: e(t("scope", r), t("bucket", i)),
|
|
6
|
+
order: [],
|
|
7
|
+
take: 1,
|
|
8
|
+
skip: void 0,
|
|
9
|
+
projection: void 0
|
|
10
|
+
}))[0] ?? null, o = 32, s = async (r, s, c) => {
|
|
11
|
+
for (let l = 0; l < o; l++) {
|
|
12
|
+
l > 0 && await new Promise((e) => setTimeout(e, l));
|
|
13
|
+
let o = await a(r, s.key, c);
|
|
14
|
+
if (o === null) await r.insertIgnore(n, {
|
|
15
|
+
id: i(s.key, c),
|
|
16
|
+
scope: s.key,
|
|
17
|
+
bucket: c,
|
|
18
|
+
count: 0,
|
|
19
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
20
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
21
|
+
}, { conflictColumns: ["id"] });
|
|
22
|
+
else {
|
|
23
|
+
if (o.count >= s.limit) return !1;
|
|
24
|
+
if (await r.updateMany("_voltro_webhook_rate_windows", {
|
|
25
|
+
count: o.count + 1,
|
|
26
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
27
|
+
}, { where: e(t("id", o.id), t("count", o.count)) }) === 1) return !0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw Error(`webhook rate window: contention on (${s.key}, ${c}) exceeded ${o} attempts`);
|
|
31
|
+
}, c = async (n, r, i) => {
|
|
32
|
+
for (let s = 0; s < o; s++) {
|
|
33
|
+
s > 0 && await new Promise((e) => setTimeout(e, s));
|
|
34
|
+
let o = await a(n, r, i);
|
|
35
|
+
if (o === null || o.count <= 0 || await n.updateMany("_voltro_webhook_rate_windows", {
|
|
36
|
+
count: o.count - 1,
|
|
37
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
38
|
+
}, { where: e(t("id", o.id), t("count", o.count)) }) === 1) return;
|
|
39
|
+
}
|
|
40
|
+
throw Error(`webhook rate window: release contention on (${r}) exceeded ${o} attempts`);
|
|
41
|
+
}, l = async (e, t, n, i = r) => {
|
|
42
|
+
if (t.length === 0) return {
|
|
43
|
+
acquired: !0,
|
|
44
|
+
retryInMs: 0
|
|
45
|
+
};
|
|
46
|
+
let a = Math.floor(n / i), o = [];
|
|
47
|
+
for (let r of t) if (await s(e, r, a)) o.push(r);
|
|
48
|
+
else {
|
|
49
|
+
for (let t of o) await c(e, t.key, a);
|
|
50
|
+
return {
|
|
51
|
+
acquired: !1,
|
|
52
|
+
retryInMs: (a + 1) * i - n
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
acquired: !0,
|
|
57
|
+
retryInMs: 0
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { r as RATE_WINDOW_MS, n as RATE_WINDOW_TABLE, l as acquireRateSlots, s as consumeRateSlot, c as releaseRateSlot };
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//#region src/retry.ts
|
|
2
|
+
var e = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/, t = (t) => {
|
|
3
|
+
let n = e.exec(t);
|
|
4
|
+
if (!n) throw Error(`invalid duration literal: ${t}`);
|
|
5
|
+
let r = Number(n[1]), i = n[2] ?? "s";
|
|
6
|
+
switch (i) {
|
|
7
|
+
case "ms": return r;
|
|
8
|
+
case "s": return r * 1e3;
|
|
9
|
+
case "m": return r * 6e4;
|
|
10
|
+
case "h": return r * 36e5;
|
|
11
|
+
case "d": return r * 864e5;
|
|
12
|
+
default: throw Error(`unreachable unit: ${i}`);
|
|
13
|
+
}
|
|
14
|
+
}, n = [
|
|
15
|
+
408,
|
|
16
|
+
425,
|
|
17
|
+
429,
|
|
18
|
+
500,
|
|
19
|
+
502,
|
|
20
|
+
503,
|
|
21
|
+
504
|
|
22
|
+
], r = (e, r, i) => {
|
|
23
|
+
if (r >= e.maxAttempts || i?.status !== void 0 && !(e.retryOn ?? n).includes(i.status)) return null;
|
|
24
|
+
if (e.honourRetryAfter !== !1 && i?.retryAfterSeconds !== void 0) {
|
|
25
|
+
let n = i.retryAfterSeconds * 1e3, r = t(e.maxDelay), a = Math.min(n, r);
|
|
26
|
+
return {
|
|
27
|
+
delayMs: a,
|
|
28
|
+
baseDelayMs: a
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
let a = t(e.initialDelay), o = t(e.maxDelay), s;
|
|
32
|
+
switch (e.strategy) {
|
|
33
|
+
case "fixed":
|
|
34
|
+
s = a;
|
|
35
|
+
break;
|
|
36
|
+
case "linear":
|
|
37
|
+
s = a * r;
|
|
38
|
+
break;
|
|
39
|
+
case "exponential": s = a * 2 ** (r - 1);
|
|
40
|
+
}
|
|
41
|
+
s = Math.min(s, o);
|
|
42
|
+
let c = s;
|
|
43
|
+
return e.jitter === "full" && (c = Math.random() * s), {
|
|
44
|
+
delayMs: Math.max(1, Math.floor(c)),
|
|
45
|
+
baseDelayMs: s
|
|
46
|
+
};
|
|
47
|
+
}, i = () => ({
|
|
48
|
+
strategy: "exponential",
|
|
49
|
+
maxAttempts: 8,
|
|
50
|
+
initialDelay: "5s",
|
|
51
|
+
maxDelay: "1h",
|
|
52
|
+
retryOn: n,
|
|
53
|
+
honourRetryAfter: !0,
|
|
54
|
+
jitter: "full"
|
|
55
|
+
}), a = () => ({
|
|
56
|
+
strategy: "fixed",
|
|
57
|
+
maxAttempts: 120,
|
|
58
|
+
initialDelay: "30s",
|
|
59
|
+
maxDelay: "30s",
|
|
60
|
+
retryOn: n,
|
|
61
|
+
honourRetryAfter: !0,
|
|
62
|
+
jitter: "full"
|
|
63
|
+
});
|
|
64
|
+
//#endregion
|
|
65
|
+
export { i as defaultRetryPolicy, a as fastRetryPolicy, r as nextRetry, t as parseDuration };
|