@better-forge/notify 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +11 -3
- package/dist/alertPolicy.d.ts +41 -0
- package/dist/alertPolicy.d.ts.map +1 -0
- package/dist/alertPolicy.js +137 -0
- package/dist/alertPolicy.js.map +1 -0
- package/dist/channelInput.d.ts +34 -0
- package/dist/channelInput.d.ts.map +1 -0
- package/dist/channelInput.js +156 -0
- package/dist/channelInput.js.map +1 -0
- package/dist/channelSecrets.d.ts +8 -0
- package/dist/channelSecrets.d.ts.map +1 -0
- package/dist/channelSecrets.js +17 -0
- package/dist/channelSecrets.js.map +1 -0
- package/dist/engine.d.ts +68 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +314 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.d.ts +907 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +86 -0
- package/dist/migrations.js.map +1 -0
- package/dist/notice.d.ts +55 -0
- package/dist/notice.d.ts.map +1 -0
- package/dist/notice.js +25 -0
- package/dist/notice.js.map +1 -0
- package/dist/routes.d.ts +74 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +302 -0
- package/dist/routes.js.map +1 -0
- package/dist/tables.d.ts +919 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +68 -0
- package/dist/tables.js.map +1 -0
- package/dist/template.d.ts +35 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +72 -0
- package/dist/template.js.map +1 -0
- package/dist/transports.d.ts +50 -0
- package/dist/transports.d.ts.map +1 -0
- package/dist/transports.js +274 -0
- package/dist/transports.js.map +1 -0
- package/dist/worker.d.ts +25 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +38 -0
- package/dist/worker.js.map +1 -0
- package/package.json +59 -6
- package/src/alertPolicy.ts +149 -0
- package/src/channelInput.ts +153 -0
- package/src/channelSecrets.ts +20 -0
- package/src/engine.ts +423 -0
- package/src/index.ts +16 -0
- package/src/migrations.ts +88 -0
- package/src/notice.ts +68 -0
- package/src/routes.ts +362 -0
- package/src/tables.ts +96 -0
- package/src/template.ts +76 -0
- package/src/transports.ts +351 -0
- package/src/worker.ts +58 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../src/tables.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEjE;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,uGAAuG;IACvG,cAAc,EAAE,MAAM,CAAA;IACtB,8DAA8D;IAC9D,gBAAgB,EAAE,MAAM,CAAA;IACxB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,UAAU,CAAC;QAAC,YAAY,EAAE,UAAU,CAAA;KAAE,CAAA;CACjE;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoE7D;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA"}
|
package/dist/tables.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { sealedText } from '@better-forge/vault';
|
|
3
|
+
const plain = sealedText({ seal: (value) => value, open: (value) => value });
|
|
4
|
+
export function defineNotifyTables(options) {
|
|
5
|
+
const sealed = options.sealed ?? { channelConfig: plain, smtpPassword: plain };
|
|
6
|
+
const notificationChannels = sqliteTable('notification_channels', {
|
|
7
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
8
|
+
name: text('name').notNull(),
|
|
9
|
+
type: text('type').notNull(), // 'email' | 'webhook'
|
|
10
|
+
config: sealed.channelConfig('config').notNull().default('{}'), // JSON
|
|
11
|
+
enabled: integer('enabled').notNull().default(1),
|
|
12
|
+
notifyOnRecovery: integer('notify_on_recovery').notNull().default(0),
|
|
13
|
+
createdAt: integer('created_at').notNull(),
|
|
14
|
+
updatedAt: integer('updated_at').notNull(),
|
|
15
|
+
// Additive columns (added via ALTER TABLE, must stay at end for sqlite-proxy position mapping)
|
|
16
|
+
alertPolicy: text('alert_policy').notNull().default('{}'), // JSON alert policy
|
|
17
|
+
/** Stable identifier for config-as-code and API clients; unlike `id` it survives export/import. */
|
|
18
|
+
key: text('key').notNull().unique().$defaultFn(options.newKey),
|
|
19
|
+
});
|
|
20
|
+
const notificationDeliveries = sqliteTable('notification_deliveries', {
|
|
21
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
22
|
+
channelId: integer('channel_id').notNull(),
|
|
23
|
+
channelName: text('channel_name').notNull(),
|
|
24
|
+
channelType: text('channel_type').notNull(),
|
|
25
|
+
sourceId: integer(options.sourceIdColumn),
|
|
26
|
+
sourceName: text(options.sourceNameColumn).notNull(),
|
|
27
|
+
eventType: text('event_type').notNull(),
|
|
28
|
+
status: text('status').notNull().default('pending'),
|
|
29
|
+
targetStatus: text('target_status').notNull(),
|
|
30
|
+
previousStatus: text('previous_status').notNull(),
|
|
31
|
+
variables: text('variables').notNull(),
|
|
32
|
+
attemptCount: integer('attempt_count').notNull().default(0),
|
|
33
|
+
maxAttempts: integer('max_attempts').notNull().default(3),
|
|
34
|
+
nextAttemptAt: integer('next_attempt_at'),
|
|
35
|
+
lastAttemptAt: integer('last_attempt_at'),
|
|
36
|
+
deliveredAt: integer('delivered_at'),
|
|
37
|
+
lastError: text('last_error'),
|
|
38
|
+
createdAt: integer('created_at').notNull(),
|
|
39
|
+
updatedAt: integer('updated_at').notNull(),
|
|
40
|
+
// Additive columns (added via ALTER TABLE, must stay at end for sqlite-proxy position mapping)
|
|
41
|
+
suppressionReason: text('suppression_reason'),
|
|
42
|
+
groupKey: text('group_key'),
|
|
43
|
+
});
|
|
44
|
+
const notificationDeliveryAttempts = sqliteTable('notification_delivery_attempts', {
|
|
45
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
46
|
+
deliveryId: integer('delivery_id').notNull(),
|
|
47
|
+
attemptNumber: integer('attempt_number').notNull(),
|
|
48
|
+
status: text('status').notNull(),
|
|
49
|
+
error: text('error'),
|
|
50
|
+
startedAt: integer('started_at').notNull(),
|
|
51
|
+
completedAt: integer('completed_at').notNull(),
|
|
52
|
+
});
|
|
53
|
+
const smtpSettings = sqliteTable('smtp_settings', {
|
|
54
|
+
id: integer('id').primaryKey(),
|
|
55
|
+
host: text('host').notNull().default(''),
|
|
56
|
+
port: integer('port').notNull().default(587),
|
|
57
|
+
secure: integer('secure').notNull().default(0),
|
|
58
|
+
user: text('user').notNull().default(''),
|
|
59
|
+
password: sealed.smtpPassword('password').notNull().default(''),
|
|
60
|
+
fromAddress: text('from_address').notNull().default(''),
|
|
61
|
+
fromName: text('from_name').notNull().default(options.smtpFromName),
|
|
62
|
+
updatedAt: integer('updated_at').notNull(),
|
|
63
|
+
// Additive columns (added via ALTER TABLE, must stay at end for sqlite-proxy position mapping)
|
|
64
|
+
vaultConfig: text('vault_config'), // JSON vault reference | null
|
|
65
|
+
});
|
|
66
|
+
return { notificationChannels, notificationDeliveries, notificationDeliveryAttempts, smtpSettings };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=tables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.js","sourceRoot":"","sources":["../src/tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AACpE,OAAO,EAAE,UAAU,EAAmB,MAAM,qBAAqB,CAAA;AAsBjE,MAAM,KAAK,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAA;AAE5E,MAAM,UAAU,kBAAkB,CAAC,OAA2B;IAC5D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAA;IAE9E,MAAM,oBAAoB,GAAG,WAAW,CAAC,uBAAuB,EAAE;QAChE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;QAC5B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,sBAAsB;QACpD,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO;QACvE,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAChD,gBAAgB,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACpE,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QAC1C,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QAC1C,+FAA+F;QAC/F,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,oBAAoB;QAC/E,mGAAmG;QACnG,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;KAC/D,CAAC,CAAA;IAEF,MAAM,sBAAsB,GAAG,WAAW,CAAC,yBAAyB,EAAE;QACpE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QACrD,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QAC1C,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;QAC3C,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;QAC3C,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC;QACzC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,OAAO,EAAE;QACpD,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QACvC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;QACnD,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE;QAC7C,cAAc,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE;QACjD,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE;QACtC,YAAY,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3D,WAAW,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACzD,aAAa,EAAE,OAAO,CAAC,iBAAiB,CAAC;QACzC,aAAa,EAAE,OAAO,CAAC,iBAAiB,CAAC;QACzC,WAAW,EAAE,OAAO,CAAC,cAAc,CAAC;QACpC,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC;QAC7B,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QAC1C,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QAC1C,+FAA+F;QAC/F,iBAAiB,EAAE,IAAI,CAAC,oBAAoB,CAAC;QAC7C,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC;KAC5B,CAAC,CAAA;IAEF,MAAM,4BAA4B,GAAG,WAAW,CAAC,gCAAgC,EAAE;QACjF,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QACrD,UAAU,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE;QAC5C,aAAa,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC,OAAO,EAAE;QAClD,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE;QAChC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;QACpB,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QAC1C,WAAW,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;KAC/C,CAAC,CAAA;IAEF,MAAM,YAAY,GAAG,WAAW,CAAC,eAAe,EAAE;QAChD,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;QAC9B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;QAC5C,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9C,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/D,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC;QACnE,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;QAC1C,+FAA+F;QAC/F,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,EAAE,8BAA8B;KAClE,CAAC,CAAA;IAEF,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,4BAA4B,EAAE,YAAY,EAAE,CAAA;AACrG,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Replaces `{{name}}` with its value; a name without a value stays as written. `escape` is applied to each value. */
|
|
2
|
+
export declare function substituteVars(template: string, vars: Record<string, string>, escape?: (value: string) => string): string;
|
|
3
|
+
/** Escapes a value for use inside a JSON string literal in a template. */
|
|
4
|
+
export declare function escapeJsonString(value: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Encodes a value as one piece of a path or query. A value that is only dots would be a dot segment (`..` climbs a level, and
|
|
7
|
+
* so does its `%2E` form), so its dots are encoded once more and arrive as the text `%2E`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function encodeUrlPart(value: string): string;
|
|
10
|
+
/** Whether a placeholder sits in the scheme, host or port of a URL, where it would choose the address the request goes to. */
|
|
11
|
+
export declare function placeholderInAuthority(url: string): boolean;
|
|
12
|
+
/** Drops line breaks and NUL from a value that goes into a header, where they would split or reject the request. */
|
|
13
|
+
export declare function stripLineBreaks(value: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* The request a webhook configuration describes once its {{variables}} are filled in. A value is one piece of a
|
|
16
|
+
* path or query in the URL (so it cannot add a segment, a parameter or a host), a JSON string in the body and a single
|
|
17
|
+
* line in a header.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildWebhookRequest(config: {
|
|
20
|
+
url: string;
|
|
21
|
+
method: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
body?: string;
|
|
24
|
+
}, vars: Record<string, string>): {
|
|
25
|
+
url: string;
|
|
26
|
+
init: {
|
|
27
|
+
method: string;
|
|
28
|
+
headers: Record<string, string>;
|
|
29
|
+
body?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** How often a delivery is tried before it is given up on, and how long to wait before each retry. */
|
|
33
|
+
export declare const MAX_DELIVERY_ATTEMPTS = 3;
|
|
34
|
+
export declare const RETRY_DELAYS_MS: number[];
|
|
35
|
+
//# sourceMappingURL=template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA,sHAAsH;AACtH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAKzH;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAYD,8HAA8H;AAC9H,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAS3D;AAED,oHAAoH;AACpH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACxF,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAc3F;AAED,sGAAsG;AACtG,eAAO,MAAM,qBAAqB,IAAI,CAAA;AACtC,eAAO,MAAM,eAAe,UAAuB,CAAA"}
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Replaces `{{name}}` with its value; a name without a value stays as written. `escape` is applied to each value. */
|
|
2
|
+
export function substituteVars(template, vars, escape) {
|
|
3
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
4
|
+
const value = vars[key];
|
|
5
|
+
return value === undefined ? `{{${key}}}` : escape ? escape(value) : value;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
/** Escapes a value for use inside a JSON string literal in a template. */
|
|
9
|
+
export function escapeJsonString(value) {
|
|
10
|
+
return JSON.stringify(value).slice(1, -1);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Encodes a value as one piece of a path or query. A value that is only dots would be a dot segment (`..` climbs a level, and
|
|
14
|
+
* so does its `%2E` form), so its dots are encoded once more and arrive as the text `%2E`.
|
|
15
|
+
*/
|
|
16
|
+
export function encodeUrlPart(value) {
|
|
17
|
+
const text = withoutLoneSurrogates(value);
|
|
18
|
+
return /^\.{1,2}$/.test(text) ? text.replace(/\./g, '%252E') : encodeURIComponent(text);
|
|
19
|
+
}
|
|
20
|
+
/** `encodeURIComponent` throws on a lone surrogate; it becomes U+FFFD, as in any other encoding of the text. */
|
|
21
|
+
function withoutLoneSurrogates(value) {
|
|
22
|
+
let out = '';
|
|
23
|
+
for (const char of value) {
|
|
24
|
+
const code = char.codePointAt(0);
|
|
25
|
+
out += code >= 0xd800 && code <= 0xdfff ? String.fromCharCode(0xfffd) : char;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
/** Whether a placeholder sits in the scheme, host or port of a URL, where it would choose the address the request goes to. */
|
|
30
|
+
export function placeholderInAuthority(url) {
|
|
31
|
+
if (!url.includes('{{'))
|
|
32
|
+
return false;
|
|
33
|
+
// Asked of the URL parser itself, which skips extra slashes (`https:///{{x}}/`) and reads `\` as `/`: a placeholder is
|
|
34
|
+
// in the authority when two different values for it lead to two different hosts, or to no valid URL at all.
|
|
35
|
+
const hostFor = (value) => {
|
|
36
|
+
try {
|
|
37
|
+
return new URL(url.replace(/\{\{\w+\}\}/g, value)).host;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const first = hostFor('aaaa');
|
|
44
|
+
return first === null || first !== hostFor('bbbb');
|
|
45
|
+
}
|
|
46
|
+
/** Drops line breaks and NUL from a value that goes into a header, where they would split or reject the request. */
|
|
47
|
+
export function stripLineBreaks(value) {
|
|
48
|
+
return value.replace(/[\r\n\0]/g, '');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The request a webhook configuration describes once its {{variables}} are filled in. A value is one piece of a
|
|
52
|
+
* path or query in the URL (so it cannot add a segment, a parameter or a host), a JSON string in the body and a single
|
|
53
|
+
* line in a header.
|
|
54
|
+
*/
|
|
55
|
+
export function buildWebhookRequest(config, vars) {
|
|
56
|
+
// A value is text for a path or query; in the host it would let whoever controls the value choose where the request goes.
|
|
57
|
+
if (placeholderInAuthority(config.url))
|
|
58
|
+
throw new Error('A placeholder cannot be part of the host of the URL');
|
|
59
|
+
const url = substituteVars(config.url, vars, encodeUrlPart);
|
|
60
|
+
const body = config.body ? substituteVars(config.body, vars, escapeJsonString) : undefined;
|
|
61
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
62
|
+
if (config.headers) {
|
|
63
|
+
for (const [k, v] of Object.entries(config.headers)) {
|
|
64
|
+
headers[k] = substituteVars(v, vars, stripLineBreaks);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { url, init: { method: config.method ?? 'POST', headers, ...(body !== undefined ? { body } : {}) } };
|
|
68
|
+
}
|
|
69
|
+
/** How often a delivery is tried before it is given up on, and how long to wait before each retry. */
|
|
70
|
+
export const MAX_DELIVERY_ATTEMPTS = 3;
|
|
71
|
+
export const RETRY_DELAYS_MS = [60_000, 5 * 60_000];
|
|
72
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { VaultRef } from '@better-forge/contracts';
|
|
2
|
+
import { type Notice } from './notice.js';
|
|
3
|
+
import type { SmtpMessage } from './engine.js';
|
|
4
|
+
import { type EventWording } from './channelInput.js';
|
|
5
|
+
export interface TransportOptions {
|
|
6
|
+
/** Resolves a vault reference to its secret fields (channel credentials may be kept in a vault). */
|
|
7
|
+
resolveSecret: (ref: VaultRef) => Promise<Record<string, string>>;
|
|
8
|
+
/** Sends one mail through the application's SMTP settings. */
|
|
9
|
+
sendMail: (message: SmtpMessage) => Promise<void>;
|
|
10
|
+
/** Discord only accepts a public image URL for a webhook avatar; it cannot be uploaded as a file. */
|
|
11
|
+
discordAvatarUrl: string;
|
|
12
|
+
/**
|
|
13
|
+
* The subject and body a new email channel starts with. A channel that still has them is written from the notice,
|
|
14
|
+
* so every kind of event gets wording of its own; a template the channel's owner edited is used as it is.
|
|
15
|
+
*/
|
|
16
|
+
standardEmail?: {
|
|
17
|
+
subject: string;
|
|
18
|
+
body: string;
|
|
19
|
+
};
|
|
20
|
+
/** Which kinds of event the channel's own message fields word, and the wording of the others when the channel has none. */
|
|
21
|
+
wording?: EventWording;
|
|
22
|
+
/**
|
|
23
|
+
* Opens the requests to an address the channel's owner typed in (a webhook, a Slack, Discord or Teams URL, an ntfy
|
|
24
|
+
* server of the channel's own), so that a product which must keep them away from some networks passes a fetch that refuses them. The
|
|
25
|
+
* requests to the fixed hosts of Telegram, Pushover and ntfy.sh use the global fetch. Defaults to the global fetch.
|
|
26
|
+
*/
|
|
27
|
+
fetch?: typeof fetch;
|
|
28
|
+
}
|
|
29
|
+
/** The part of a stored channel that picks and configures its transport. */
|
|
30
|
+
export interface TransportChannel {
|
|
31
|
+
type: string;
|
|
32
|
+
/** JSON configuration of the channel. */
|
|
33
|
+
config: string;
|
|
34
|
+
}
|
|
35
|
+
type Vars = Record<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* The channel senders. An email and a webhook are written by the user as templates over `vars`; the other channels
|
|
38
|
+
* format the `notice` themselves and only read `vars` to fill in the free text the user typed into the channel.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createTransports(options: TransportOptions): {
|
|
41
|
+
deliver: (channel: TransportChannel, vars: Vars, notice: Notice) => Promise<void>;
|
|
42
|
+
sendEmail: (config: {
|
|
43
|
+
to: string;
|
|
44
|
+
subject?: string;
|
|
45
|
+
body?: string;
|
|
46
|
+
}, vars: Vars, notice?: Notice) => Promise<void>;
|
|
47
|
+
};
|
|
48
|
+
export type Transports = ReturnType<typeof createTransports>;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=transports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transports.d.ts","sourceRoot":"","sources":["../src/transports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAoD,KAAK,MAAM,EAAuB,MAAM,aAAa,CAAA;AAChH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGrE,MAAM,WAAW,gBAAgB;IAC/B,oGAAoG;IACpG,aAAa,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACjE,8DAA8D;IAC9D,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,qGAAqG;IACrG,gBAAgB,EAAE,MAAM,CAAA;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjD,2HAA2H;IAC3H,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAA;CACf;AAgHD,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAElC;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB;uBAqLxB,gBAAgB,QAAQ,IAAI,UAAU,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;wBAzK3D;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,QAAQ,IAAI,WAAW,MAAM;EAyL9G;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { plainSegments, readableTimestamp, renderSegments } from './notice.js';
|
|
2
|
+
import { configForEvent } from './channelInput.js';
|
|
3
|
+
import { buildWebhookRequest, encodeUrlPart, substituteVars } from './template.js';
|
|
4
|
+
const OUTBOUND_TIMEOUT_MS = 15_000;
|
|
5
|
+
const EMAIL_MAX_BODY = 10_000;
|
|
6
|
+
const SEVERITY_EMOJI = { critical: '🔴', warning: '🟡', ok: '🟢' };
|
|
7
|
+
/** Telegram HTML and Slack mrkdwn both need exactly `&`, `<` and `>` escaped; Slack reads `<...>` as a link or a mention. */
|
|
8
|
+
const escapeHtml = (value) => value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
9
|
+
/** Neutralises markdown (links, emphasis, quotes) in text that a sender, not the channel's owner, controls. */
|
|
10
|
+
const escapeMarkdown = (value) => value.replace(/[\\*_~`|>[\]()<#]/g, '\\$&');
|
|
11
|
+
/** Discord also pings for `@everyone`, `@here` and `<@id>` in a message body; an invisible space breaks each of them. */
|
|
12
|
+
const ZERO_WIDTH_SPACE = String.fromCharCode(0x200b);
|
|
13
|
+
const escapeDiscordText = (value) => escapeMarkdown(value).replace(/@/g, `@${ZERO_WIDTH_SPACE}`);
|
|
14
|
+
/**
|
|
15
|
+
* A MessageCard understands only a small part of markdown and may not honour a backslash, so instead of escaping, the
|
|
16
|
+
* two things that make a link or a tag are broken with an invisible space: `](` and `<`.
|
|
17
|
+
*/
|
|
18
|
+
const escapeTeamsText = (value) => value.replace(/\]\(/g, `]${ZERO_WIDTH_SPACE}(`).replace(/</g, `<${ZERO_WIDTH_SPACE}`);
|
|
19
|
+
/**
|
|
20
|
+
* `escapeMarkdown`, cut so the result is at most `max` characters: whole escaped characters only, so no backslash is left
|
|
21
|
+
* at the end without the character it escapes. A cut text ends in an ellipsis.
|
|
22
|
+
*/
|
|
23
|
+
function escapeWithin(value, max) {
|
|
24
|
+
const whole = escapeMarkdown(value);
|
|
25
|
+
if (whole.length <= max)
|
|
26
|
+
return whole;
|
|
27
|
+
let out = '';
|
|
28
|
+
for (const char of value) {
|
|
29
|
+
const escaped = escapeMarkdown(char);
|
|
30
|
+
if (out.length + escaped.length > max - 1)
|
|
31
|
+
break;
|
|
32
|
+
out += escaped;
|
|
33
|
+
}
|
|
34
|
+
return `${out}…`;
|
|
35
|
+
}
|
|
36
|
+
/** The variables with the error text of the check, which a sender can write, run through `escape`. */
|
|
37
|
+
const withEscapedError = (vars, escape) => vars['error_message'] === undefined ? vars : { ...vars, error_message: escape(vars['error_message']) };
|
|
38
|
+
/** Cuts `value` to `max` characters without splitting a surrogate pair. */
|
|
39
|
+
function clip(value, max) {
|
|
40
|
+
const chars = Array.from(value);
|
|
41
|
+
return chars.length <= max ? value : `${chars.slice(0, max - 1).join('')}…`;
|
|
42
|
+
}
|
|
43
|
+
/** Cuts `value` to `maxBytes` of UTF-8 without splitting a character. */
|
|
44
|
+
function clipBytes(value, maxBytes) {
|
|
45
|
+
if (Buffer.byteLength(value) <= maxBytes)
|
|
46
|
+
return value;
|
|
47
|
+
let out = '';
|
|
48
|
+
for (const char of value) {
|
|
49
|
+
if (Buffer.byteLength(out + char + '…') > maxBytes)
|
|
50
|
+
break;
|
|
51
|
+
out += char;
|
|
52
|
+
}
|
|
53
|
+
return `${out}…`;
|
|
54
|
+
}
|
|
55
|
+
/** One outbound notification request; a non-2xx answer fails the delivery as `<failurePrefix> <status>`. */
|
|
56
|
+
async function sendHttp(doFetch, url, init, failurePrefix) {
|
|
57
|
+
const res = await doFetch(url, { ...init, signal: AbortSignal.timeout(OUTBOUND_TIMEOUT_MS) });
|
|
58
|
+
if (!res.ok)
|
|
59
|
+
throw new Error(`${failurePrefix} ${res.status}`);
|
|
60
|
+
}
|
|
61
|
+
function postWebhookJson(doFetch, url, payload, label) {
|
|
62
|
+
return sendHttp(doFetch, url, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'Content-Type': 'application/json' },
|
|
65
|
+
body: JSON.stringify(payload),
|
|
66
|
+
}, `${label} webhook returned HTTP`);
|
|
67
|
+
}
|
|
68
|
+
/** Throws `<service> API returned HTTP <status>[: <reason>]`; the reason is the service's own error text, never a credential. */
|
|
69
|
+
async function failOnHttpError(res, service, reason) {
|
|
70
|
+
if (res.ok)
|
|
71
|
+
return;
|
|
72
|
+
const text = await res.json().then((j) => (j && typeof j === 'object' && !Array.isArray(j) ? reason(j) : '')).catch(() => '');
|
|
73
|
+
throw new Error(`${service} API returned HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : ''}`);
|
|
74
|
+
}
|
|
75
|
+
const TELEGRAM_MAX_LENGTH = 4096;
|
|
76
|
+
/** Keeps the optional Message Text from crowding out the facts; the detail field is what gets cut to fit. */
|
|
77
|
+
const TELEGRAM_MAX_TEXT_LINE = 1000;
|
|
78
|
+
const NTFY_DEFAULT_SERVER = 'https://ntfy.sh';
|
|
79
|
+
/** ntfy's default message limit is 4096 bytes, not characters. */
|
|
80
|
+
const NTFY_MAX_MESSAGE_BYTES = 4000;
|
|
81
|
+
const HEADER_SAFE_TOKEN = /^[\x21-\x7e]+$/;
|
|
82
|
+
const NTFY_PRIORITY = { critical: 4, warning: 3, ok: 3 };
|
|
83
|
+
const NTFY_TAGS = { critical: 'rotating_light', warning: 'warning', ok: 'white_check_mark' };
|
|
84
|
+
const PUSHOVER_URL = 'https://api.pushover.net/1/messages.json';
|
|
85
|
+
const PUSHOVER_MAX_MESSAGE = 1024;
|
|
86
|
+
/** Pushover priority 1 is high: it bypasses the recipient's quiet hours. Only a critical event gets it. */
|
|
87
|
+
const PUSHOVER_PRIORITY = { critical: 1, warning: 0, ok: 0 };
|
|
88
|
+
/** Discord refuses an embed field value of more than 1024 characters. */
|
|
89
|
+
const DISCORD_MAX_FIELD_VALUE = 1024;
|
|
90
|
+
const DISCORD_COLORS = { critical: 0xe53935, warning: 0xfb8c00, ok: 0x43a047 };
|
|
91
|
+
const TEAMS_COLORS = { critical: 'E53935', warning: 'FB8C00', ok: '43A047' };
|
|
92
|
+
const SLACK_COLORS = { critical: '#E53935', warning: '#FB8C00', ok: '#43A047' };
|
|
93
|
+
/**
|
|
94
|
+
* The channel senders. An email and a webhook are written by the user as templates over `vars`; the other channels
|
|
95
|
+
* format the `notice` themselves and only read `vars` to fill in the free text the user typed into the channel.
|
|
96
|
+
*/
|
|
97
|
+
export function createTransports(options) {
|
|
98
|
+
const doFetch = options.fetch ?? fetch;
|
|
99
|
+
/** A credential typed into the channel, or the `value` secret of its vault reference. */
|
|
100
|
+
async function resolveToken(direct, vault, vaultProblem) {
|
|
101
|
+
if (!vault)
|
|
102
|
+
return direct ?? '';
|
|
103
|
+
const secret = await options.resolveSecret(vault);
|
|
104
|
+
const token = (secret['value'] ?? '').trim();
|
|
105
|
+
if (!token)
|
|
106
|
+
throw new Error(vaultProblem);
|
|
107
|
+
return token;
|
|
108
|
+
}
|
|
109
|
+
async function sendEmail(config, vars, notice) {
|
|
110
|
+
const standard = options.standardEmail;
|
|
111
|
+
const isStandard = !!standard && config.subject === standard.subject && config.body === standard.body;
|
|
112
|
+
// A part without wording of its own, or one that still has the stock template, is written from the notice.
|
|
113
|
+
const written = notice && (isStandard || !config.subject || !config.body) ? plainAlert(notice, vars, undefined, EMAIL_MAX_BODY) : undefined;
|
|
114
|
+
await options.sendMail({
|
|
115
|
+
to: substituteVars(config.to, vars),
|
|
116
|
+
subject: written && (isStandard || !config.subject) ? written.title : substituteVars(config.subject ?? '', vars),
|
|
117
|
+
text: written && (isStandard || !config.body) ? written.body : substituteVars(config.body ?? '', vars),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async function sendWebhook(config, vars) {
|
|
121
|
+
const { url, init } = buildWebhookRequest(config, vars);
|
|
122
|
+
await sendHttp(doFetch, url, init, 'HTTP');
|
|
123
|
+
}
|
|
124
|
+
async function sendDiscord(config, vars, notice) {
|
|
125
|
+
const embed = {
|
|
126
|
+
title: renderSegments(notice.headline, { subject: (v) => `\`${v}\``, state: (v) => `**${v}**` }),
|
|
127
|
+
color: DISCORD_COLORS[notice.severity],
|
|
128
|
+
fields: [
|
|
129
|
+
...notice.facts.map(({ label, value }) => ({ name: label, value, inline: true })),
|
|
130
|
+
...(notice.detail ? [{ name: notice.detail.label, value: escapeWithin(notice.detail.value, DISCORD_MAX_FIELD_VALUE), inline: false }] : []),
|
|
131
|
+
],
|
|
132
|
+
footer: { text: `${notice.atLabel} ${notice.at}` },
|
|
133
|
+
timestamp: new Date().toISOString(),
|
|
134
|
+
};
|
|
135
|
+
const payload = { embeds: [embed] };
|
|
136
|
+
if (config.username)
|
|
137
|
+
payload['username'] = config.username;
|
|
138
|
+
payload['avatar_url'] = config.avatarUrl ? substituteVars(config.avatarUrl, vars, encodeUrlPart) : options.discordAvatarUrl;
|
|
139
|
+
if (config.content)
|
|
140
|
+
payload['content'] = substituteVars(config.content, withEscapedError(vars, escapeDiscordText));
|
|
141
|
+
await postWebhookJson(doFetch, config.webhookUrl, payload, 'Discord');
|
|
142
|
+
}
|
|
143
|
+
async function sendTeams(config, vars, notice) {
|
|
144
|
+
// The card title is led by the emoji and the bold subject; the kind of subject is left to the summary.
|
|
145
|
+
const bold = { kind: () => '', subject: (v) => `**${v}**`, state: (v) => `**${v}**` };
|
|
146
|
+
const summary = config.summary ? substituteVars(config.summary, vars) : plainSegments(notice.headline);
|
|
147
|
+
const facts = [
|
|
148
|
+
...notice.facts.map((fact) => ({ name: fact.fullLabel ?? fact.label, value: fact.value })),
|
|
149
|
+
...(notice.detail ? [{ name: notice.detail.label, value: escapeTeamsText(notice.detail.value) }] : []),
|
|
150
|
+
{ name: notice.atLabel, value: notice.at },
|
|
151
|
+
];
|
|
152
|
+
const payload = {
|
|
153
|
+
'@type': 'MessageCard',
|
|
154
|
+
'@context': 'https://schema.org/extensions',
|
|
155
|
+
themeColor: TEAMS_COLORS[notice.severity],
|
|
156
|
+
summary,
|
|
157
|
+
sections: [{
|
|
158
|
+
activityTitle: `${SEVERITY_EMOJI[notice.severity]} ${renderSegments(notice.headline, bold)}`,
|
|
159
|
+
activitySubtitle: renderSegments(notice.subtitle, bold),
|
|
160
|
+
facts,
|
|
161
|
+
markdown: true,
|
|
162
|
+
}],
|
|
163
|
+
};
|
|
164
|
+
await postWebhookJson(doFetch, config.webhookUrl, payload, 'Teams');
|
|
165
|
+
}
|
|
166
|
+
async function sendSlack(config, vars, notice) {
|
|
167
|
+
const headline = renderSegments(notice.headline, { subject: (v) => `*${v}*`, state: (v) => `*${v}*` });
|
|
168
|
+
const fallbackText = `${SEVERITY_EMOJI[notice.severity]} ${headline}`;
|
|
169
|
+
const fields = [
|
|
170
|
+
...notice.facts.map(({ label, value }) => ({ type: 'mrkdwn', text: `*${label}:*\n${value}` })),
|
|
171
|
+
...(notice.detail ? [{ type: 'mrkdwn', text: `*${notice.detail.label}:*\n${escapeHtml(notice.detail.value)}` }] : []),
|
|
172
|
+
];
|
|
173
|
+
const blocks = [
|
|
174
|
+
{ type: 'section', text: { type: 'mrkdwn', text: fallbackText } },
|
|
175
|
+
{ type: 'section', fields },
|
|
176
|
+
{ type: 'context', elements: [{ type: 'mrkdwn', text: `${notice.atLabel} ${notice.at}` }] },
|
|
177
|
+
];
|
|
178
|
+
const payload = {
|
|
179
|
+
text: fallbackText,
|
|
180
|
+
attachments: [{ color: SLACK_COLORS[notice.severity], blocks }],
|
|
181
|
+
};
|
|
182
|
+
if (config.text)
|
|
183
|
+
payload['text'] = substituteVars(config.text, vars, escapeHtml) + '\n' + fallbackText;
|
|
184
|
+
await postWebhookJson(doFetch, config.webhookUrl, payload, 'Slack');
|
|
185
|
+
}
|
|
186
|
+
async function sendTelegram(config, vars, notice) {
|
|
187
|
+
const botToken = await resolveToken(config.botToken, config.vault, 'Telegram vault secret has no bot token: use a value secret');
|
|
188
|
+
const headline = renderSegments(notice.headline, { text: escapeHtml, subject: (v) => `<b>${v}</b>`, state: (v) => `<b>${v}</b>` });
|
|
189
|
+
const detail = notice.detail ? escapeHtml(notice.detail.value) : '';
|
|
190
|
+
const build = (detailText) => [
|
|
191
|
+
...(config.text ? [escapeHtml(Array.from(substituteVars(config.text, vars)).slice(0, TELEGRAM_MAX_TEXT_LINE).join(''))] : []),
|
|
192
|
+
`${SEVERITY_EMOJI[notice.severity]} ${headline}`,
|
|
193
|
+
'',
|
|
194
|
+
...notice.facts.map(({ label, value }) => `<b>${label}:</b> ${escapeHtml(value)}`),
|
|
195
|
+
...(detailText ? [`<b>${notice.detail.label}:</b> ${detailText}`] : []),
|
|
196
|
+
`<i>${notice.atLabel} ${escapeHtml(readableTimestamp(notice.at))}</i>`,
|
|
197
|
+
].join('\n');
|
|
198
|
+
// Cutting the finished message could split an HTML tag, so an oversized one loses the tail of the details field only.
|
|
199
|
+
let text = build(detail);
|
|
200
|
+
if (text.length > TELEGRAM_MAX_LENGTH && detail) {
|
|
201
|
+
const keep = Math.max(0, detail.length - (text.length - TELEGRAM_MAX_LENGTH) - 1);
|
|
202
|
+
text = build(`${detail.slice(0, keep).replace(/&[a-z]*$/, '')}…`);
|
|
203
|
+
}
|
|
204
|
+
// The bot token is part of the URL path. Only the status code and Telegram's own description are reported
|
|
205
|
+
// (the Bot API never echoes the token), so the token never reaches the delivery history.
|
|
206
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
headers: { 'Content-Type': 'application/json' },
|
|
209
|
+
body: JSON.stringify({ chat_id: config.chatId.trim().replace(/^#(?=-?\d)/, ''), text, parse_mode: 'HTML', link_preview_options: { is_disabled: true } }),
|
|
210
|
+
signal: AbortSignal.timeout(OUTBOUND_TIMEOUT_MS),
|
|
211
|
+
});
|
|
212
|
+
await failOnHttpError(res, 'Telegram', (j) => typeof j['description'] === 'string' ? j['description'] : '');
|
|
213
|
+
}
|
|
214
|
+
/** The plain-text alert the push channels (ntfy, Pushover) show: a headline and the facts under it. */
|
|
215
|
+
function plainAlert(notice, vars, customText, maxBody) {
|
|
216
|
+
const body = [
|
|
217
|
+
...(customText ? [clip(substituteVars(customText, vars), TELEGRAM_MAX_TEXT_LINE)] : []),
|
|
218
|
+
...notice.facts.map(({ label, value }) => `${label}: ${value}`),
|
|
219
|
+
...(notice.detail ? [`${notice.detail.label}: ${notice.detail.value}`] : []),
|
|
220
|
+
`${notice.atLabel} ${readableTimestamp(notice.at)}`,
|
|
221
|
+
].join('\n');
|
|
222
|
+
return { title: clip(plainSegments(notice.headline), 250), body: clip(body, maxBody) };
|
|
223
|
+
}
|
|
224
|
+
async function sendNtfy(config, vars, notice) {
|
|
225
|
+
const token = await resolveToken(config.accessToken, config.vault, 'ntfy vault secret has no access token: use a value secret');
|
|
226
|
+
// fetch quotes an invalid header value in its error, which would put the token into the delivery history.
|
|
227
|
+
if (token && !HEADER_SAFE_TOKEN.test(token))
|
|
228
|
+
throw new Error('ntfy access token contains characters that cannot be sent in a header');
|
|
229
|
+
const { title, body } = plainAlert(notice, vars, config.text, NTFY_MAX_MESSAGE_BYTES);
|
|
230
|
+
// A server behind a reverse proxy may live under a path prefix, which publishing keeps.
|
|
231
|
+
const customServer = config.serverUrl?.trim();
|
|
232
|
+
const target = new URL(customServer || NTFY_DEFAULT_SERVER);
|
|
233
|
+
if (!target.pathname.endsWith('/'))
|
|
234
|
+
target.pathname += '/';
|
|
235
|
+
// Publishing as JSON to the server root keeps non-ASCII titles intact, which a header-based publish would not.
|
|
236
|
+
const res = await (customServer ? doFetch : fetch)(target.href, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
|
239
|
+
body: JSON.stringify({ topic: config.topic.trim(), title, message: clipBytes(body, NTFY_MAX_MESSAGE_BYTES), priority: NTFY_PRIORITY[notice.severity], tags: [NTFY_TAGS[notice.severity]] }),
|
|
240
|
+
signal: AbortSignal.timeout(OUTBOUND_TIMEOUT_MS),
|
|
241
|
+
redirect: 'error',
|
|
242
|
+
});
|
|
243
|
+
await failOnHttpError(res, 'ntfy', (j) => typeof j['error'] === 'string' ? j['error'] : '');
|
|
244
|
+
}
|
|
245
|
+
async function sendPushover(config, vars, notice) {
|
|
246
|
+
const token = await resolveToken(config.appToken, config.vault, 'Pushover vault secret has no application token: use a value secret');
|
|
247
|
+
const { title, body } = plainAlert(notice, vars, config.text, PUSHOVER_MAX_MESSAGE);
|
|
248
|
+
const res = await fetch(PUSHOVER_URL, {
|
|
249
|
+
method: 'POST',
|
|
250
|
+
headers: { 'Content-Type': 'application/json' },
|
|
251
|
+
body: JSON.stringify({ token, user: config.userKey.trim(), title, message: body, priority: PUSHOVER_PRIORITY[notice.severity] }),
|
|
252
|
+
signal: AbortSignal.timeout(OUTBOUND_TIMEOUT_MS),
|
|
253
|
+
redirect: 'error',
|
|
254
|
+
});
|
|
255
|
+
await failOnHttpError(res, 'Pushover', (j) => Array.isArray(j['errors']) ? j['errors'].filter((e) => typeof e === 'string').join('; ') : '');
|
|
256
|
+
}
|
|
257
|
+
/** Sends one event through the channel; throws when the channel type is unknown or the send fails. */
|
|
258
|
+
async function deliver(channel, vars, notice) {
|
|
259
|
+
const config = configForEvent(channel.type, JSON.parse(channel.config), vars['event_type'], options.wording);
|
|
260
|
+
switch (channel.type) {
|
|
261
|
+
case 'email': return sendEmail(config, vars, notice);
|
|
262
|
+
case 'webhook': return sendWebhook(config, vars);
|
|
263
|
+
case 'discord': return sendDiscord(config, vars, notice);
|
|
264
|
+
case 'teams': return sendTeams(config, vars, notice);
|
|
265
|
+
case 'slack': return sendSlack(config, vars, notice);
|
|
266
|
+
case 'telegram': return sendTelegram(config, vars, notice);
|
|
267
|
+
case 'ntfy': return sendNtfy(config, vars, notice);
|
|
268
|
+
case 'pushover': return sendPushover(config, vars, notice);
|
|
269
|
+
default: throw new Error(`Unsupported notification channel type: ${channel.type}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { deliver, sendEmail };
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=transports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transports.js","sourceRoot":"","sources":["../src/transports.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { NotifyEngine } from './engine.js';
|
|
2
|
+
/** Work the application runs on the same ticks as the deliveries. A rejection is logged under `failure`, never thrown. */
|
|
3
|
+
export interface WorkerTask {
|
|
4
|
+
/** The line logged when the task fails (the error follows it). */
|
|
5
|
+
failure: string;
|
|
6
|
+
run: () => Promise<unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface DeliveryWorkerOptions {
|
|
9
|
+
engine: Pick<NotifyEngine, 'processDue' | 'purgeOldDeliveries'>;
|
|
10
|
+
/** Run every 30 seconds beside the sending of due deliveries (retries, digests that came due). */
|
|
11
|
+
retryTasks?: readonly WorkerTask[];
|
|
12
|
+
/** Run daily at 03:00 beside the purge of old delivery history. */
|
|
13
|
+
purgeTasks?: readonly WorkerTask[];
|
|
14
|
+
/** Prefix of the line logged when the worker starts. */
|
|
15
|
+
logPrefix?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sends the deliveries that came due (retries and digests) every 30 seconds and purges old history daily. Does not
|
|
19
|
+
* wait for a pass to finish before the next one is due; the engine takes care of overlapping passes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createDeliveryWorker(options: DeliveryWorkerOptions): {
|
|
22
|
+
start: () => void;
|
|
23
|
+
stop: () => void;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,0HAA0H;AAC1H,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,oBAAoB,CAAC,CAAA;IAC/D,kGAAkG;IAClG,UAAU,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;IAClC,mEAAmE;IACnE,UAAU,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;IAClC,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB;iBAQ/C,IAAI;gBAiBL,IAAI;EAQtB"}
|