@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.
- package/LICENSE +21 -0
- package/README.md +478 -2
- package/dist/approvals.d.ts +38 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +111 -0
- package/dist/approvals.js.map +1 -0
- package/dist/callbacks.d.ts +14 -0
- package/dist/callbacks.d.ts.map +1 -0
- package/dist/callbacks.js +144 -0
- package/dist/callbacks.js.map +1 -0
- package/dist/dispatch.d.ts +26 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +42 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/egress.d.ts +8 -0
- package/dist/egress.d.ts.map +1 -0
- package/dist/egress.js +96 -0
- package/dist/egress.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/monitor.d.ts +10 -0
- package/dist/monitor.d.ts.map +1 -0
- package/dist/monitor.js +20 -0
- package/dist/monitor.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +15 -0
- package/dist/registry.js.map +1 -0
- package/dist/sms.d.ts +28 -0
- package/dist/sms.d.ts.map +1 -0
- package/dist/sms.js +43 -0
- package/dist/sms.js.map +1 -0
- package/dist/steps.d.ts +7 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +131 -0
- package/dist/steps.js.map +1 -0
- package/dist/testing/index.d.ts +18 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +19 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing.d.ts +18 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +19 -0
- package/dist/testing.js.map +1 -0
- package/dist/tokens.d.ts +24 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +48 -0
- package/dist/tokens.js.map +1 -0
- package/dist/transports.d.ts +120 -0
- package/dist/transports.d.ts.map +1 -0
- package/dist/transports.js +172 -0
- package/dist/transports.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- 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"}
|
package/dist/testing.js
ADDED
|
@@ -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"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -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 => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[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
|