@siglo/notifications 0.0.0-stage → 1.0.1

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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +478 -2
  3. package/dist/approvals.d.ts +38 -0
  4. package/dist/approvals.d.ts.map +1 -0
  5. package/dist/approvals.js +111 -0
  6. package/dist/approvals.js.map +1 -0
  7. package/dist/callbacks.d.ts +14 -0
  8. package/dist/callbacks.d.ts.map +1 -0
  9. package/dist/callbacks.js +144 -0
  10. package/dist/callbacks.js.map +1 -0
  11. package/dist/dispatch.d.ts +26 -0
  12. package/dist/dispatch.d.ts.map +1 -0
  13. package/dist/dispatch.js +42 -0
  14. package/dist/dispatch.js.map +1 -0
  15. package/dist/egress.d.ts +8 -0
  16. package/dist/egress.d.ts.map +1 -0
  17. package/dist/egress.js +96 -0
  18. package/dist/egress.js.map +1 -0
  19. package/dist/index.d.ts +11 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +27 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/monitor.d.ts +10 -0
  24. package/dist/monitor.d.ts.map +1 -0
  25. package/dist/monitor.js +20 -0
  26. package/dist/monitor.js.map +1 -0
  27. package/dist/registry.d.ts +5 -0
  28. package/dist/registry.d.ts.map +1 -0
  29. package/dist/registry.js +15 -0
  30. package/dist/registry.js.map +1 -0
  31. package/dist/sms.d.ts +28 -0
  32. package/dist/sms.d.ts.map +1 -0
  33. package/dist/sms.js +43 -0
  34. package/dist/sms.js.map +1 -0
  35. package/dist/steps.d.ts +7 -0
  36. package/dist/steps.d.ts.map +1 -0
  37. package/dist/steps.js +131 -0
  38. package/dist/steps.js.map +1 -0
  39. package/dist/testing/index.d.ts +18 -0
  40. package/dist/testing/index.d.ts.map +1 -0
  41. package/dist/testing/index.js +19 -0
  42. package/dist/testing/index.js.map +1 -0
  43. package/dist/testing.d.ts +18 -0
  44. package/dist/testing.d.ts.map +1 -0
  45. package/dist/testing.js +19 -0
  46. package/dist/testing.js.map +1 -0
  47. package/dist/tokens.d.ts +24 -0
  48. package/dist/tokens.d.ts.map +1 -0
  49. package/dist/tokens.js +48 -0
  50. package/dist/tokens.js.map +1 -0
  51. package/dist/transports.d.ts +120 -0
  52. package/dist/transports.d.ts.map +1 -0
  53. package/dist/transports.js +172 -0
  54. package/dist/transports.js.map +1 -0
  55. package/dist/types.d.ts +106 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +3 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +64 -4
package/dist/steps.js ADDED
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertTransportPolicy = assertTransportPolicy;
4
+ exports.registerNotificationCompensationHandlers = registerNotificationCompensationHandlers;
5
+ exports.sendNotificationStep = sendNotificationStep;
6
+ exports.sendInteractiveApprovalStep = sendInteractiveApprovalStep;
7
+ const dispatch_1 = require("./dispatch");
8
+ const node_crypto_1 = require("node:crypto");
9
+ const core_1 = require("@siglo/core");
10
+ const http_1 = require("@siglo/http");
11
+ const registry_1 = require("./registry");
12
+ const approvals_1 = require("./approvals");
13
+ const tokens_1 = require("./tokens");
14
+ const resolve = (v, ctx) => typeof v === 'function' ? v(ctx) : v;
15
+ function assertTransportPolicy(name, transport) {
16
+ for (const host of transport.allowedHosts)
17
+ (0, http_1.assertNetworkPolicy)(name, `https://${host}`, host);
18
+ }
19
+ async function cancelNotification(output, transport) {
20
+ if (output.correlationKey)
21
+ await (0, approvals_1.getApprovalRevocationStore)().revoke(output.correlationKey);
22
+ if (!transport.cancel && output.correlationKey)
23
+ return;
24
+ assertTransportPolicy('cancelNotification', transport);
25
+ if (!transport.cancel)
26
+ throw new Error('Transport does not support cancellation');
27
+ await transport.cancel(output.notificationId, 'Workflow cancelled');
28
+ }
29
+ function registerNotificationCompensationHandlers() {
30
+ if ((0, core_1.hasCompensationHandler)('cancelNotification'))
31
+ return;
32
+ (0, core_1.registerCompensationHandler)('cancelNotification', output => cancelNotification(output, (0, registry_1.getNotificationTransport)(output.$notificationMetadata.channel)));
33
+ }
34
+ registerNotificationCompensationHandlers();
35
+ function sendNotificationStep(name, options) {
36
+ return createNotificationStep(name, options);
37
+ }
38
+ function createNotificationStep(name, options, enrich) {
39
+ registerNotificationCompensationHandlers();
40
+ const transportFor = (ctx) => options.transport ?? (0, registry_1.getNotificationTransport)(resolve(options.channel, ctx));
41
+ // Schema and before hooks run outside the observed action. Report their failures
42
+ // without resolving caller callbacks again or copying exception content.
43
+ const inspectPreAction = async (action) => {
44
+ const started = Date.now();
45
+ try {
46
+ return await action();
47
+ }
48
+ catch (error) {
49
+ (0, core_1.recordInspection)({ version: 1, kind: 'notification', outcome: 'failure',
50
+ approval: !!enrich, tokenStatus: 'unavailable', durationMs: Date.now() - started,
51
+ channel: typeof options.channel === 'string' ? options.channel : undefined });
52
+ throw error;
53
+ }
54
+ };
55
+ const dispatchKey = (ctx) => `${typeof ctx?.$stackId === 'function' ? ctx.$stackId() : ctx?.$stackId || ctx?.stackId || 'standalone'}:${name}:${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify(resolve(options.recipient, ctx))).digest('hex')}`;
56
+ const action = async (ctx, inspection) => {
57
+ const channel = resolve(options.channel, ctx), to = resolve(options.recipient, ctx), transport = transportFor(ctx);
58
+ if (transport.channel !== channel)
59
+ throw new Error('Notification transport channel mismatch');
60
+ assertTransportPolicy(name, transport);
61
+ const target = typeof to === 'string' ? to : channel === 'email' ? to.email : channel === 'sms' ? to.phone : channel === 'slack' ? to.slackChannelId || to.slackUserId : channel === 'push' ? to.pushToken : to.discordWebhookUrl;
62
+ const idempotencyKey = (0, node_crypto_1.createHash)('sha256').update(dispatchKey(ctx)).digest('hex');
63
+ const intent = await (options.dispatchStore ?? (0, dispatch_1.getNotificationDispatchStore)()).prepare(idempotencyKey, async () => {
64
+ const enriched = await enrich?.(ctx);
65
+ return { message: { to, idempotencyKey, subject: options.subject === undefined ? undefined : resolve(options.subject, ctx), content: resolve(options.template, ctx), interactiveActions: enriched?.actions }, extra: enriched?.extra };
66
+ });
67
+ inspection.content = (0, core_1.captureInspection)(options.capture, 'preview', intent.message.content);
68
+ const result = await transport.send({ ...intent.message, signal: typeof ctx?.$signal === 'function' ? ctx.$signal() : ctx?.$signal });
69
+ const metadata = { notificationId: result.id, channel, dispatchedAt: result.timestamp, recipientMasked: `${target.slice(0, 1)}***` };
70
+ const stackId = typeof ctx?.$stackId === 'function' ? ctx.$stackId() : ctx?.$stackId || ctx?.stackId || 'standalone';
71
+ metadata.stackId = stackId;
72
+ if (enrich && typeof intent.extra?.approvalExpiresAt === 'number') {
73
+ metadata.approvalExpiresAt = intent.extra.approvalExpiresAt;
74
+ }
75
+ return { ...intent.extra, notificationId: result.id, sentAt: result.timestamp, $notificationMetadata: metadata, notificationMetadata: metadata };
76
+ };
77
+ const step = new core_1.Step(name, ctx => {
78
+ const inspection = {
79
+ version: 1, kind: 'notification', outcome: 'failure', approval: !!enrich,
80
+ channel: typeof options.channel === 'string' ? options.channel : undefined
81
+ };
82
+ return (0, core_1.observeEcosystem)(inspection, () => action(ctx, inspection), result => ({ channel: result.$notificationMetadata.channel,
83
+ notificationId: result.notificationId, approvalState: enrich ? 'requested' : undefined, approvalExpiresAt: result.$notificationMetadata.approvalExpiresAt, tokenStatus: enrich ? 'issued' : 'unavailable' }));
84
+ });
85
+ step.schema({ '~standard': { version: 1, vendor: 'siglo-notifications', validate: async (ctx) => inspectPreAction(async () => {
86
+ const channel = resolve(options.channel, ctx), to = resolve(options.recipient, ctx);
87
+ const target = typeof to === 'string' ? to : channel === 'email' ? to.email : channel === 'sms' ? to.phone : channel === 'slack' ? to.slackChannelId || to.slackUserId : channel === 'push' ? to.pushToken : to.discordWebhookUrl;
88
+ const valid = target && (channel === 'email' ? /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(target) : channel === 'sms' ? /^\+[1-9]\d{6,14}$/.test(target) : channel === 'slack' ? /^[A-Z][A-Z0-9]+$/.test(target) : channel === 'push' ? target.length > 0 : /^https:\/\/discord.com\/api\/webhooks\//.test(target));
89
+ if (!valid)
90
+ throw new core_1.ValidationException([{ field: 'recipient', message: 'Invalid notification recipient' }], { phase: 'validation', retryable: false });
91
+ if (options.dataSchema)
92
+ await (0, http_1.validatePayload)(options.dataSchema, options.data?.(ctx) ?? ctx, 'validation', name);
93
+ return { value: ctx };
94
+ }) } });
95
+ step.onAfter(async (output) => {
96
+ const metadata = output.$notificationMetadata;
97
+ const stackId = metadata.stackId;
98
+ for (const type of (enrich ? ['notification:dispatched', 'approval:requested'] : ['notification:dispatched'])) {
99
+ (0, core_1.emitMonitoringEvent)({ monitor: core_1.defaultStorageManager.getStack(stackId)?.monitor }, (0, core_1.redactEvent)({ type, stackId, stepId: name, timestamp: output.sentAt, ...metadata }));
100
+ }
101
+ return output;
102
+ });
103
+ step.onBefore(async (ctx) => inspectPreAction(() => { assertTransportPolicy(name, transportFor(ctx)); return true; }));
104
+ if (options.transport)
105
+ step.allowNetwork(options.transport.allowedHosts);
106
+ step.idempotent(dispatchKey, { ttlMs: (options.deduplicationTtl ?? 86400) * 1000, store: options.idempotencyStore });
107
+ if (options.compensateOnFailure) {
108
+ if (options.transport)
109
+ step.compensate((out) => cancelNotification(out, options.transport));
110
+ else
111
+ step.compensate('cancelNotification');
112
+ }
113
+ return step;
114
+ }
115
+ function sendInteractiveApprovalStep(name, options) {
116
+ const ttl = (0, core_1.parseDuration)(options.timeout);
117
+ if (!ttl || !Number.isFinite(ttl) || ttl <= 0)
118
+ throw new Error('Approval timeout must be positive');
119
+ return createNotificationStep(name, { capture: options.capture, channel: options.channel, recipient: options.recipient, template: options.title, transport: options.transport, idempotencyStore: options.idempotencyStore, dispatchStore: options.dispatchStore, compensateOnFailure: options.autoCompensate }, async (ctx) => {
120
+ const correlationKey = resolve(options.correlationKey, ctx);
121
+ const stackId = typeof ctx?.$stackId === 'function' ? ctx.$stackId() : ctx?.$stackId || ctx?.stackId || correlationKey;
122
+ const expiresAt = Date.now() + ttl;
123
+ const scope = core_1.stepInspectionStorage.getStore();
124
+ const inspection = scope ? { stepId: scope.stepId, stepName: scope.stepName, attempt: scope.attempt, namespace: scope.namespace } : undefined;
125
+ const url = async (approved) => { const u = new URL(options.callbackBaseUrl); if (u.protocol !== 'https:' && u.hostname !== 'localhost')
126
+ throw new Error('Callback URL must use HTTPS'); u.searchParams.set('token', await (0, tokens_1.signActionToken)({ stackId, correlationKey, expectedEvent: approved ? options.onApproveEvent : options.onRejectEvent, approved, expiresAt, inspection }, options.signingSecret)); return u.toString(); };
127
+ const approvalTokens = { approveUrl: await url(true), rejectUrl: await url(false) };
128
+ return { actions: { ...approvalTokens, title: resolve(options.title, ctx), description: options.description === undefined ? undefined : resolve(options.description, ctx) }, extra: { correlationKey, approvalTokens, approvalExpiresAt: expiresAt } };
129
+ });
130
+ }
131
+ //# sourceMappingURL=steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { NotificationChannel, NotificationTransport, ResolvedNotificationMessage } from '../types';
2
+ export declare class MockNotificationTransport implements NotificationTransport {
3
+ readonly channel: NotificationChannel;
4
+ readonly allowedHosts: string[];
5
+ private now;
6
+ readonly messages: ResolvedNotificationMessage[];
7
+ readonly cancellations: {
8
+ id: string;
9
+ reason: string;
10
+ }[];
11
+ constructor(channel?: NotificationChannel, allowedHosts?: string[], now?: () => number);
12
+ send(message: ResolvedNotificationMessage): Promise<{
13
+ id: string;
14
+ timestamp: number;
15
+ }>;
16
+ cancel(id: string, reason: string): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AACnG,qBAAa,yBAA0B,YAAW,qBAAqB;IAGzD,QAAQ,CAAC,OAAO,EAAE,mBAAmB;IAAY,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE;IAAO,OAAO,CAAC,GAAG;IAF9G,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,EAAE,CAAM;IACtD,QAAQ,CAAC,aAAa,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAM;gBACzC,OAAO,GAAE,mBAA6B,EAAW,YAAY,GAAE,MAAM,EAAO,EAAU,GAAG,eAAW;IACnH,IAAI,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACtF,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CACxD"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockNotificationTransport = void 0;
4
+ class MockNotificationTransport {
5
+ channel;
6
+ allowedHosts;
7
+ now;
8
+ messages = [];
9
+ cancellations = [];
10
+ constructor(channel = 'email', allowedHosts = [], now = Date.now) {
11
+ this.channel = channel;
12
+ this.allowedHosts = allowedHosts;
13
+ this.now = now;
14
+ }
15
+ async send(message) { this.messages.push(message); return { id: `mock-${this.messages.length}`, timestamp: this.now() }; }
16
+ async cancel(id, reason) { this.cancellations.push({ id, reason }); }
17
+ }
18
+ exports.MockNotificationTransport = MockNotificationTransport;
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;AACA,MAAa,yBAAyB;IAGf;IAAiD;IAAqC;IAFlG,QAAQ,GAAkC,EAAE,CAAC;IAC7C,aAAa,GAAqC,EAAE,CAAC;IAC9D,YAAqB,UAA+B,OAAO,EAAW,eAAyB,EAAE,EAAU,MAAM,IAAI,CAAC,GAAG;QAApG,YAAO,GAAP,OAAO,CAA+B;QAAW,iBAAY,GAAZ,YAAY,CAAe;QAAU,QAAG,GAAH,GAAG,CAAW;IAAG,CAAC;IAC7H,KAAK,CAAC,IAAI,CAAC,OAAoC,IAAgD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IACnM,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,MAAc,IAAmB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;CACrG;AAND,8DAMC"}
@@ -0,0 +1,18 @@
1
+ import { NotificationChannel, NotificationTransport, ResolvedNotificationMessage } from './index';
2
+ export declare class MockNotificationTransport implements NotificationTransport {
3
+ readonly channel: NotificationChannel;
4
+ readonly allowedHosts: string[];
5
+ private readonly now;
6
+ readonly messages: ResolvedNotificationMessage[];
7
+ readonly cancellations: {
8
+ id: string;
9
+ reason: string;
10
+ }[];
11
+ constructor(channel?: NotificationChannel, allowedHosts?: string[], now?: () => number);
12
+ send(message: ResolvedNotificationMessage): Promise<{
13
+ id: string;
14
+ timestamp: number;
15
+ }>;
16
+ cancel(id: string, reason: string): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAClG,qBAAa,yBAA0B,YAAW,qBAAqB;IAGzD,QAAQ,CAAC,OAAO,EAAE,mBAAmB;IAAY,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE;IAAO,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFvH,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,EAAE,CAAM;IACtD,QAAQ,CAAC,aAAa,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAM;gBACzC,OAAO,GAAE,mBAA6B,EAAW,YAAY,GAAE,MAAM,EAAO,EAAmB,GAAG,eAAW;IAC5H,IAAI,CAAC,OAAO,EAAE,2BAA2B;;;;IACzC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CACxD"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockNotificationTransport = void 0;
4
+ class MockNotificationTransport {
5
+ channel;
6
+ allowedHosts;
7
+ now;
8
+ messages = [];
9
+ cancellations = [];
10
+ constructor(channel = 'email', allowedHosts = [], now = Date.now) {
11
+ this.channel = channel;
12
+ this.allowedHosts = allowedHosts;
13
+ this.now = now;
14
+ }
15
+ async send(message) { this.messages.push(structuredClone(message)); return { id: String(this.messages.length), timestamp: this.now() }; }
16
+ async cancel(id, reason) { this.cancellations.push({ id, reason }); }
17
+ }
18
+ exports.MockNotificationTransport = MockNotificationTransport;
19
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":";;;AACA,MAAa,yBAAyB;IAGf;IAAiD;IAA8C;IAF3G,QAAQ,GAAkC,EAAE,CAAC;IAC7C,aAAa,GAAqC,EAAE,CAAC;IAC9D,YAAqB,UAA+B,OAAO,EAAW,eAAyB,EAAE,EAAmB,MAAM,IAAI,CAAC,GAAG;QAA7G,YAAO,GAAP,OAAO,CAA+B;QAAW,iBAAY,GAAZ,YAAY,CAAe;QAAmB,QAAG,GAAH,GAAG,CAAW;IAAG,CAAC;IACtI,KAAK,CAAC,IAAI,CAAC,OAAoC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IACtK,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,MAAc,IAAmB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;CACrG;AAND,8DAMC"}
@@ -0,0 +1,24 @@
1
+ import { KeyRing } from '@siglo/crypto';
2
+ export interface ActionTokenPayload {
3
+ stackId: string;
4
+ correlationKey: string;
5
+ expectedEvent: string;
6
+ approved: boolean;
7
+ expiresAt: number;
8
+ issuedAt: number;
9
+ nonce: string;
10
+ kid?: string;
11
+ inspection?: {
12
+ stepId: string;
13
+ stepName: string;
14
+ attempt: number;
15
+ namespace?: string;
16
+ };
17
+ }
18
+ export declare function invalidToken(message?: string): never;
19
+ export declare function signActionToken(payload: Omit<ActionTokenPayload, 'nonce' | 'issuedAt' | 'kid'>, secret: string | KeyRing, now?: number): Promise<string>;
20
+ export declare function verifyActionToken(token: string, secret: string | KeyRing, options?: {
21
+ now?: number;
22
+ toleranceSeconds?: number;
23
+ }): Promise<ActionTokenPayload>;
24
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,OAAO,EAAE,MAAM,eAAe,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IAAG,OAAO,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE;AACnR,wBAAgB,YAAY,CAAC,OAAO,SAA2B,GAAG,KAAK,CAEtE;AACD,wBAAsB,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,UAAU,GAAG,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,SAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlK;AACD,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAiBvK"}
package/dist/tokens.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.invalidToken = invalidToken;
4
+ exports.signActionToken = signActionToken;
5
+ exports.verifyActionToken = verifyActionToken;
6
+ const node_crypto_1 = require("node:crypto");
7
+ const crypto_1 = require("@siglo/crypto");
8
+ const core_1 = require("@siglo/core");
9
+ function invalidToken(message = 'Invalid approval token') {
10
+ throw new core_1.ValidationException([{ field: 'token', message }], { phase: 'validation', retryable: false });
11
+ }
12
+ async function signActionToken(payload, secret, now = Date.now()) {
13
+ const active = typeof secret === 'string' ? { key: secret, kid: undefined } : await secret.getActiveKey();
14
+ if (!active.key.length)
15
+ invalidToken('Empty signing key');
16
+ const body = Buffer.from(JSON.stringify({ ...payload, issuedAt: now, nonce: (0, node_crypto_1.randomUUID)(), kid: active.kid })).toString('base64url');
17
+ return `${body}.${(0, node_crypto_1.createHmac)('sha256', active.key).update(body).digest('base64url')}`;
18
+ }
19
+ async function verifyActionToken(token, secret, options = {}) {
20
+ try {
21
+ if (token.length > 16384)
22
+ invalidToken();
23
+ if (!/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{43}$/.test(token))
24
+ invalidToken();
25
+ const parts = token.split('.');
26
+ const p = JSON.parse(Buffer.from(parts[0], 'base64url').toString());
27
+ const key = typeof secret === 'string' ? secret : await secret.getKey(p.kid);
28
+ if (!key || !(0, crypto_1.timingSafeCompare)((0, node_crypto_1.createHmac)('sha256', key).update(parts[0]).digest('base64url'), parts[1], 'base64url'))
29
+ invalidToken();
30
+ const now = options.now ?? Date.now();
31
+ if (![p.stackId, p.correlationKey, p.expectedEvent, p.nonce].every(v => typeof v === 'string' && v.length > 0) || typeof p.approved !== 'boolean' || !Number.isFinite(p.expiresAt) || !Number.isFinite(p.issuedAt) || p.issuedAt > now + (options.toleranceSeconds ?? 0) * 1000)
32
+ invalidToken();
33
+ if (p.inspection && (typeof p.inspection.stepId !== 'string' || !p.inspection.stepId ||
34
+ typeof p.inspection.stepName !== 'string' || !p.inspection.stepName ||
35
+ !Number.isSafeInteger(p.inspection.attempt) || p.inspection.attempt < 1 ||
36
+ (p.inspection.namespace !== undefined && typeof p.inspection.namespace !== 'string')))
37
+ invalidToken();
38
+ if (p.expiresAt <= now)
39
+ invalidToken('Approval token expired');
40
+ return p;
41
+ }
42
+ catch (e) {
43
+ if (e instanceof core_1.ValidationException)
44
+ throw e;
45
+ return invalidToken();
46
+ }
47
+ }
48
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ import { NotificationTransport, ResolvedNotificationMessage } from './types';
2
+ export declare const escapeHtml: (s: string) => string;
3
+ export declare function renderNotificationHtml(m: ResolvedNotificationMessage): string;
4
+ interface HttpTransportOptions {
5
+ apiKey: string;
6
+ from?: string;
7
+ fetch?: typeof fetch;
8
+ }
9
+ export declare class SlackTransport implements NotificationTransport {
10
+ private options;
11
+ readonly channel = "slack";
12
+ readonly allowedHosts: string[];
13
+ constructor(options: HttpTransportOptions);
14
+ private call;
15
+ send(m: ResolvedNotificationMessage): Promise<{
16
+ id: string;
17
+ timestamp: number;
18
+ }>;
19
+ openModal(triggerId: string, view: {
20
+ title: string;
21
+ blocks: unknown[];
22
+ callbackId?: string;
23
+ }, signal?: AbortSignal): Promise<string>;
24
+ cancel(id: string, reason: string): Promise<void>;
25
+ }
26
+ export declare class ResendTransport implements NotificationTransport {
27
+ private options;
28
+ readonly channel = "email";
29
+ readonly allowedHosts: string[];
30
+ constructor(options: HttpTransportOptions);
31
+ send(m: ResolvedNotificationMessage): Promise<{
32
+ id: string;
33
+ timestamp: number;
34
+ }>;
35
+ }
36
+ export declare class SendGridTransport implements NotificationTransport {
37
+ private options;
38
+ readonly channel = "email";
39
+ readonly allowedHosts: string[];
40
+ constructor(options: HttpTransportOptions);
41
+ send(m: ResolvedNotificationMessage): Promise<{
42
+ id: string;
43
+ timestamp: number;
44
+ }>;
45
+ }
46
+ export declare class DiscordTransport implements NotificationTransport {
47
+ private request;
48
+ readonly channel = "discord";
49
+ readonly allowedHosts: string[];
50
+ constructor(request?: typeof fetch);
51
+ send(m: ResolvedNotificationMessage): Promise<{
52
+ id: string;
53
+ timestamp: number;
54
+ }>;
55
+ }
56
+ export declare class TwilioTransport implements NotificationTransport {
57
+ private options;
58
+ readonly channel = "sms";
59
+ readonly allowedHosts: string[];
60
+ constructor(options: {
61
+ accountSid: string;
62
+ authToken: string;
63
+ from: string;
64
+ fetch?: typeof fetch;
65
+ });
66
+ send(m: ResolvedNotificationMessage): Promise<{
67
+ id: string;
68
+ timestamp: number;
69
+ }>;
70
+ }
71
+ /** Inject an SMTP client (e.g. Nodemailer) without imposing an SMTP dependency. */
72
+ export declare class SmtpTransport implements NotificationTransport {
73
+ private options;
74
+ readonly channel = "email";
75
+ readonly allowedHosts: string[];
76
+ constructor(options: {
77
+ host: string;
78
+ from: string;
79
+ sendMail: (mail: {
80
+ from: string;
81
+ to: string;
82
+ subject: string;
83
+ html: string;
84
+ signal?: AbortSignal;
85
+ }) => Promise<{
86
+ messageId: string;
87
+ }>;
88
+ });
89
+ send(m: ResolvedNotificationMessage): Promise<{
90
+ id: string;
91
+ timestamp: number;
92
+ }>;
93
+ }
94
+ export declare class PostmarkTransport implements NotificationTransport {
95
+ private options;
96
+ readonly channel = "email";
97
+ readonly allowedHosts: string[];
98
+ constructor(options: HttpTransportOptions);
99
+ send(message: ResolvedNotificationMessage): Promise<{
100
+ id: string;
101
+ timestamp: number;
102
+ }>;
103
+ }
104
+ /** Sends a provider-neutral JSON push payload to a configured HTTPS webhook. */
105
+ export declare class PushWebhookTransport implements NotificationTransport {
106
+ private options;
107
+ readonly channel = "push";
108
+ readonly allowedHosts: string[];
109
+ constructor(options: {
110
+ url: string;
111
+ headers?: Record<string, string>;
112
+ fetch?: typeof fetch;
113
+ });
114
+ send(message: ResolvedNotificationMessage): Promise<{
115
+ id: string;
116
+ timestamp: number;
117
+ }>;
118
+ }
119
+ export {};
120
+ //# sourceMappingURL=transports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transports.d.ts","sourceRoot":"","sources":["../src/transports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PushWebhookTransport = exports.PostmarkTransport = exports.SmtpTransport = exports.TwilioTransport = exports.DiscordTransport = exports.SendGridTransport = exports.ResendTransport = exports.SlackTransport = exports.escapeHtml = void 0;
4
+ exports.renderNotificationHtml = renderNotificationHtml;
5
+ const egress_1 = require("./egress");
6
+ const http_1 = require("@siglo/http");
7
+ const escapeHtml = (s) => s.replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
8
+ exports.escapeHtml = escapeHtml;
9
+ function renderNotificationHtml(m) {
10
+ const a = m.interactiveActions;
11
+ return `<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1"></head><body style="margin:0;padding:24px;font-family:Arial,sans-serif"><main style="max-width:600px;margin:auto"><p>${(0, exports.escapeHtml)(m.content)}</p>${a ? `<p>${(0, exports.escapeHtml)(a.description ?? '')}</p><a href="${(0, exports.escapeHtml)(a.approveUrl)}">Approve</a> <a href="${(0, exports.escapeHtml)(a.rejectUrl)}">Reject</a>` : ''}</main></body></html>`;
12
+ }
13
+ function recipient(m, channel) {
14
+ if (typeof m.to === 'string')
15
+ return m.to;
16
+ return (channel === 'email' ? m.to.email : channel === 'sms' ? m.to.phone : channel === 'slack' ? m.to.slackChannelId || m.to.slackUserId : m.to.discordWebhookUrl);
17
+ }
18
+ async function post(url, headers, body, signal, request = egress_1.publicNotificationFetch) {
19
+ const u = new URL(url);
20
+ (0, http_1.assertNetworkPolicy)('notificationTransport', url, u.hostname, u.port);
21
+ const r = await request(url, { method: 'POST', headers, body: typeof body === 'string' ? body : JSON.stringify(body), signal, redirect: 'error' });
22
+ if (!r.ok)
23
+ throw new Error(`Notification provider failed (${r.status})`);
24
+ return r.status === 204 ? {} : r.json();
25
+ }
26
+ class SlackTransport {
27
+ options;
28
+ channel = 'slack';
29
+ allowedHosts = ['slack.com'];
30
+ constructor(options) {
31
+ this.options = options;
32
+ }
33
+ async call(method, body, signal) {
34
+ const r = await post(`https://slack.com/api/${method}`, { Authorization: `Bearer ${this.options.apiKey}`, 'Content-Type': 'application/json' }, body, signal, this.options.fetch);
35
+ if (!r.ok)
36
+ throw new Error('Slack notification request failed');
37
+ return r;
38
+ }
39
+ async send(m) {
40
+ const a = m.interactiveActions;
41
+ const r = await this.call('chat.postMessage', { channel: recipient(m, this.channel), text: m.content, blocks: [{ type: 'section', text: { type: 'plain_text', text: m.content } }, ...(a ? [{ type: 'actions', elements: [{ type: 'button', text: { type: 'plain_text', text: 'Approve' }, url: a.approveUrl }, { type: 'button', text: { type: 'plain_text', text: 'Reject' }, url: a.rejectUrl }] }] : [])] }, m.signal);
42
+ return { id: `${r.channel}:${r.ts}`, timestamp: Date.now() };
43
+ }
44
+ async openModal(triggerId, view, signal) {
45
+ if (!triggerId || !view.title || view.title.length > 24)
46
+ throw new Error('A trigger ID and modal title of at most 24 characters are required');
47
+ const result = await this.call('views.open', { trigger_id: triggerId, view: { type: 'modal', title: { type: 'plain_text', text: view.title }, blocks: view.blocks, callback_id: view.callbackId } }, signal);
48
+ return result.view.id;
49
+ }
50
+ async cancel(id, reason) { const [channel, ts] = id.split(':'); await this.call('chat.update', { channel, ts, text: reason, blocks: [] }); }
51
+ }
52
+ exports.SlackTransport = SlackTransport;
53
+ class ResendTransport {
54
+ options;
55
+ channel = 'email';
56
+ allowedHosts = ['api.resend.com'];
57
+ constructor(options) {
58
+ this.options = options;
59
+ }
60
+ async send(m) {
61
+ const r = await post('https://api.resend.com/emails', { Authorization: `Bearer ${this.options.apiKey}`, 'Content-Type': 'application/json', ...(m.idempotencyKey ? { 'Idempotency-Key': m.idempotencyKey } : {}) }, { from: this.options.from, to: recipient(m, this.channel), subject: m.subject ?? m.content, html: renderNotificationHtml(m) }, m.signal, this.options.fetch);
62
+ return { id: r.id, timestamp: Date.now() };
63
+ }
64
+ }
65
+ exports.ResendTransport = ResendTransport;
66
+ class SendGridTransport {
67
+ options;
68
+ channel = 'email';
69
+ allowedHosts = ['api.sendgrid.com'];
70
+ constructor(options) {
71
+ this.options = options;
72
+ }
73
+ async send(m) {
74
+ const url = 'https://api.sendgrid.com/v3/mail/send';
75
+ (0, http_1.assertNetworkPolicy)('sendgrid', url, 'api.sendgrid.com');
76
+ const r = await (this.options.fetch ?? egress_1.publicNotificationFetch)(url, { method: 'POST', redirect: 'error', signal: m.signal, headers: { Authorization: `Bearer ${this.options.apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ personalizations: [{ to: [{ email: recipient(m, this.channel) }] }], from: { email: this.options.from }, subject: m.subject ?? m.content, content: [{ type: 'text/html', value: renderNotificationHtml(m) }] }) });
77
+ if (!r.ok)
78
+ throw new Error(`SendGrid notification failed (${r.status})`);
79
+ return { id: r.headers.get('x-message-id') ?? '', timestamp: Date.now() };
80
+ }
81
+ }
82
+ exports.SendGridTransport = SendGridTransport;
83
+ class DiscordTransport {
84
+ request;
85
+ channel = 'discord';
86
+ allowedHosts = ['discord.com'];
87
+ constructor(request = egress_1.publicNotificationFetch) {
88
+ this.request = request;
89
+ }
90
+ async send(m) {
91
+ const u = new URL(recipient(m, this.channel));
92
+ if (u.protocol !== 'https:' || u.hostname !== 'discord.com' || !u.pathname.startsWith('/api/webhooks/'))
93
+ throw new Error('Invalid Discord webhook');
94
+ u.searchParams.set('wait', 'true');
95
+ const r = await post(u.toString(), { 'Content-Type': 'application/json' }, { content: m.content, allowed_mentions: { parse: [] }, embeds: m.interactiveActions ? [{ title: m.interactiveActions.title, description: `${m.interactiveActions.description ?? ''}\n[Approve](${m.interactiveActions.approveUrl}) · [Reject](${m.interactiveActions.rejectUrl})` }] : undefined }, m.signal, this.request);
96
+ return { id: r.id, timestamp: Date.now() };
97
+ }
98
+ }
99
+ exports.DiscordTransport = DiscordTransport;
100
+ class TwilioTransport {
101
+ options;
102
+ channel = 'sms';
103
+ allowedHosts = ['api.twilio.com'];
104
+ constructor(options) {
105
+ this.options = options;
106
+ }
107
+ async send(m) {
108
+ if (!/^AC[a-zA-Z0-9]+$/.test(this.options.accountSid))
109
+ throw new Error('Invalid Twilio account SID');
110
+ const r = await post(`https://api.twilio.com/2010-04-01/Accounts/${this.options.accountSid}/Messages.json`, { Authorization: `Basic ${Buffer.from(`${this.options.accountSid}:${this.options.authToken}`).toString('base64')}`, 'Content-Type': 'application/x-www-form-urlencoded' }, new URLSearchParams({ To: recipient(m, this.channel), From: this.options.from, Body: m.content }).toString(), m.signal, this.options.fetch);
111
+ return { id: r.sid, timestamp: Date.now() };
112
+ }
113
+ }
114
+ exports.TwilioTransport = TwilioTransport;
115
+ /** Inject an SMTP client (e.g. Nodemailer) without imposing an SMTP dependency. */
116
+ class SmtpTransport {
117
+ options;
118
+ channel = 'email';
119
+ allowedHosts;
120
+ constructor(options) {
121
+ this.options = options;
122
+ this.allowedHosts = [options.host];
123
+ }
124
+ async send(m) {
125
+ (0, http_1.assertNetworkPolicy)('smtp', `smtp://${this.options.host}`, this.options.host);
126
+ const r = await this.options.sendMail({ from: this.options.from, to: recipient(m, this.channel), subject: m.subject ?? m.content, html: renderNotificationHtml(m), signal: m.signal });
127
+ return { id: r.messageId, timestamp: Date.now() };
128
+ }
129
+ }
130
+ exports.SmtpTransport = SmtpTransport;
131
+ class PostmarkTransport {
132
+ options;
133
+ channel = 'email';
134
+ allowedHosts = ['api.postmarkapp.com'];
135
+ constructor(options) {
136
+ this.options = options;
137
+ }
138
+ async send(message) {
139
+ const result = await post('https://api.postmarkapp.com/email', { 'X-Postmark-Server-Token': this.options.apiKey, 'Content-Type': 'application/json' }, {
140
+ From: this.options.from, To: recipient(message, 'email'), Subject: message.subject ?? message.content,
141
+ HtmlBody: renderNotificationHtml(message), TextBody: message.content
142
+ }, message.signal, this.options.fetch);
143
+ if (result.ErrorCode)
144
+ throw new Error('Postmark notification request failed');
145
+ return { id: result.MessageID, timestamp: Date.now() };
146
+ }
147
+ }
148
+ exports.PostmarkTransport = PostmarkTransport;
149
+ /** Sends a provider-neutral JSON push payload to a configured HTTPS webhook. */
150
+ class PushWebhookTransport {
151
+ options;
152
+ channel = 'push';
153
+ allowedHosts;
154
+ constructor(options) {
155
+ this.options = options;
156
+ const url = new URL(options.url);
157
+ if (url.protocol !== 'https:' || url.username || url.password)
158
+ throw new Error('Push webhook must use HTTPS without URL credentials');
159
+ this.allowedHosts = [url.hostname];
160
+ }
161
+ async send(message) {
162
+ const result = await post(this.options.url, { ...this.options.headers, 'Content-Type': 'application/json', ...(message.idempotencyKey ? { 'Idempotency-Key': message.idempotencyKey } : {}) }, {
163
+ to: typeof message.to === 'string' ? message.to : message.to.pushToken,
164
+ title: message.subject, body: message.content, actions: message.interactiveActions, metadata: message.metadata
165
+ }, message.signal, this.options.fetch);
166
+ if (typeof result.id !== 'string' || !result.id)
167
+ throw new Error('Push webhook response must contain a message id');
168
+ return { id: result.id, timestamp: Date.now() };
169
+ }
170
+ }
171
+ exports.PushWebhookTransport = PushWebhookTransport;
172
+ //# sourceMappingURL=transports.js.map