@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/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 };
@@ -1,86 +1,5 @@
1
- import { T as e, n as t, r as n, s as r } from "../signing-DeHObNv6.js";
2
- import { createHmac as i, timingSafeEqual as a } from "node:crypto";
3
- //#region src/providers/stripe.ts
4
- var o = (t) => e({
5
- id: "stripe",
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 };