@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BetterStatusPage contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @better-forge/notify
|
|
2
|
+
|
|
3
|
+
Notification channels, delivery history, retries and alert hygiene.
|
|
4
|
+
|
|
5
|
+
- `createNotifyEngine` records every notification, applies a channel's quiet hours, rate cap and digest window, sends with retries and sends SMTP mail. The application injects the sender, the digest wording, the names of the events hygiene treats specially and the secret resolver.
|
|
6
|
+
- `createTransports` holds the senders for email, webhook, Discord, Teams, Slack, Telegram, ntfy and Pushover. The rich channels format a neutral `Notice` (severity, headline segments, facts, detail, time) that the application builds from its own event.
|
|
7
|
+
- `createNotifyRoutes` makes the admin routes: delivery history and retry, channel management with secret masking, and SMTP settings. The application injects audit, identity, secret masking, the vault-use guard, the names its API uses for what a notification is about, and the test sends.
|
|
8
|
+
- `createDeliveryWorker` sends the deliveries that came due every 30 seconds and purges old history daily, and runs the extra tasks the application adds to those ticks.
|
|
9
|
+
- `defineNotifyTables` / `createNotifyDbModule` define the tables and their migrations; the application passes the names of the columns that identify what a delivery is about.
|
|
10
|
+
|
|
11
|
+
Depends on: `@better-forge/db`, `@better-forge/contracts`, `@better-forge/vault`, `@better-forge/audit`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { QuietHoursPolicy } from '@better-forge/contracts';
|
|
2
|
+
export declare function isValidTimezone(timezone: string): boolean;
|
|
3
|
+
/** Minutes elapsed since local midnight in `timezone` at the given instant. */
|
|
4
|
+
export declare function localMinutes(at: number, timezone: string): number;
|
|
5
|
+
/** True when `at` falls inside the channel's quiet window. Windows where end <= start wrap midnight. */
|
|
6
|
+
export declare function isWithinQuietHours(policy: QuietHoursPolicy, at: number): boolean;
|
|
7
|
+
/** The instant the current quiet window ends. Only meaningful while `isWithinQuietHours` holds. */
|
|
8
|
+
export declare function quietHoursEndAt(policy: QuietHoursPolicy, at: number): number;
|
|
9
|
+
export interface ThrottlePolicy {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
maxAlerts: number;
|
|
12
|
+
windowMinutes: number;
|
|
13
|
+
}
|
|
14
|
+
export interface GroupingPolicy {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
minSources: number;
|
|
17
|
+
windowSeconds: number;
|
|
18
|
+
}
|
|
19
|
+
export interface AlertPolicy {
|
|
20
|
+
quietHours: QuietHoursPolicy;
|
|
21
|
+
throttle: ThrottlePolicy;
|
|
22
|
+
grouping: GroupingPolicy;
|
|
23
|
+
}
|
|
24
|
+
export declare const DEFAULT_ALERT_POLICY: AlertPolicy;
|
|
25
|
+
/**
|
|
26
|
+
* Alert policy parsing for an application that stores and sends the "at least this many sources" setting of the
|
|
27
|
+
* grouping under `minSourcesKey`. The package works on `minSources`; `toWire` gives the stored form back, with the
|
|
28
|
+
* keys in the order they have always been written in.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createAlertPolicy(options: {
|
|
31
|
+
minSourcesKey: string;
|
|
32
|
+
}): {
|
|
33
|
+
normalizeAlertPolicy: (input: unknown) => AlertPolicy;
|
|
34
|
+
parseAlertPolicy: (stored: string | null | undefined) => AlertPolicy;
|
|
35
|
+
toWire: (policy: AlertPolicy) => {
|
|
36
|
+
quietHours: QuietHoursPolicy;
|
|
37
|
+
throttle: ThrottlePolicy;
|
|
38
|
+
grouping: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=alertPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alertPolicy.d.ts","sourceRoot":"","sources":["../src/alertPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAQ/D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOzD;AAWD,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKjE;AAQD,wGAAwG;AACxG,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAOhF;AAED,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAQ5E;AAsBD,MAAM,WAAW,cAAc;IAAG,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE;AAC9F,MAAM,WAAW,cAAc;IAAG,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE;AAC/F,MAAM,WAAW,WAAW;IAAG,UAAU,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE;AAEjH,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE;kCAE7B,OAAO,KAAG,WAAW;+BA+BxB,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,WAAW;qBAUjD,WAAW,KAAG;QAAE,UAAU,EAAE,gBAAgB,CAAC;QAAC,QAAQ,EAAE,cAAc,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE;EASpI"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const MINUTE_MS = 60_000;
|
|
2
|
+
const DAY_MINUTES = 24 * 60;
|
|
3
|
+
const TIME_PATTERN = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
4
|
+
const formatters = new Map();
|
|
5
|
+
export function isValidTimezone(timezone) {
|
|
6
|
+
try {
|
|
7
|
+
new Intl.DateTimeFormat('en-GB', { timeZone: timezone });
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function formatter(timezone) {
|
|
15
|
+
let cached = formatters.get(timezone);
|
|
16
|
+
if (!cached) {
|
|
17
|
+
cached = new Intl.DateTimeFormat('en-GB', { timeZone: timezone, hour: '2-digit', minute: '2-digit', hourCycle: 'h23' });
|
|
18
|
+
formatters.set(timezone, cached);
|
|
19
|
+
}
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
/** Minutes elapsed since local midnight in `timezone` at the given instant. */
|
|
23
|
+
export function localMinutes(at, timezone) {
|
|
24
|
+
const parts = formatter(isValidTimezone(timezone) ? timezone : 'UTC').formatToParts(new Date(at));
|
|
25
|
+
const hour = Number(parts.find((part) => part.type === 'hour')?.value ?? '0');
|
|
26
|
+
const minute = Number(parts.find((part) => part.type === 'minute')?.value ?? '0');
|
|
27
|
+
return hour * 60 + minute;
|
|
28
|
+
}
|
|
29
|
+
function timeToMinutes(value) {
|
|
30
|
+
const match = TIME_PATTERN.exec(value);
|
|
31
|
+
if (!match)
|
|
32
|
+
return 0;
|
|
33
|
+
return Number(match[1]) * 60 + Number(match[2]);
|
|
34
|
+
}
|
|
35
|
+
/** True when `at` falls inside the channel's quiet window. Windows where end <= start wrap midnight. */
|
|
36
|
+
export function isWithinQuietHours(policy, at) {
|
|
37
|
+
if (!policy.enabled)
|
|
38
|
+
return false;
|
|
39
|
+
const start = timeToMinutes(policy.start);
|
|
40
|
+
const end = timeToMinutes(policy.end);
|
|
41
|
+
if (start === end)
|
|
42
|
+
return false; // zero-length window — nothing is ever quiet
|
|
43
|
+
const now = localMinutes(at, policy.timezone);
|
|
44
|
+
return start < end ? now >= start && now < end : now >= start || now < end;
|
|
45
|
+
}
|
|
46
|
+
/** The instant the current quiet window ends. Only meaningful while `isWithinQuietHours` holds. */
|
|
47
|
+
export function quietHoursEndAt(policy, at) {
|
|
48
|
+
const end = timeToMinutes(policy.end);
|
|
49
|
+
const now = localMinutes(at, policy.timezone);
|
|
50
|
+
const minutesAhead = (end - now + DAY_MINUTES) % DAY_MINUTES || DAY_MINUTES;
|
|
51
|
+
let candidate = at - (at % MINUTE_MS) + minutesAhead * MINUTE_MS;
|
|
52
|
+
// A DST shift inside the window moves the wall clock under us; nudge forward until it is over.
|
|
53
|
+
for (let guard = 0; guard < 8 && isWithinQuietHours(policy, candidate); guard++)
|
|
54
|
+
candidate += 30 * MINUTE_MS;
|
|
55
|
+
return candidate;
|
|
56
|
+
}
|
|
57
|
+
function asRecord(value) {
|
|
58
|
+
return value && typeof value === 'object' ? value : {};
|
|
59
|
+
}
|
|
60
|
+
function asBool(value, fallback) {
|
|
61
|
+
if (typeof value === 'boolean')
|
|
62
|
+
return value;
|
|
63
|
+
if (value === 1 || value === 0)
|
|
64
|
+
return value === 1;
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
function asInt(value, min, max, fallback) {
|
|
68
|
+
const parsed = typeof value === 'number' ? value : Number(value);
|
|
69
|
+
if (!Number.isFinite(parsed))
|
|
70
|
+
return fallback;
|
|
71
|
+
return Math.min(max, Math.max(min, Math.round(parsed)));
|
|
72
|
+
}
|
|
73
|
+
function asTime(value, fallback) {
|
|
74
|
+
return typeof value === 'string' && TIME_PATTERN.test(value) ? value : fallback;
|
|
75
|
+
}
|
|
76
|
+
export const DEFAULT_ALERT_POLICY = {
|
|
77
|
+
quietHours: { enabled: false, start: '22:00', end: '07:00', timezone: 'UTC', mode: 'defer' },
|
|
78
|
+
throttle: { enabled: false, maxAlerts: 3, windowMinutes: 60 },
|
|
79
|
+
grouping: { enabled: false, minSources: 3, windowSeconds: 60 },
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Alert policy parsing for an application that stores and sends the "at least this many sources" setting of the
|
|
83
|
+
* grouping under `minSourcesKey`. The package works on `minSources`; `toWire` gives the stored form back, with the
|
|
84
|
+
* keys in the order they have always been written in.
|
|
85
|
+
*/
|
|
86
|
+
export function createAlertPolicy(options) {
|
|
87
|
+
/** Coerces untrusted input (API body or stored JSON) into a complete, in-range policy. */
|
|
88
|
+
function normalizeAlertPolicy(input) {
|
|
89
|
+
const raw = asRecord(input);
|
|
90
|
+
const quiet = asRecord(raw['quietHours']);
|
|
91
|
+
const throttle = asRecord(raw['throttle']);
|
|
92
|
+
const grouping = asRecord(raw['grouping']);
|
|
93
|
+
const timezone = typeof quiet['timezone'] === 'string' && isValidTimezone(quiet['timezone'])
|
|
94
|
+
? quiet['timezone']
|
|
95
|
+
: DEFAULT_ALERT_POLICY.quietHours.timezone;
|
|
96
|
+
return {
|
|
97
|
+
quietHours: {
|
|
98
|
+
enabled: asBool(quiet['enabled'], DEFAULT_ALERT_POLICY.quietHours.enabled),
|
|
99
|
+
start: asTime(quiet['start'], DEFAULT_ALERT_POLICY.quietHours.start),
|
|
100
|
+
end: asTime(quiet['end'], DEFAULT_ALERT_POLICY.quietHours.end),
|
|
101
|
+
timezone,
|
|
102
|
+
mode: quiet['mode'] === 'suppress' ? 'suppress' : 'defer',
|
|
103
|
+
},
|
|
104
|
+
throttle: {
|
|
105
|
+
enabled: asBool(throttle['enabled'], DEFAULT_ALERT_POLICY.throttle.enabled),
|
|
106
|
+
maxAlerts: asInt(throttle['maxAlerts'], 1, 100, DEFAULT_ALERT_POLICY.throttle.maxAlerts),
|
|
107
|
+
windowMinutes: asInt(throttle['windowMinutes'], 1, 1440, DEFAULT_ALERT_POLICY.throttle.windowMinutes),
|
|
108
|
+
},
|
|
109
|
+
grouping: {
|
|
110
|
+
enabled: asBool(grouping['enabled'], DEFAULT_ALERT_POLICY.grouping.enabled),
|
|
111
|
+
minSources: asInt(grouping[options.minSourcesKey], 2, 100, DEFAULT_ALERT_POLICY.grouping.minSources),
|
|
112
|
+
windowSeconds: asInt(grouping['windowSeconds'], 10, 900, DEFAULT_ALERT_POLICY.grouping.windowSeconds),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Reads a channel's stored `alert_policy` column, falling back to defaults on anything unusable. */
|
|
117
|
+
function parseAlertPolicy(stored) {
|
|
118
|
+
if (!stored)
|
|
119
|
+
return normalizeAlertPolicy({});
|
|
120
|
+
try {
|
|
121
|
+
return normalizeAlertPolicy(JSON.parse(stored));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return normalizeAlertPolicy({});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** The form that is stored and sent: `minSources` under the application's key, the other keys in their usual order. */
|
|
128
|
+
function toWire(policy) {
|
|
129
|
+
return {
|
|
130
|
+
quietHours: policy.quietHours,
|
|
131
|
+
throttle: policy.throttle,
|
|
132
|
+
grouping: { enabled: policy.grouping.enabled, [options.minSourcesKey]: policy.grouping.minSources, windowSeconds: policy.grouping.windowSeconds },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return { normalizeAlertPolicy, parseAlertPolicy, toWire };
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=alertPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alertPolicy.js","sourceRoot":"","sources":["../src/alertPolicy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { NotificationChannelType } from '@better-forge/contracts';
|
|
2
|
+
export declare const CHANNEL_TYPES: readonly NotificationChannelType[];
|
|
3
|
+
/** Telegram needs a chat and a token, direct or from the vault; an edited mask is not a token. */
|
|
4
|
+
export declare function telegramConfigError(config: unknown): string | null;
|
|
5
|
+
/** ntfy needs a topic; the server defaults to ntfy.sh and the access token is optional. */
|
|
6
|
+
export declare function ntfyConfigError(config: unknown): string | null;
|
|
7
|
+
/** Pushover needs a user or group key and an application token, direct or from the vault. */
|
|
8
|
+
export declare function pushoverConfigError(config: unknown): string | null;
|
|
9
|
+
/** The first problem with a channel's config that depends on its type, or null. */
|
|
10
|
+
export declare function channelConfigError(type: string, config: unknown): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* The fields of a channel that word a message, per type. A channel may carry its own wording for one kind of event
|
|
13
|
+
* (`eventOverrides`, keyed by the event type) made of these fields only; the destination and the credentials always
|
|
14
|
+
* come from the channel itself, so an override can never send a message somewhere else.
|
|
15
|
+
*/
|
|
16
|
+
export declare const TEMPLATE_KEYS: Record<NotificationChannelType, readonly string[]>;
|
|
17
|
+
/** What is wrong with the `eventOverrides` of a channel's config, or null. */
|
|
18
|
+
export declare function eventOverridesError(type: string, config: unknown): string | null;
|
|
19
|
+
/** The wording a kind of event has when its channel has none of its own: by channel type, then message field. */
|
|
20
|
+
export type DefaultWording = Record<string, Partial<Record<string, Record<string, string>>>>;
|
|
21
|
+
/** How a channel words each kind of event: its own message fields for `ownEvents`, its override or the default for any other. */
|
|
22
|
+
export interface EventWording {
|
|
23
|
+
ownEvents: readonly string[];
|
|
24
|
+
defaults?: DefaultWording;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The config a message of the given kind of event is sent with. The channel's own message fields word the kinds in
|
|
28
|
+
* `wording.ownEvents`. Any other kind is worded by what the channel has for it (`eventOverrides`), and by the default
|
|
29
|
+
* for the fields it has nothing for, never by the channel's own fields, which were written for something else. A field
|
|
30
|
+
* left without wording is sent in the channel's standard form. Without `wording`, every kind uses the channel's own
|
|
31
|
+
* fields and an override only replaces some of them.
|
|
32
|
+
*/
|
|
33
|
+
export declare function configForEvent(type: string, config: Record<string, unknown>, event: string | undefined, wording?: EventWording): Record<string, unknown>;
|
|
34
|
+
//# sourceMappingURL=channelInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channelInput.d.ts","sourceRoot":"","sources":["../src/channelInput.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AAGtE,eAAO,MAAM,aAAa,EAAE,SAAS,uBAAuB,EAAsF,CAAA;AAuBlJ,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIlE;AAID,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAoB9D;AAED,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAIlE;AAED,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAW/E;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,uBAAuB,EAAE,SAAS,MAAM,EAAE,CAS5E,CAAA;AAMD,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgBhF;AAED,iHAAiH;AACjH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AAE5F,iIAAiI;AACjI,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,cAAc,CAAA;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBxJ"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { placeholderInAuthority } from './template.js';
|
|
2
|
+
export const CHANNEL_TYPES = ['email', 'webhook', 'discord', 'teams', 'slack', 'telegram', 'ntfy', 'pushover'];
|
|
3
|
+
function asConfig(config) {
|
|
4
|
+
return (config && typeof config === 'object' ? config : {});
|
|
5
|
+
}
|
|
6
|
+
const isText = (value) => typeof value === 'string' && value.trim() !== '';
|
|
7
|
+
/**
|
|
8
|
+
* A credential is a vault reference or a direct token; an edited mask is not a token. A missing one is reported
|
|
9
|
+
* as `missing`, or accepted when `missing` is null (a public ntfy topic needs none).
|
|
10
|
+
*/
|
|
11
|
+
function credentialError(c, key, label, missing) {
|
|
12
|
+
if (c.vault)
|
|
13
|
+
return Number.isInteger(c.vault.vaultId) && Number.isInteger(c.vault.secretId) && Number(c.vault.secretId) > 0 ? null : `Pick a vault secret for the ${label}`;
|
|
14
|
+
const token = c[key];
|
|
15
|
+
if (token === undefined || token === null || token === '')
|
|
16
|
+
return missing;
|
|
17
|
+
if (typeof token !== 'string')
|
|
18
|
+
return `The ${label} must be text`;
|
|
19
|
+
if (!token.trim())
|
|
20
|
+
return missing;
|
|
21
|
+
return token.includes('•') ? `Paste the full ${label} to replace the stored one` : null;
|
|
22
|
+
}
|
|
23
|
+
/** Telegram needs a chat and a token, direct or from the vault; an edited mask is not a token. */
|
|
24
|
+
export function telegramConfigError(config) {
|
|
25
|
+
const c = asConfig(config);
|
|
26
|
+
if (!isText(c['chatId']))
|
|
27
|
+
return 'Telegram needs a Chat ID';
|
|
28
|
+
return credentialError(c, 'botToken', 'bot token', 'Telegram needs a Bot Token');
|
|
29
|
+
}
|
|
30
|
+
const NTFY_TOPIC = /^[A-Za-z0-9_-]{1,64}$/;
|
|
31
|
+
/** ntfy needs a topic; the server defaults to ntfy.sh and the access token is optional. */
|
|
32
|
+
export function ntfyConfigError(config) {
|
|
33
|
+
const c = asConfig(config);
|
|
34
|
+
if (!isText(c['topic']) || !NTFY_TOPIC.test(c['topic'].trim()))
|
|
35
|
+
return 'ntfy needs a topic of up to 64 letters, digits, "-" or "_"';
|
|
36
|
+
const server = c['serverUrl'];
|
|
37
|
+
if (server !== undefined && server !== null && server !== '') {
|
|
38
|
+
if (typeof server !== 'string')
|
|
39
|
+
return 'Server URL must be text';
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(server);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return 'Server URL is not a valid URL';
|
|
46
|
+
}
|
|
47
|
+
if (!/^https?:$/.test(url.protocol))
|
|
48
|
+
return 'Server URL must start with http:// or https://';
|
|
49
|
+
// The URL is not a masked field, so it must not carry a login, and a query would end up in the middle of the publish URL.
|
|
50
|
+
if (url.username || url.password || url.search || url.hash)
|
|
51
|
+
return 'Server URL must not contain a login, query or fragment';
|
|
52
|
+
}
|
|
53
|
+
const problem = credentialError(c, 'accessToken', 'access token', null);
|
|
54
|
+
if (problem)
|
|
55
|
+
return problem;
|
|
56
|
+
// A token goes into a header; a character that cannot be sent there makes every delivery fail.
|
|
57
|
+
return typeof c['accessToken'] === 'string' && c['accessToken'] && !c.vault && !/^[\x21-\x7e•]+$/.test(c['accessToken']) ? 'The access token contains characters that cannot be sent in a header' : null;
|
|
58
|
+
}
|
|
59
|
+
/** Pushover needs a user or group key and an application token, direct or from the vault. */
|
|
60
|
+
export function pushoverConfigError(config) {
|
|
61
|
+
const c = asConfig(config);
|
|
62
|
+
if (!isText(c['userKey']))
|
|
63
|
+
return 'Pushover needs a User Key';
|
|
64
|
+
return credentialError(c, 'appToken', 'application token', 'Pushover needs an Application Token');
|
|
65
|
+
}
|
|
66
|
+
/** The first problem with a channel's config that depends on its type, or null. */
|
|
67
|
+
export function channelConfigError(type, config) {
|
|
68
|
+
const overridesProblem = eventOverridesError(type, config);
|
|
69
|
+
if (overridesProblem)
|
|
70
|
+
return overridesProblem;
|
|
71
|
+
if (type === 'telegram')
|
|
72
|
+
return telegramConfigError(config);
|
|
73
|
+
if (type === 'ntfy')
|
|
74
|
+
return ntfyConfigError(config);
|
|
75
|
+
if (type === 'pushover')
|
|
76
|
+
return pushoverConfigError(config);
|
|
77
|
+
if (type === 'webhook') {
|
|
78
|
+
const url = asConfig(config)['url'];
|
|
79
|
+
if (typeof url === 'string' && placeholderInAuthority(url))
|
|
80
|
+
return 'A placeholder cannot be part of the host of the URL';
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The fields of a channel that word a message, per type. A channel may carry its own wording for one kind of event
|
|
86
|
+
* (`eventOverrides`, keyed by the event type) made of these fields only; the destination and the credentials always
|
|
87
|
+
* come from the channel itself, so an override can never send a message somewhere else.
|
|
88
|
+
*/
|
|
89
|
+
export const TEMPLATE_KEYS = {
|
|
90
|
+
email: ['subject', 'body'],
|
|
91
|
+
webhook: ['body'],
|
|
92
|
+
discord: ['content'],
|
|
93
|
+
teams: ['summary'],
|
|
94
|
+
slack: ['text'],
|
|
95
|
+
telegram: ['text'],
|
|
96
|
+
ntfy: ['text'],
|
|
97
|
+
pushover: ['text'],
|
|
98
|
+
};
|
|
99
|
+
const MAX_OVERRIDE_TEXT = 10_000;
|
|
100
|
+
const MAX_OVERRIDE_EVENTS = 8;
|
|
101
|
+
const EVENT_KEY = /^[a-z_]{1,32}$/;
|
|
102
|
+
/** What is wrong with the `eventOverrides` of a channel's config, or null. */
|
|
103
|
+
export function eventOverridesError(type, config) {
|
|
104
|
+
const overrides = asConfig(config)['eventOverrides'];
|
|
105
|
+
if (overrides === undefined)
|
|
106
|
+
return null;
|
|
107
|
+
if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides))
|
|
108
|
+
return 'eventOverrides must be an object';
|
|
109
|
+
const events = Object.entries(overrides);
|
|
110
|
+
if (events.length > MAX_OVERRIDE_EVENTS)
|
|
111
|
+
return `eventOverrides can hold at most ${MAX_OVERRIDE_EVENTS} events`;
|
|
112
|
+
const allowed = TEMPLATE_KEYS[type] ?? [];
|
|
113
|
+
for (const [event, fields] of events) {
|
|
114
|
+
if (!EVENT_KEY.test(event) || event in Object.prototype || event === 'prototype')
|
|
115
|
+
return `eventOverrides has an invalid event name "${event}"`;
|
|
116
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields))
|
|
117
|
+
return `eventOverrides.${event} must be an object`;
|
|
118
|
+
for (const [field, value] of Object.entries(fields)) {
|
|
119
|
+
if (!allowed.includes(field))
|
|
120
|
+
return `eventOverrides.${event}.${field} is not a message field of a ${type} channel (${allowed.join(', ')})`;
|
|
121
|
+
if (typeof value !== 'string' || value.length > MAX_OVERRIDE_TEXT)
|
|
122
|
+
return `eventOverrides.${event}.${field} must be text of up to ${MAX_OVERRIDE_TEXT} characters`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The config a message of the given kind of event is sent with. The channel's own message fields word the kinds in
|
|
129
|
+
* `wording.ownEvents`. Any other kind is worded by what the channel has for it (`eventOverrides`), and by the default
|
|
130
|
+
* for the fields it has nothing for, never by the channel's own fields, which were written for something else. A field
|
|
131
|
+
* left without wording is sent in the channel's standard form. Without `wording`, every kind uses the channel's own
|
|
132
|
+
* fields and an override only replaces some of them.
|
|
133
|
+
*/
|
|
134
|
+
export function configForEvent(type, config, event, wording) {
|
|
135
|
+
const keys = TEMPLATE_KEYS[type] ?? [];
|
|
136
|
+
const own = !wording || event === undefined || wording.ownEvents.includes(event);
|
|
137
|
+
const all = config['eventOverrides'];
|
|
138
|
+
const overrides = event && all && typeof all === 'object' && Object.hasOwn(all, event) ? all[event] : undefined;
|
|
139
|
+
const fields = overrides && typeof overrides === 'object' ? overrides : {};
|
|
140
|
+
const defaults = own ? {} : wording.defaults?.[event]?.[type] ?? {};
|
|
141
|
+
if (own && !overrides)
|
|
142
|
+
return config;
|
|
143
|
+
const merged = { ...config };
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
if (!own)
|
|
146
|
+
delete merged[key];
|
|
147
|
+
const text = fields[key] !== undefined && fields[key] !== '' ? fields[key] : defaults[key];
|
|
148
|
+
if (typeof text === 'string' && text !== '')
|
|
149
|
+
merged[key] = text;
|
|
150
|
+
}
|
|
151
|
+
// A request without a body cannot carry one, and the form drops it too.
|
|
152
|
+
if (type === 'webhook' && /^(GET|HEAD)$/i.test(String(merged['method'] ?? 'POST')))
|
|
153
|
+
delete merged['body'];
|
|
154
|
+
return merged;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=channelInput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channelInput.js","sourceRoot":"","sources":["../src/channelInput.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAEtD,MAAM,CAAC,MAAM,aAAa,GAAuC,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,CAAA;AAIlJ,SAAS,QAAQ,CAAC,MAAe;IAC/B,OAAO,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAgB,CAAA;AAC5E,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;AAEpG;;;GAGG;AACH,SAAS,eAAe,CAAC,CAAc,EAAE,GAAW,EAAE,KAAa,EAAE,OAAsB;IACzF,IAAI,CAAC,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,+BAA+B,KAAK,EAAE,CAAA;IAC3K,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;IACpB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,OAAO,CAAA;IACzE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,OAAO,KAAK,eAAe,CAAA;IACjE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,OAAO,CAAA;IACjC,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB,KAAK,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAA;AACzF,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,MAAe;IACjD,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC1B,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,0BAA0B,CAAA;IAC3D,OAAO,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B,CAAC,CAAA;AAClF,CAAC;AAED,MAAM,UAAU,GAAG,uBAAuB,CAAA;AAE1C,2FAA2F;AAC3F,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC1B,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,4DAA4D,CAAA;IACnI,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7B,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAC7D,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,yBAAyB,CAAA;QAChE,IAAI,GAAQ,CAAA;QACZ,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,+BAA+B,CAAA;QACxC,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,gDAAgD,CAAA;QAC5F,0HAA0H;QAC1H,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI;YAAE,OAAO,wDAAwD,CAAA;IAC7H,CAAC;IACD,MAAM,OAAO,GAAG,eAAe,CAAC,CAAC,EAAE,aAAa,EAAE,cAAc,EAAE,IAAI,CAAC,CAAA;IACvE,IAAI,OAAO;QAAE,OAAO,OAAO,CAAA;IAC3B,+FAA+F;IAC/F,OAAO,OAAO,CAAC,CAAC,aAAa,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,sEAAsE,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1M,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,mBAAmB,CAAC,MAAe;IACjD,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC1B,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,2BAA2B,CAAA;IAC7D,OAAO,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,mBAAmB,EAAE,qCAAqC,CAAC,CAAA;AACnG,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,MAAe;IAC9D,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC1D,IAAI,gBAAgB;QAAE,OAAO,gBAAgB,CAAA;IAC7C,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAA;IAC3D,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,eAAe,CAAC,MAAM,CAAC,CAAA;IACnD,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAA;IAC3D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,sBAAsB,CAAC,GAAG,CAAC;YAAE,OAAO,qDAAqD,CAAA;IAC1H,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAuD;IAC/E,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,CAAC,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,SAAS,CAAC;IACpB,KAAK,EAAE,CAAC,SAAS,CAAC;IAClB,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,MAAM,CAAC;IAClB,IAAI,EAAE,CAAC,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,iBAAiB,GAAG,MAAM,CAAA;AAChC,MAAM,mBAAmB,GAAG,CAAC,CAAA;AAC7B,MAAM,SAAS,GAAG,gBAAgB,CAAA;AAElC,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,MAAe;IAC/D,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,gBAAgB,CAAC,CAAA;IACpD,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACxC,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QAAE,OAAO,kCAAkC,CAAA;IACtH,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACxC,IAAI,MAAM,CAAC,MAAM,GAAG,mBAAmB;QAAE,OAAO,mCAAmC,mBAAmB,SAAS,CAAA;IAC/G,MAAM,OAAO,GAAG,aAAa,CAAC,IAA+B,CAAC,IAAI,EAAE,CAAA;IACpE,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACrC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,WAAW;YAAE,OAAO,6CAA6C,KAAK,GAAG,CAAA;QAC9I,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,kBAAkB,KAAK,oBAAoB,CAAA;QACtH,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,kBAAkB,KAAK,IAAI,KAAK,gCAAgC,IAAI,aAAa,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAA;YAC3I,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB;gBAAE,OAAO,kBAAkB,KAAK,IAAI,KAAK,0BAA0B,iBAAiB,aAAa,CAAA;QACpK,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAA+B,EAAE,KAAyB,EAAE,OAAsB;IAC7H,MAAM,IAAI,GAAG,aAAa,CAAC,IAA+B,CAAC,IAAI,EAAE,CAAA;IACjE,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAChF,MAAM,GAAG,GAAG,MAAM,CAAC,gBAAgB,CAAwD,CAAA;IAC3F,MAAM,SAAS,GAAG,KAAK,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/G,MAAM,MAAM,GAAG,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1E,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAM,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IACpE,IAAI,GAAG,IAAI,CAAC,SAAS;QAAE,OAAO,MAAM,CAAA;IAEpC,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAA;IAC5B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG;YAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QAC1F,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;IACjE,CAAC;IACD,wEAAwE;IACxE,IAAI,IAAI,KAAK,SAAS,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAA;IACzG,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SecretField } from '@better-forge/vault';
|
|
2
|
+
export declare const CHANNEL_SECRETS: Readonly<Record<string, readonly SecretField[]>>;
|
|
3
|
+
/**
|
|
4
|
+
* Where a channel's secret ends up. Keeping a stored secret while this changes would let whoever edits the channel
|
|
5
|
+
* send it to a host of their choosing, so the secret must be entered again.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CHANNEL_DESTINATIONS: readonly (readonly string[])[];
|
|
8
|
+
//# sourceMappingURL=channelSecrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channelSecrets.d.ts","sourceRoot":"","sources":["../src/channelSecrets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAKtD,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC,CAQ5E,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAA6B,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Which parts of a channel's configuration are secrets, by channel type. */
|
|
2
|
+
const WEBHOOK_URL_SECRET = [{ path: ['webhookUrl'] }];
|
|
3
|
+
export const CHANNEL_SECRETS = {
|
|
4
|
+
webhook: [{ mapAt: ['headers'] }],
|
|
5
|
+
slack: WEBHOOK_URL_SECRET,
|
|
6
|
+
discord: WEBHOOK_URL_SECRET,
|
|
7
|
+
teams: WEBHOOK_URL_SECRET,
|
|
8
|
+
telegram: [{ path: ['botToken'], tail: true }],
|
|
9
|
+
ntfy: [{ path: ['accessToken'], tail: true }],
|
|
10
|
+
pushover: [{ path: ['appToken'], tail: true }],
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Where a channel's secret ends up. Keeping a stored secret while this changes would let whoever edits the channel
|
|
14
|
+
* send it to a host of their choosing, so the secret must be entered again.
|
|
15
|
+
*/
|
|
16
|
+
export const CHANNEL_DESTINATIONS = [['url'], ['serverUrl']];
|
|
17
|
+
//# sourceMappingURL=channelSecrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channelSecrets.js","sourceRoot":"","sources":["../src/channelSecrets.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,MAAM,kBAAkB,GAA2B,CAAC,EAAE,IAAI,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;AAE7E,MAAM,CAAC,MAAM,eAAe,GAAqD;IAC/E,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;IACjC,KAAK,EAAE,kBAAkB;IACzB,OAAO,EAAE,kBAAkB;IAC3B,KAAK,EAAE,kBAAkB;IACzB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC9C,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7C,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;CAC/C,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAmC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAA"}
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { VaultRef } from '@better-forge/contracts';
|
|
2
|
+
import type { SqliteDatabase } from '@better-forge/db';
|
|
3
|
+
import type { NotifyTables } from './tables.js';
|
|
4
|
+
export type ChannelRow = NotifyTables['notificationChannels']['$inferSelect'];
|
|
5
|
+
export type DeliveryRow = NotifyTables['notificationDeliveries']['$inferSelect'];
|
|
6
|
+
/** What a delivery is about. A delivery without a source (a test, a digest) has none. */
|
|
7
|
+
export interface DeliveryDetails {
|
|
8
|
+
sourceId: number | null;
|
|
9
|
+
sourceName: string;
|
|
10
|
+
eventType: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NotifyEngineOptions {
|
|
13
|
+
db: SqliteDatabase;
|
|
14
|
+
tables: NotifyTables;
|
|
15
|
+
/** Stored name of the grouping threshold in a channel's alert policy; see `createAlertPolicy`. */
|
|
16
|
+
minSourcesKey: string;
|
|
17
|
+
/** The event types the application raises that alert hygiene treats specially. */
|
|
18
|
+
events: {
|
|
19
|
+
/** Rate-capped per source; the only event a quiet-hours or throttle rule can drop. */
|
|
20
|
+
alert: string;
|
|
21
|
+
/** Closes an alert. Together with `alert` it tells whether a source still has an alert open. */
|
|
22
|
+
recovery: string;
|
|
23
|
+
/** Never folded into a digest. */
|
|
24
|
+
ungrouped: string;
|
|
25
|
+
};
|
|
26
|
+
/** Sends one delivery's variables through the channel. Throws on failure. */
|
|
27
|
+
deliver: (channel: ChannelRow, vars: Record<string, string>) => Promise<void>;
|
|
28
|
+
/** The notification that stands in for several single-source events: its variables and the name it is recorded under. */
|
|
29
|
+
digest: (rows: DeliveryRow[], now: number) => {
|
|
30
|
+
vars: Record<string, string>;
|
|
31
|
+
sourceName: string;
|
|
32
|
+
};
|
|
33
|
+
/** Resolves a vault reference to its secret fields (used for SMTP credentials). */
|
|
34
|
+
resolveSecret: (ref: VaultRef) => Promise<Record<string, string>>;
|
|
35
|
+
}
|
|
36
|
+
export interface SmtpMessage {
|
|
37
|
+
to: string;
|
|
38
|
+
subject: string;
|
|
39
|
+
text: string;
|
|
40
|
+
/** Sent alongside `text` as multipart/alternative; clients that cannot render HTML show the text. */
|
|
41
|
+
html?: string;
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The delivery engine: records every notification, applies the channel's alert hygiene, sends with retries, folds
|
|
46
|
+
* bursts into digests and sends SMTP mail. Locks and in-flight sets live in the returned closure, so two engines
|
|
47
|
+
* never share state.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createNotifyEngine(options: NotifyEngineOptions): {
|
|
50
|
+
plan: (channel: ChannelRow, vars: Record<string, string>, details: DeliveryDetails) => Promise<number | null>;
|
|
51
|
+
dispatch: (channel: ChannelRow, vars: Record<string, string>, details: DeliveryDetails) => Promise<void>;
|
|
52
|
+
sendDirect: (channel: ChannelRow, vars: Record<string, string>, details: DeliveryDetails) => Promise<{
|
|
53
|
+
ok: true;
|
|
54
|
+
} | {
|
|
55
|
+
ok: false;
|
|
56
|
+
error: string;
|
|
57
|
+
}>;
|
|
58
|
+
attemptDelivery: (deliveryId: number, now?: number) => Promise<void>;
|
|
59
|
+
flushGroups: (now?: number) => Promise<number>;
|
|
60
|
+
processDue: (now?: number) => Promise<number>;
|
|
61
|
+
retryDelivery: (deliveryId: number) => Promise<boolean>;
|
|
62
|
+
purgeOldDeliveries: (now?: number) => Promise<void>;
|
|
63
|
+
hasOpenAlert: (sourceId: number) => Promise<boolean>;
|
|
64
|
+
isSmtpConfigured: () => Promise<boolean>;
|
|
65
|
+
sendSmtpMail: (message: SmtpMessage) => Promise<void>;
|
|
66
|
+
};
|
|
67
|
+
export type NotifyEngine = ReturnType<typeof createNotifyEngine>;
|
|
68
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiC,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG/C,MAAM,MAAM,UAAU,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC,cAAc,CAAC,CAAA;AAC7E,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,wBAAwB,CAAC,CAAC,cAAc,CAAC,CAAA;AAEhF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,cAAc,CAAA;IAClB,MAAM,EAAE,YAAY,CAAA;IACpB,kGAAkG;IAClG,aAAa,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,MAAM,EAAE;QACN,sFAAsF;QACtF,KAAK,EAAE,MAAM,CAAA;QACb,gGAAgG;QAChG,QAAQ,EAAE,MAAM,CAAA;QAChB,kCAAkC;QAClC,SAAS,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,6EAA6E;IAC7E,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E,yHAAyH;IACzH,MAAM,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IAClG,mFAAmF;IACnF,aAAa,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAClE;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,qGAAqG;IACrG,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACjC;AAiBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB;oBA4QhC,UAAU,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,eAAe,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;wBAgBvF,UAAU,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,eAAe,KAAG,OAAO,CAAC,IAAI,CAAC;0BAOxG,UAAU,QACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WACnB,eAAe,KACvB,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;kCA/PZ,MAAM,mBAAqB,OAAO,CAAC,IAAI,CAAC;mCAoI3C,OAAO,CAAC,MAAM,CAAC;kCAqDV,OAAO,CAAC,MAAM,CAAC;gCASnB,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;0CAYb,OAAO,CAAC,IAAI,CAAC;6BAK5B,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;4BAqD5B,OAAO,CAAC,OAAO,CAAC;4BAMd,WAAW,KAAG,OAAO,CAAC,IAAI,CAAC;EA2CjE;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA"}
|