@things-factory/notification 10.1.218 → 10.1.223
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/bootstrap.js +8 -3
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +1 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/store-once.d.ts +14 -0
- package/dist-client/store-once.js +32 -0
- package/dist-client/store-once.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/notice-channel-setting-let.d.ts +32 -0
- package/dist-client/viewparts/notice-channel-setting-let.js +175 -0
- package/dist-client/viewparts/notice-channel-setting-let.js.map +1 -0
- package/dist-server/controllers/index.d.ts +1 -0
- package/dist-server/controllers/index.js +1 -0
- package/dist-server/controllers/index.js.map +1 -1
- package/dist-server/controllers/notice/deliver-notice.d.ts +78 -0
- package/dist-server/controllers/notice/deliver-notice.js +101 -0
- package/dist-server/controllers/notice/deliver-notice.js.map +1 -0
- package/dist-server/controllers/notice/index.d.ts +4 -0
- package/dist-server/controllers/notice/index.js +8 -0
- package/dist-server/controllers/notice/index.js.map +1 -0
- package/dist-server/controllers/notice/notice-channels.d.ts +67 -0
- package/dist-server/controllers/notice/notice-channels.js +62 -0
- package/dist-server/controllers/notice/notice-channels.js.map +1 -0
- package/dist-server/controllers/notice/notice-email.d.ts +28 -0
- package/dist-server/controllers/notice/notice-email.js +125 -0
- package/dist-server/controllers/notice/notice-email.js.map +1 -0
- package/dist-server/controllers/notice/send-notice.d.ts +16 -0
- package/dist-server/controllers/notice/send-notice.js +72 -0
- package/dist-server/controllers/notice/send-notice.js.map +1 -0
- package/dist-server/middlewares/index.d.ts +1 -0
- package/dist-server/middlewares/index.js +3 -0
- package/dist-server/middlewares/index.js.map +1 -1
- package/dist-server/middlewares/notification-middleware.d.ts +19 -0
- package/dist-server/middlewares/notification-middleware.js +9 -3
- package/dist-server/middlewares/notification-middleware.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/service/index.js +3 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/notice-channel/index.d.ts +2 -0
- package/dist-server/service/notice-channel/index.js +6 -0
- package/dist-server/service/notice-channel/index.js.map +1 -0
- package/dist-server/service/notice-channel/notice-channel-resolver.d.ts +23 -0
- package/dist-server/service/notice-channel/notice-channel-resolver.js +118 -0
- package/dist-server/service/notice-channel/notice-channel-resolver.js.map +1 -0
- package/dist-server/service/notice-channel/notice-channel-rules.d.ts +25 -0
- package/dist-server/service/notice-channel/notice-channel-rules.js +60 -0
- package/dist-server/service/notice-channel/notice-channel-rules.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -7
- package/tests/notice-channel-setting.test.ts +107 -0
- package/tests/notice-delivery.test.ts +328 -0
- package/tests/open-doors.test.ts +5 -2
- package/tests/store-once.test.ts +88 -0
- package/translations/en.json +10 -1
- package/translations/es.json +10 -1
- package/translations/ja.json +10 -1
- package/translations/ko.json +10 -1
- package/translations/ms.json +10 -1
- package/translations/zh.json +10 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.escapeHtml = escapeHtml;
|
|
4
|
+
exports.safeNoticeHref = safeNoticeHref;
|
|
5
|
+
exports.renderNoticeMarkdown = renderNoticeMarkdown;
|
|
6
|
+
exports.noticeEmailForm = noticeEmailForm;
|
|
7
|
+
const marked_1 = require("marked");
|
|
8
|
+
/** Every value goes into the HTML through this - a subject like `<b>` or `A&B` stays text. */
|
|
9
|
+
function escapeHtml(value) {
|
|
10
|
+
return String(value ?? '')
|
|
11
|
+
.replace(/&/g, '&')
|
|
12
|
+
.replace(/</g, '<')
|
|
13
|
+
.replace(/>/g, '>')
|
|
14
|
+
.replace(/"/g, '"')
|
|
15
|
+
.replace(/'/g, ''');
|
|
16
|
+
}
|
|
17
|
+
/** The schemes a link in a notice may take; anything else is shown as its text. */
|
|
18
|
+
const SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:']);
|
|
19
|
+
const NO_BASE = 'https://no-base.invalid/';
|
|
20
|
+
/**
|
|
21
|
+
* Where a link in a notice may go: http(s) and mailto as written, and an address of the app (no scheme) under `base`,
|
|
22
|
+
* the domain's own address. With no base an app address has nowhere to go and is dropped, as is every other scheme -
|
|
23
|
+
* `javascript:` in any spelling the URL parser reads as one.
|
|
24
|
+
*/
|
|
25
|
+
function safeNoticeHref(href, base) {
|
|
26
|
+
const raw = String(href ?? '').trim();
|
|
27
|
+
if (!raw)
|
|
28
|
+
return undefined;
|
|
29
|
+
let url;
|
|
30
|
+
try {
|
|
31
|
+
url = new URL(raw, base || NO_BASE);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
if (!SAFE_PROTOCOLS.has(url.protocol))
|
|
37
|
+
return undefined;
|
|
38
|
+
if (url.origin === new URL(NO_BASE).origin)
|
|
39
|
+
return undefined;
|
|
40
|
+
return url.href;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Markdown as mail HTML, rendered here on the server. Raw HTML in the text is shown as text, never as markup; a link or
|
|
44
|
+
* an image becomes a link only to an address `safeNoticeHref` allows, and an image is not loaded - it is shown as a
|
|
45
|
+
* link with its alt text, so a mail does not fetch from wherever its writer pointed.
|
|
46
|
+
*/
|
|
47
|
+
function renderNoticeMarkdown(markdown, base) {
|
|
48
|
+
const marked = new marked_1.Marked({
|
|
49
|
+
gfm: true,
|
|
50
|
+
breaks: true,
|
|
51
|
+
async: false,
|
|
52
|
+
renderer: {
|
|
53
|
+
html({ text }) {
|
|
54
|
+
return escapeHtml(text);
|
|
55
|
+
},
|
|
56
|
+
link({ href, title, tokens }) {
|
|
57
|
+
const label = this.parser.parseInline(tokens);
|
|
58
|
+
const safe = safeNoticeHref(href, base);
|
|
59
|
+
return safe ? `<a href="${escapeHtml(safe)}"${title ? ` title="${escapeHtml(title)}"` : ''}>${label}</a>` : label;
|
|
60
|
+
},
|
|
61
|
+
image({ href, text }) {
|
|
62
|
+
const safe = safeNoticeHref(href, base);
|
|
63
|
+
return safe ? `<a href="${escapeHtml(safe)}">${escapeHtml(text || safe)}</a>` : escapeHtml(text);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return marked.parse(String(markdown ?? ''));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The notice mail (ADR-0164) - the head, band, button and foot of the auth mails, so a person knows the sender.
|
|
71
|
+
* Without a link (a notice sent with no request to take the address from) the button gives way to the note.
|
|
72
|
+
*/
|
|
73
|
+
function noticeEmailForm({ lang, words, link, format = 'text', base }) {
|
|
74
|
+
const action = link
|
|
75
|
+
? `<a
|
|
76
|
+
href="${escapeHtml(link)}"
|
|
77
|
+
style="display:inline-block;margin:10px 5px 5px 0;border-radius:7px;background-color:#22a6a7;padding:7px 15px;color:#fff;font-size:18px;text-decoration:none;"
|
|
78
|
+
>${escapeHtml(words.action)}</a
|
|
79
|
+
>`
|
|
80
|
+
: `<span style="display:block;color:#555">${escapeHtml(words.note)}</span>`;
|
|
81
|
+
return `
|
|
82
|
+
<html lang="${escapeHtml(lang)}">
|
|
83
|
+
<head>
|
|
84
|
+
<meta charset="utf-8" />
|
|
85
|
+
<title>${escapeHtml(words.subject)}</title>
|
|
86
|
+
<meta name="author" content="hatiolab" />
|
|
87
|
+
<meta name="google" content="notranslate"/>
|
|
88
|
+
</head>
|
|
89
|
+
|
|
90
|
+
<body>
|
|
91
|
+
<div style="background-color:#f6f6f6">
|
|
92
|
+
<!--header begin-->
|
|
93
|
+
<div style="background-color:#fff;padding:0 10px;border-top: 2px solid #394e64;">
|
|
94
|
+
<img src="http://www.hatiolab.com/assets/img/logo-operato.png" style="max-height:50px" />
|
|
95
|
+
</div>
|
|
96
|
+
<!--header end-->
|
|
97
|
+
|
|
98
|
+
<!--title begin-->
|
|
99
|
+
<div style="background-color:#22a6a7;padding:12px 10px 10px 10px;min-height:50px;">
|
|
100
|
+
<span style="display:block;color:#fff;font-size:24px;font-weight:bold;margin-left:40px"
|
|
101
|
+
>${escapeHtml(words.subject)}</span
|
|
102
|
+
>
|
|
103
|
+
</div>
|
|
104
|
+
<!--title end-->
|
|
105
|
+
|
|
106
|
+
<!--body begin-->
|
|
107
|
+
${format === 'markdown'
|
|
108
|
+
? `<div style="padding:10px 20px;line-height:1.5;font-size:16px">${renderNoticeMarkdown(words.body, base)}</div>`
|
|
109
|
+
: `<p style="padding:10px 20px;line-height:1.5;font-size:16px;white-space:pre-line">${escapeHtml(words.body)}</p>`}
|
|
110
|
+
<p style="padding:0 20px 10px 20px;font-size:16px">
|
|
111
|
+
${action}
|
|
112
|
+
</p>
|
|
113
|
+
<!--body end-->
|
|
114
|
+
|
|
115
|
+
<!--footer begin-->
|
|
116
|
+
<div style="background-color:#3d5874;padding:7px 20px 5px 20px;font-size:12px;color:#efefef">
|
|
117
|
+
© Hatio, Lab. Inc. All rights reserved.
|
|
118
|
+
</div>
|
|
119
|
+
<!--footer end-->
|
|
120
|
+
</div>
|
|
121
|
+
</body>
|
|
122
|
+
</html>
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=notice-email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notice-email.js","sourceRoot":"","sources":["../../../server/controllers/notice/notice-email.ts"],"names":[],"mappings":"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","sourcesContent":["import { Marked } from 'marked'\n\nimport type { NoticeWords } from './notice-channels.js'\n\n/** Every value goes into the HTML through this - a subject like `<b>` or `A&B` stays text. */\nexport function escapeHtml(value: unknown): string {\n return String(value ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/** The schemes a link in a notice may take; anything else is shown as its text. */\nconst SAFE_PROTOCOLS = new Set(['http:', 'https:', 'mailto:'])\nconst NO_BASE = 'https://no-base.invalid/'\n\n/**\n * Where a link in a notice may go: http(s) and mailto as written, and an address of the app (no scheme) under `base`,\n * the domain's own address. With no base an app address has nowhere to go and is dropped, as is every other scheme -\n * `javascript:` in any spelling the URL parser reads as one.\n */\nexport function safeNoticeHref(href: string | null | undefined, base?: string): string | undefined {\n const raw = String(href ?? '').trim()\n if (!raw) return undefined\n\n let url: URL\n try {\n url = new URL(raw, base || NO_BASE)\n } catch {\n return undefined\n }\n\n if (!SAFE_PROTOCOLS.has(url.protocol)) return undefined\n if (url.origin === new URL(NO_BASE).origin) return undefined\n return url.href\n}\n\n/**\n * Markdown as mail HTML, rendered here on the server. Raw HTML in the text is shown as text, never as markup; a link or\n * an image becomes a link only to an address `safeNoticeHref` allows, and an image is not loaded - it is shown as a\n * link with its alt text, so a mail does not fetch from wherever its writer pointed.\n */\nexport function renderNoticeMarkdown(markdown: string, base?: string): string {\n const marked = new Marked({\n gfm: true,\n breaks: true,\n async: false,\n renderer: {\n html({ text }) {\n return escapeHtml(text)\n },\n link({ href, title, tokens }) {\n const label = this.parser.parseInline(tokens)\n const safe = safeNoticeHref(href, base)\n return safe ? `<a href=\"${escapeHtml(safe)}\"${title ? ` title=\"${escapeHtml(title)}\"` : ''}>${label}</a>` : label\n },\n image({ href, text }) {\n const safe = safeNoticeHref(href, base)\n return safe ? `<a href=\"${escapeHtml(safe)}\">${escapeHtml(text || safe)}</a>` : escapeHtml(text)\n }\n }\n })\n\n return marked.parse(String(markdown ?? '')) as string\n}\n\n/**\n * The notice mail (ADR-0164) - the head, band, button and foot of the auth mails, so a person knows the sender.\n * Without a link (a notice sent with no request to take the address from) the button gives way to the note.\n */\nexport function noticeEmailForm({\n lang,\n words,\n link,\n format = 'text',\n base\n}: {\n lang: string\n words: NoticeWords\n link?: string\n /** How the body is written - an announcement's is Markdown. */\n format?: 'text' | 'markdown'\n /** The domain's address, under which an app address in a Markdown body is resolved. */\n base?: string\n}) {\n const action = link\n ? `<a\n href=\"${escapeHtml(link)}\"\n style=\"display:inline-block;margin:10px 5px 5px 0;border-radius:7px;background-color:#22a6a7;padding:7px 15px;color:#fff;font-size:18px;text-decoration:none;\"\n >${escapeHtml(words.action)}</a\n >`\n : `<span style=\"display:block;color:#555\">${escapeHtml(words.note)}</span>`\n\n return `\n <html lang=\"${escapeHtml(lang)}\">\n <head>\n <meta charset=\"utf-8\" />\n <title>${escapeHtml(words.subject)}</title>\n <meta name=\"author\" content=\"hatiolab\" />\n <meta name=\"google\" content=\"notranslate\"/>\n </head>\n\n <body>\n <div style=\"background-color:#f6f6f6\">\n <!--header begin-->\n <div style=\"background-color:#fff;padding:0 10px;border-top: 2px solid #394e64;\">\n <img src=\"http://www.hatiolab.com/assets/img/logo-operato.png\" style=\"max-height:50px\" />\n </div>\n <!--header end-->\n\n <!--title begin-->\n <div style=\"background-color:#22a6a7;padding:12px 10px 10px 10px;min-height:50px;\">\n <span style=\"display:block;color:#fff;font-size:24px;font-weight:bold;margin-left:40px\"\n >${escapeHtml(words.subject)}</span\n >\n </div>\n <!--title end-->\n\n <!--body begin-->\n ${\n format === 'markdown'\n ? `<div style=\"padding:10px 20px;line-height:1.5;font-size:16px\">${renderNoticeMarkdown(words.body, base)}</div>`\n : `<p style=\"padding:10px 20px;line-height:1.5;font-size:16px;white-space:pre-line\">${escapeHtml(words.body)}</p>`\n }\n <p style=\"padding:0 20px 10px 20px;font-size:16px\">\n ${action}\n </p>\n <!--body end-->\n\n <!--footer begin-->\n <div style=\"background-color:#3d5874;padding:7px 20px 5px 20px;font-size:12px;color:#efefef\">\n © Hatio, Lab. Inc. All rights reserved.\n </div>\n <!--footer end-->\n </div>\n </body>\n </html>\n `\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Notice, type NoticeChannelFunctions, type NoticeResult } from './deliver-notice.js';
|
|
2
|
+
/** The real channels of a domain. */
|
|
3
|
+
export declare function noticeChannelsOf(domain: any, push?: (payload: any) => unknown): NoticeChannelFunctions;
|
|
4
|
+
/**
|
|
5
|
+
* Sends now and answers what went out - for work with no request (a scheduled publication), or a sender that records
|
|
6
|
+
* the result. Never throws; a notice that could not be sent at all answers undefined.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sendNotice(domain: any, notice: Notice, push?: (payload: any) => unknown): Promise<NoticeResult | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Sends once the request's transaction has committed, and does not make the request wait for it - a slow mail server
|
|
11
|
+
* does not hold the button that caused the notice. Rolled back, nothing goes. With no transaction list, it goes now.
|
|
12
|
+
* `sent` hears what went out, for a sender that records it (in its own transaction - the request's is over).
|
|
13
|
+
*/
|
|
14
|
+
export declare function sendNoticeWhenCommitted(context: ResolverContext, notice: Notice, sent?: (result: NoticeResult | undefined) => unknown | Promise<unknown>): void;
|
|
15
|
+
/** A page of the request's domain as a full address - where this request came in. None with no request. */
|
|
16
|
+
export declare function noticeLinkOf(context: ResolverContext, address: string): string | undefined;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noticeChannelsOf = noticeChannelsOf;
|
|
4
|
+
exports.sendNotice = sendNotice;
|
|
5
|
+
exports.sendNoticeWhenCommitted = sendNoticeWhenCommitted;
|
|
6
|
+
exports.noticeLinkOf = noticeLinkOf;
|
|
7
|
+
/*
|
|
8
|
+
* The one way a module tells people of something (ADR-0164): in the app and by web push always, by mail and Teams
|
|
9
|
+
* when the domain turned them on. The module settles who and what; this sends it.
|
|
10
|
+
*/
|
|
11
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
12
|
+
const email_base_1 = require("@things-factory/email-base");
|
|
13
|
+
const env_1 = require("@things-factory/env");
|
|
14
|
+
const shell_1 = require("@things-factory/shell");
|
|
15
|
+
const notification_middleware_js_1 = require("../../middlewares/notification-middleware.js");
|
|
16
|
+
const deliver_notice_js_1 = require("./deliver-notice.js");
|
|
17
|
+
const notice_channels_js_1 = require("./notice-channels.js");
|
|
18
|
+
const report = (message, error) => env_1.logger.error(`[notification] ${message}`, error);
|
|
19
|
+
/** The real channels of a domain. */
|
|
20
|
+
function noticeChannelsOf(domain, push) {
|
|
21
|
+
return {
|
|
22
|
+
attributes: () => (0, shell_1.loadDomainAttributes)(domain, notice_channels_js_1.NOTICE_ATTRIBUTE_PREFIX),
|
|
23
|
+
push: push ?? (0, notification_middleware_js_1.notifierOf)(domain),
|
|
24
|
+
mail: mail => (0, email_base_1.sendEmail)(mail),
|
|
25
|
+
teams: async (url, body) => {
|
|
26
|
+
const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
27
|
+
return { ok: response.ok, status: response.status };
|
|
28
|
+
},
|
|
29
|
+
report
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sends now and answers what went out - for work with no request (a scheduled publication), or a sender that records
|
|
34
|
+
* the result. Never throws; a notice that could not be sent at all answers undefined.
|
|
35
|
+
*/
|
|
36
|
+
async function sendNotice(domain, notice, push) {
|
|
37
|
+
try {
|
|
38
|
+
return await (0, deliver_notice_js_1.deliverNotice)({ defaultLanguage: env_1.config.get('i18n/defaultLanguage', 'en'), ...notice }, noticeChannelsOf(domain, push));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
report(`${notice.label}: sending failed`, error);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sends once the request's transaction has committed, and does not make the request wait for it - a slow mail server
|
|
47
|
+
* does not hold the button that caused the notice. Rolled back, nothing goes. With no transaction list, it goes now.
|
|
48
|
+
* `sent` hears what went out, for a sender that records it (in its own transaction - the request's is over).
|
|
49
|
+
*/
|
|
50
|
+
function sendNoticeWhenCommitted(context, notice, sent) {
|
|
51
|
+
const { domain } = context.state;
|
|
52
|
+
const push = context.state.notify;
|
|
53
|
+
const send = () => {
|
|
54
|
+
void sendNotice(domain, notice, push)
|
|
55
|
+
.then(result => sent?.(result))
|
|
56
|
+
.catch(error => report(`${notice.label}: recording what was sent failed`, error));
|
|
57
|
+
};
|
|
58
|
+
if (!(0, shell_1.whenCommitted)(context.state.afterCommit, notice.label, send))
|
|
59
|
+
send();
|
|
60
|
+
}
|
|
61
|
+
/** A page of the request's domain as a full address - where this request came in. None with no request. */
|
|
62
|
+
function noticeLinkOf(context, address) {
|
|
63
|
+
try {
|
|
64
|
+
if (!context?.request)
|
|
65
|
+
return undefined;
|
|
66
|
+
return `${(0, auth_base_1.buildAuthURL)('/', context)}${(0, shell_1.getContextPath)(context.state.domain)}${address}`;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=send-notice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send-notice.js","sourceRoot":"","sources":["../../../server/controllers/notice/send-notice.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The one way a module tells people of something (ADR-0164): in the app and by web push always, by mail and Teams\n * when the domain turned them on. The module settles who and what; this sends it.\n */\nimport { buildAuthURL } from '@things-factory/auth-base'\nimport { sendEmail } from '@things-factory/email-base'\nimport { config, logger } from '@things-factory/env'\nimport { getContextPath, loadDomainAttributes, whenCommitted } from '@things-factory/shell'\n\nimport { notifierOf } from '../../middlewares/notification-middleware.js'\nimport { deliverNotice, type Notice, type NoticeChannelFunctions, type NoticeResult } from './deliver-notice.js'\nimport { NOTICE_ATTRIBUTE_PREFIX } from './notice-channels.js'\n\nconst report = (message: string, error?: unknown) => logger.error(`[notification] ${message}`, error)\n\n/** The real channels of a domain. */\nexport function noticeChannelsOf(domain: any, push?: (payload: any) => unknown): NoticeChannelFunctions {\n return {\n attributes: () => loadDomainAttributes(domain, NOTICE_ATTRIBUTE_PREFIX),\n push: push ?? notifierOf(domain),\n mail: mail => sendEmail(mail) as Promise<unknown>,\n teams: async (url, body) => {\n const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })\n return { ok: response.ok, status: response.status }\n },\n report\n }\n}\n\n/**\n * Sends now and answers what went out - for work with no request (a scheduled publication), or a sender that records\n * the result. Never throws; a notice that could not be sent at all answers undefined.\n */\nexport async function sendNotice(domain: any, notice: Notice, push?: (payload: any) => unknown): Promise<NoticeResult | undefined> {\n try {\n return await deliverNotice({ defaultLanguage: config.get('i18n/defaultLanguage', 'en'), ...notice }, noticeChannelsOf(domain, push))\n } catch (error) {\n report(`${notice.label}: sending failed`, error)\n return undefined\n }\n}\n\n/**\n * Sends once the request's transaction has committed, and does not make the request wait for it - a slow mail server\n * does not hold the button that caused the notice. Rolled back, nothing goes. With no transaction list, it goes now.\n * `sent` hears what went out, for a sender that records it (in its own transaction - the request's is over).\n */\nexport function sendNoticeWhenCommitted(\n context: ResolverContext,\n notice: Notice,\n sent?: (result: NoticeResult | undefined) => unknown | Promise<unknown>\n): void {\n const { domain } = context.state\n const push = (context.state as any).notify\n const send = () => {\n void sendNotice(domain, notice, push)\n .then(result => sent?.(result))\n .catch(error => report(`${notice.label}: recording what was sent failed`, error))\n }\n\n if (!whenCommitted((context.state as any).afterCommit, notice.label, send)) send()\n}\n\n/** A page of the request's domain as a full address - where this request came in. None with no request. */\nexport function noticeLinkOf(context: ResolverContext, address: string): string | undefined {\n try {\n if (!(context as any)?.request) return undefined\n return `${buildAuthURL('/', context)}${getContextPath(context.state.domain)}${address}`\n } catch {\n return undefined\n }\n}\n"]}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.notifierOf = void 0;
|
|
3
4
|
exports.initMiddlewares = initMiddlewares;
|
|
4
5
|
const notification_middleware_1 = require("./notification-middleware");
|
|
6
|
+
var notification_middleware_2 = require("./notification-middleware");
|
|
7
|
+
Object.defineProperty(exports, "notifierOf", { enumerable: true, get: function () { return notification_middleware_2.notifierOf; } });
|
|
5
8
|
function initMiddlewares(app) {
|
|
6
9
|
app.use(notification_middleware_1.notificationMiddleware);
|
|
7
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/middlewares/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/middlewares/index.ts"],"names":[],"mappings":";;;AAMA,0CAEC;AAND,uEAAkE;AAElE,qEAAsD;AAA7C,qHAAA,UAAU,OAAA;AAEnB,SAAgB,eAAe,CAAC,GAAQ;IACtC,GAAG,CAAC,GAAG,CAAC,gDAAsB,CAAC,CAAA;AACjC,CAAC","sourcesContent":["import Koa from 'koa'\n\nimport { notificationMiddleware } from './notification-middleware'\n\nexport { notifierOf } from './notification-middleware'\n\nexport function initMiddlewares(app: Koa) {\n app.use(notificationMiddleware)\n}\n"]}
|
|
@@ -1 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tells the people of `domain` - by web push (`background`) or live to the screens they have open (`foreground`).
|
|
3
|
+
* The middleware gives each request one as `context.state.notify`; work that runs with no request (a scheduled
|
|
4
|
+
* announcement) takes one for its domain.
|
|
5
|
+
*/
|
|
6
|
+
export declare function notifierOf(domain: any): ({ receivers, tokens, topic, type, subject, title, body, image, url, actions, timestamp, mode }: {
|
|
7
|
+
receivers: any;
|
|
8
|
+
tokens: any;
|
|
9
|
+
topic: any;
|
|
10
|
+
type: any;
|
|
11
|
+
subject?: string;
|
|
12
|
+
title: any;
|
|
13
|
+
body: any;
|
|
14
|
+
image: any;
|
|
15
|
+
url: any;
|
|
16
|
+
actions: any;
|
|
17
|
+
timestamp?: number;
|
|
18
|
+
mode?: string;
|
|
19
|
+
}) => Promise<void>;
|
|
1
20
|
export declare function notificationMiddleware(context: any, next: any): Promise<void>;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.notifierOf = notifierOf;
|
|
3
4
|
exports.notificationMiddleware = notificationMiddleware;
|
|
4
5
|
const shell_1 = require("@things-factory/shell");
|
|
5
6
|
const env_1 = require("@things-factory/env");
|
|
6
7
|
const fcm_1 = require("../controllers/fcm");
|
|
7
8
|
const debug = require('debug')('things-factory:notification:notification-middleware');
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Tells the people of `domain` - by web push (`background`) or live to the screens they have open (`foreground`).
|
|
11
|
+
* The middleware gives each request one as `context.state.notify`; work that runs with no request (a scheduled
|
|
12
|
+
* announcement) takes one for its domain.
|
|
13
|
+
*/
|
|
14
|
+
function notifierOf(domain) {
|
|
9
15
|
return async function ({ receivers, tokens, topic, type, subject = 'info', title, body, image, url, actions, timestamp = Date.now(), mode = 'background' }) {
|
|
10
|
-
const { domain, user } = context.state;
|
|
11
16
|
try {
|
|
12
17
|
if (mode === 'background') {
|
|
13
18
|
/* for send webpush notification message */
|
|
@@ -51,7 +56,8 @@ function notifier(context) {
|
|
|
51
56
|
}
|
|
52
57
|
async function notificationMiddleware(context, next) {
|
|
53
58
|
if (!context.state.notify) {
|
|
54
|
-
|
|
59
|
+
/* the domain as it stands when the notice is sent - a later middleware may be the one that sets it */
|
|
60
|
+
context.state.notify = payload => notifierOf(context.state.domain)(payload);
|
|
55
61
|
}
|
|
56
62
|
await next();
|
|
57
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-middleware.js","sourceRoot":"","sources":["../../server/middlewares/notification-middleware.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"notification-middleware.js","sourceRoot":"","sources":["../../server/middlewares/notification-middleware.ts"],"names":[],"mappings":";;AAYA,gCAsDC;AAED,wDAOC;AA3ED,iDAA8C;AAC9C,6CAA4C;AAE5C,4CAA2C;AAE3C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,qDAAqD,CAAC,CAAA;AAErF;;;;GAIG;AACH,SAAgB,UAAU,CAAC,MAAM;IAC/B,OAAO,KAAK,WAAW,EACrB,SAAS,EACT,MAAM,EACN,KAAK,EACL,IAAI,EACJ,OAAO,GAAG,MAAM,EAChB,KAAK,EACL,IAAI,EACJ,KAAK,EACL,GAAG,EACH,OAAO,EACP,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EACtB,IAAI,GAAG,YAAY,EACpB;QACC,IAAI,CAAC;YACH,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC1B,2CAA2C;gBAE3C,MAAM,IAAA,YAAM,EAAC;oBACX,SAAS;oBACT,MAAM;oBACN,KAAK;oBACL,KAAK;oBACL,IAAI;oBACJ,IAAI,EAAE;wBACJ,IAAI;wBACJ,GAAG;wBACH,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC;qBAC7B;oBACD,KAAK;oBACL,OAAO;iBACR,CAAC,CAAA;YACJ,CAAC;iBAAM,CAAC;gBACN,0FAA0F;gBAC1F,2BAA2B;gBAC3B,MAAM,cAAM,CAAC,OAAO,CAAC,cAAc,EAAE;oBACnC,YAAY,EAAE;wBACZ,MAAM;wBACN,SAAS;wBACT,OAAO;wBACP,IAAI;wBACJ,KAAK;wBACL,IAAI;wBACJ,KAAK;wBACL,GAAG;wBACH,SAAS;qBACV;iBACF,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,YAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACnB,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAEM,KAAK,UAAU,sBAAsB,CAAC,OAAY,EAAE,IAAS;IAClE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAC1B,sGAAsG;QACtG,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAA;IAC7E,CAAC;IAED,MAAM,IAAI,EAAE,CAAA;AACd,CAAC","sourcesContent":["import { pubsub } from '@things-factory/shell'\nimport { logger } from '@things-factory/env'\n\nimport { notify } from '../controllers/fcm'\n\nconst debug = require('debug')('things-factory:notification:notification-middleware')\n\n/**\n * Tells the people of `domain` - by web push (`background`) or live to the screens they have open (`foreground`).\n * The middleware gives each request one as `context.state.notify`; work that runs with no request (a scheduled\n * announcement) takes one for its domain.\n */\nexport function notifierOf(domain) {\n return async function ({\n receivers,\n tokens,\n topic,\n type,\n subject = 'info',\n title,\n body,\n image,\n url,\n actions,\n timestamp = Date.now(),\n mode = 'background'\n }) {\n try {\n if (mode === 'background') {\n /* for send webpush notification message */\n\n await notify({\n receivers,\n tokens,\n topic,\n title,\n body,\n data: {\n type,\n url,\n timestamp: String(timestamp)\n },\n image,\n actions\n })\n } else {\n /* for send publish notification to clients - to the receivers only, when there are any */\n // TODO CONFIRM data format\n await pubsub.publish('notification', {\n notification: {\n domain,\n receivers,\n subject,\n type,\n title,\n body,\n image,\n url,\n timestamp\n }\n })\n }\n } catch (err) {\n logger.error(err)\n }\n }\n}\n\nexport async function notificationMiddleware(context: any, next: any) {\n if (!context.state.notify) {\n /* the domain as it stands when the notice is sent - a later middleware may be the one that sets it */\n context.state.notify = payload => notifierOf(context.state.domain)(payload)\n }\n\n await next()\n}\n"]}
|
|
@@ -6,7 +6,7 @@ export declare const schema: {
|
|
|
6
6
|
typeDefs: {
|
|
7
7
|
notificationDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
8
8
|
};
|
|
9
|
-
resolverClasses: (typeof import("./notification-rule/notification-rule-query").NotificationRuleQuery | typeof import("./notification-rule/notification-rule-mutation").NotificationRuleMutation | typeof import("./notification/notification-query").NotificationQuery | typeof import("./notification/notification-mutation").NotificationMutation | typeof import("./notification/notification-subscription").NotificationSubscription)[];
|
|
9
|
+
resolverClasses: (typeof import("./notification-rule/notification-rule-query").NotificationRuleQuery | typeof import("./notification-rule/notification-rule-mutation").NotificationRuleMutation | typeof import("./notification/notification-query").NotificationQuery | typeof import("./notification/notification-mutation").NotificationMutation | typeof import("./notification/notification-subscription").NotificationSubscription | typeof import("./notice-channel/notice-channel-resolver").NoticeChannelResolver)[];
|
|
10
10
|
directives: {
|
|
11
11
|
notification: (schema: import("graphql").GraphQLSchema) => import("graphql").GraphQLSchema;
|
|
12
12
|
};
|
|
@@ -8,6 +8,7 @@ tslib_1.__exportStar(require("./notification/notification"), exports);
|
|
|
8
8
|
/* IMPORT ENTITIES AND RESOLVERS */
|
|
9
9
|
const notification_rule_1 = require("./notification-rule");
|
|
10
10
|
const notification_1 = require("./notification");
|
|
11
|
+
const notice_channel_1 = require("./notice-channel");
|
|
11
12
|
exports.entities = [
|
|
12
13
|
/* ENTITIES */
|
|
13
14
|
...notification_rule_1.entities,
|
|
@@ -25,7 +26,8 @@ exports.schema = {
|
|
|
25
26
|
/* RESOLVER CLASSES */
|
|
26
27
|
...notification_rule_1.resolvers,
|
|
27
28
|
...notification_1.resolvers,
|
|
28
|
-
...notification_1.resolvers
|
|
29
|
+
...notification_1.resolvers,
|
|
30
|
+
...notice_channel_1.resolvers
|
|
29
31
|
],
|
|
30
32
|
directives: {
|
|
31
33
|
...notification_1.directives
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,gFAAqD;AACrD,sEAA2C;AAE3C,mCAAmC;AACnC,2DAI4B;AAC5B,iDAKuB;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,gFAAqD;AACrD,sEAA2C;AAE3C,mCAAmC;AACnC,2DAI4B;AAC5B,iDAKuB;AACvB,qDAAsE;AAEzD,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,4BAAwB;IAC3B,GAAG,uBAAoB;CACxB,CAAA;AAEY,QAAA,WAAW,GAAG;IACzB,iBAAiB;IACjB,GAAG,+BAA2B;CAC/B,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,QAAQ,EAAE;QACR,GAAG,uBAAoB;KACxB;IACD,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,6BAAyB;QAC5B,GAAG,wBAAqB;QACxB,GAAG,wBAAqB;QACxB,GAAG,0BAAsB;KAC1B;IACD,UAAU,EAAE;QACV,GAAG,yBAAsB;KAC1B;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './notification-rule/notification-rule'\nexport * from './notification/notification'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport {\n entities as NotificationRuleEntities,\n resolvers as NotificationRuleResolvers,\n subscribers as NotificationRuleSubscribers\n} from './notification-rule'\nimport {\n entities as NotificationEntities,\n typeDefs as NotificationTypeDefs,\n resolvers as NotificationResolvers,\n directives as NotificationDirectives\n} from './notification'\nimport { resolvers as NoticeChannelResolvers } from './notice-channel'\n\nexport const entities = [\n /* ENTITIES */\n ...NotificationRuleEntities,\n ...NotificationEntities\n]\n\nexport const subscribers = [\n /* SUBSCRIBERS */\n ...NotificationRuleSubscribers\n]\n\nexport const schema = {\n typeDefs: {\n ...NotificationTypeDefs\n },\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...NotificationRuleResolvers,\n ...NotificationResolvers,\n ...NotificationResolvers,\n ...NoticeChannelResolvers\n ],\n directives: {\n ...NotificationDirectives\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvers = void 0;
|
|
4
|
+
const notice_channel_resolver_js_1 = require("./notice-channel-resolver.js");
|
|
5
|
+
exports.resolvers = [notice_channel_resolver_js_1.NoticeChannelResolver];
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/notice-channel/index.ts"],"names":[],"mappings":";;;AAAA,6EAAoE;AAEvD,QAAA,SAAS,GAAG,CAAC,kDAAqB,CAAC,CAAA","sourcesContent":["import { NoticeChannelResolver } from './notice-channel-resolver.js'\n\nexport const resolvers = [NoticeChannelResolver]\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type NoticeChannelState } from './notice-channel-rules.js';
|
|
2
|
+
export declare class NoticeSecretState {
|
|
3
|
+
set: boolean;
|
|
4
|
+
host?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class NoticeChannelSetting {
|
|
7
|
+
email: boolean;
|
|
8
|
+
teamsChannel: NoticeSecretState;
|
|
9
|
+
}
|
|
10
|
+
export declare class NoticeChannelSettingPatch {
|
|
11
|
+
email?: boolean;
|
|
12
|
+
teamsChannelWebhook?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Writes the patch as the domain's own attributes. A cleared one is removed from this domain. */
|
|
15
|
+
export declare function writeNoticeChannels(domain: any, patch: any, manager: any): Promise<NoticeChannelState>;
|
|
16
|
+
/**
|
|
17
|
+
* The Settings screen's door to the notice channels. They are domain attributes (EnvVar), so the same privilege as the
|
|
18
|
+
* EnvVar screen opens them; what comes back never carries a webhook address.
|
|
19
|
+
*/
|
|
20
|
+
export declare class NoticeChannelResolver {
|
|
21
|
+
noticeChannelSetting(context: ResolverContext): Promise<NoticeChannelSetting>;
|
|
22
|
+
updateNoticeChannelSetting(patch: NoticeChannelSettingPatch, context: ResolverContext): Promise<NoticeChannelSetting>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoticeChannelResolver = exports.NoticeChannelSettingPatch = exports.NoticeChannelSetting = exports.NoticeSecretState = void 0;
|
|
4
|
+
exports.writeNoticeChannels = writeNoticeChannels;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const type_graphql_1 = require("type-graphql");
|
|
7
|
+
const typeorm_1 = require("typeorm");
|
|
8
|
+
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
const notice_channels_js_1 = require("../../controllers/notice/notice-channels.js");
|
|
10
|
+
const notice_channel_rules_js_1 = require("./notice-channel-rules.js");
|
|
11
|
+
let NoticeSecretState = class NoticeSecretState {
|
|
12
|
+
};
|
|
13
|
+
exports.NoticeSecretState = NoticeSecretState;
|
|
14
|
+
tslib_1.__decorate([
|
|
15
|
+
(0, type_graphql_1.Field)({ description: 'Whether it is set for this domain.' }),
|
|
16
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
17
|
+
], NoticeSecretState.prototype, "set", void 0);
|
|
18
|
+
tslib_1.__decorate([
|
|
19
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The host of the address, to tell which flow it is.' }),
|
|
20
|
+
tslib_1.__metadata("design:type", String)
|
|
21
|
+
], NoticeSecretState.prototype, "host", void 0);
|
|
22
|
+
exports.NoticeSecretState = NoticeSecretState = tslib_1.__decorate([
|
|
23
|
+
(0, type_graphql_1.ObjectType)({ description: 'Whether a secret is set, and the host it points at - never the secret itself.' })
|
|
24
|
+
], NoticeSecretState);
|
|
25
|
+
let NoticeChannelSetting = class NoticeChannelSetting {
|
|
26
|
+
};
|
|
27
|
+
exports.NoticeChannelSetting = NoticeChannelSetting;
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, type_graphql_1.Field)({ description: 'Whether notices also go by mail.' }),
|
|
30
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
31
|
+
], NoticeChannelSetting.prototype, "email", void 0);
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, type_graphql_1.Field)(type => NoticeSecretState, { description: 'The Teams channel webhook - where a notice to everyone in the domain is posted.' }),
|
|
34
|
+
tslib_1.__metadata("design:type", NoticeSecretState)
|
|
35
|
+
], NoticeChannelSetting.prototype, "teamsChannel", void 0);
|
|
36
|
+
exports.NoticeChannelSetting = NoticeChannelSetting = tslib_1.__decorate([
|
|
37
|
+
(0, type_graphql_1.ObjectType)({ description: 'The channels notices take beyond the app and web push in this domain (ADR-0164).' })
|
|
38
|
+
], NoticeChannelSetting);
|
|
39
|
+
let NoticeChannelSettingPatch = class NoticeChannelSettingPatch {
|
|
40
|
+
};
|
|
41
|
+
exports.NoticeChannelSettingPatch = NoticeChannelSettingPatch;
|
|
42
|
+
tslib_1.__decorate([
|
|
43
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Whether notices also go by mail.' }),
|
|
44
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
45
|
+
], NoticeChannelSettingPatch.prototype, "email", void 0);
|
|
46
|
+
tslib_1.__decorate([
|
|
47
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The Teams channel webhook address (https), or empty to clear it.' }),
|
|
48
|
+
tslib_1.__metadata("design:type", String)
|
|
49
|
+
], NoticeChannelSettingPatch.prototype, "teamsChannelWebhook", void 0);
|
|
50
|
+
exports.NoticeChannelSettingPatch = NoticeChannelSettingPatch = tslib_1.__decorate([
|
|
51
|
+
(0, type_graphql_1.InputType)({ description: 'A change to the notice channels. A field left out stays as it is; an empty webhook clears it.' })
|
|
52
|
+
], NoticeChannelSettingPatch);
|
|
53
|
+
/** The notice attributes the domain sees - its own, then its ancestors', the closest one kept - read through `manager`. */
|
|
54
|
+
async function noticeAttributes(domain, manager) {
|
|
55
|
+
const ids = await (0, shell_1.getDomainIdsWithAncestors)(domain);
|
|
56
|
+
const all = await manager.getRepository(shell_1.EnvVar).find({ where: { domain: { id: (0, typeorm_1.In)(ids) }, active: true } });
|
|
57
|
+
const visible = all.filter(one => one.name.startsWith(notice_channels_js_1.NOTICE_ATTRIBUTE_PREFIX));
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const one of (0, shell_1.dedupByClosestDomain)(visible, ids, one => one.name))
|
|
60
|
+
result[one.name] = one.value;
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/** Writes the patch as the domain's own attributes. A cleared one is removed from this domain. */
|
|
64
|
+
async function writeNoticeChannels(domain, patch, manager) {
|
|
65
|
+
const repository = manager.getRepository(shell_1.EnvVar);
|
|
66
|
+
for (const { name, value } of (0, notice_channel_rules_js_1.noticeChannelWrites)(patch)) {
|
|
67
|
+
const own = await repository.findOne({ where: { domain: { id: domain.id }, name } });
|
|
68
|
+
if (value === null) {
|
|
69
|
+
if (own)
|
|
70
|
+
await repository.delete(own.id);
|
|
71
|
+
}
|
|
72
|
+
else if (own) {
|
|
73
|
+
await repository.save({ ...own, value, active: true });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
await repository.save({ domain, name, value, active: true, description: 'Notice channel (ADR-0164) - set on the Settings screen' });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return (0, notice_channel_rules_js_1.noticeChannelState)(await noticeAttributes(domain, manager));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The Settings screen's door to the notice channels. They are domain attributes (EnvVar), so the same privilege as the
|
|
83
|
+
* EnvVar screen opens them; what comes back never carries a webhook address.
|
|
84
|
+
*/
|
|
85
|
+
let NoticeChannelResolver = class NoticeChannelResolver {
|
|
86
|
+
async noticeChannelSetting(context) {
|
|
87
|
+
const { domain, tx } = context.state;
|
|
88
|
+
return (0, notice_channel_rules_js_1.noticeChannelState)(await noticeAttributes(domain, tx ?? (0, shell_1.getDataSource)().manager));
|
|
89
|
+
}
|
|
90
|
+
async updateNoticeChannelSetting(patch, context) {
|
|
91
|
+
const { domain, tx } = context.state;
|
|
92
|
+
return await writeNoticeChannels(domain, patch, tx);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
exports.NoticeChannelResolver = NoticeChannelResolver;
|
|
96
|
+
tslib_1.__decorate([
|
|
97
|
+
(0, type_graphql_1.Directive)('@privilege(category: "env-var", privilege: "query", domainOwnerGranted: true)'),
|
|
98
|
+
(0, type_graphql_1.Query)(returns => NoticeChannelSetting, { description: 'The notice channels of this domain - set or not, never the webhook addresses.' }),
|
|
99
|
+
tslib_1.__param(0, (0, type_graphql_1.Ctx)()),
|
|
100
|
+
tslib_1.__metadata("design:type", Function),
|
|
101
|
+
tslib_1.__metadata("design:paramtypes", [Object]),
|
|
102
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
103
|
+
], NoticeChannelResolver.prototype, "noticeChannelSetting", null);
|
|
104
|
+
tslib_1.__decorate([
|
|
105
|
+
(0, type_graphql_1.Directive)('@transaction'),
|
|
106
|
+
(0, type_graphql_1.Directive)('@privilege(category: "env-var", privilege: "mutation", domainOwnerGranted: true)'),
|
|
107
|
+
(0, type_graphql_1.Mutation)(returns => NoticeChannelSetting, { description: 'To change the notice channels of this domain.' }),
|
|
108
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('patch', type => NoticeChannelSettingPatch)),
|
|
109
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
110
|
+
tslib_1.__metadata("design:type", Function),
|
|
111
|
+
tslib_1.__metadata("design:paramtypes", [NoticeChannelSettingPatch, Object]),
|
|
112
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
113
|
+
], NoticeChannelResolver.prototype, "updateNoticeChannelSetting", null);
|
|
114
|
+
exports.NoticeChannelResolver = NoticeChannelResolver = tslib_1.__decorate([
|
|
115
|
+
(0, type_graphql_1.Resolver)(),
|
|
116
|
+
(0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
|
|
117
|
+
], NoticeChannelResolver);
|
|
118
|
+
//# sourceMappingURL=notice-channel-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notice-channel-resolver.js","sourceRoot":"","sources":["../../../server/service/notice-channel/notice-channel-resolver.ts"],"names":[],"mappings":";;;AA8CA,kDAgBC;;AA9DD,+CAA0H;AAC1H,qCAA4B;AAE5B,iDAA6H;AAE7H,oFAAqF;AACrF,uEAA4G;AAGrG,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CAM7B,CAAA;AANY,8CAAiB;AAE5B;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC;;8CACjD;AAGZ;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oDAAoD,EAAE,CAAC;;+CAChF;4BALF,iBAAiB;IAD7B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,+EAA+E,EAAE,CAAC;GAChG,iBAAiB,CAM7B;AAGM,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;CAMhC,CAAA;AANY,oDAAoB;AAE/B;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;;mDAC7C;AAGd;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAiB,EAAE,EAAE,WAAW,EAAE,iFAAiF,EAAE,CAAC;sCACvH,iBAAiB;0DAAA;+BALpB,oBAAoB;IADhC,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,kFAAkF,EAAE,CAAC;GACnG,oBAAoB,CAMhC;AAGM,IAAM,yBAAyB,GAA/B,MAAM,yBAAyB;CAMrC,CAAA;AANY,8DAAyB;AAEpC;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;;wDAC5D;AAGf;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kEAAkE,EAAE,CAAC;;sEAC/E;oCALjB,yBAAyB;IADrC,IAAA,wBAAS,EAAC,EAAE,WAAW,EAAE,+FAA+F,EAAE,CAAC;GAC/G,yBAAyB,CAMrC;AAED,2HAA2H;AAC3H,KAAK,UAAU,gBAAgB,CAAC,MAAW,EAAE,OAAY;IACvD,MAAM,GAAG,GAAG,MAAM,IAAA,iCAAyB,EAAC,MAAM,CAAC,CAAA;IACnD,MAAM,GAAG,GAAa,MAAM,OAAO,CAAC,aAAa,CAAC,cAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IACpH,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,4CAAuB,CAAC,CAAC,CAAA;IAC/E,MAAM,MAAM,GAA2B,EAAE,CAAA;IACzC,KAAK,MAAM,GAAG,IAAI,IAAA,4BAAoB,EAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAA;IACnG,OAAO,MAAM,CAAA;AACf,CAAC;AAED,kGAAkG;AAC3F,KAAK,UAAU,mBAAmB,CAAC,MAAW,EAAE,KAAU,EAAE,OAAY;IAC7E,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,cAAM,CAAC,CAAA;IAEhD,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAA,6CAAmB,EAAC,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;QAEpF,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,GAAG;gBAAE,MAAM,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC1C,CAAC;aAAM,IAAI,GAAG,EAAE,CAAC;YACf,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;QACxD,CAAC;aAAM,CAAC;YACN,MAAM,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,wDAAwD,EAAE,CAAC,CAAA;QACrI,CAAC;IACH,CAAC;IAED,OAAO,IAAA,4CAAkB,EAAC,MAAM,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AACpE,CAAC;AAED;;;GAGG;AAGI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAG1B,AAAN,KAAK,CAAC,oBAAoB,CAAQ,OAAwB;QACxD,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAY,CAAA;QAC3C,OAAO,IAAA,4CAAkB,EAAC,MAAM,gBAAgB,CAAC,MAAM,EAAE,EAAE,IAAI,IAAA,qBAAa,GAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAC1F,CAAC;IAKK,AAAN,KAAK,CAAC,0BAA0B,CACmB,KAAgC,EAC1E,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,KAAY,CAAA;QAC3C,OAAO,MAAM,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;IACrD,CAAC;CACF,CAAA;AAlBY,sDAAqB;AAG1B;IAFL,IAAA,wBAAS,EAAC,+EAA+E,CAAC;IAC1F,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,EAAE,WAAW,EAAE,+EAA+E,EAAE,CAAC;IAC7G,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;iEAGhC;AAKK;IAHL,IAAA,wBAAS,EAAC,cAAc,CAAC;IACzB,IAAA,wBAAS,EAAC,kFAAkF,CAAC;IAC7F,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC;IAEzG,mBAAA,IAAA,kBAAG,EAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAA;IAC/C,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CADkD,yBAAyB;;uEAKlF;gCAjBU,qBAAqB;IAFjC,IAAA,uBAAQ,GAAE;IACV,IAAA,4BAAa,EAAC,qBAAa,CAAC;GAChB,qBAAqB,CAkBjC","sourcesContent":["import { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Query, Resolver, UseMiddleware } from 'type-graphql'\nimport { In } from 'typeorm'\n\nimport { dedupByClosestDomain, EnvVar, getDataSource, getDomainIdsWithAncestors, SpeakRefusals } from '@things-factory/shell'\n\nimport { NOTICE_ATTRIBUTE_PREFIX } from '../../controllers/notice/notice-channels.js'\nimport { noticeChannelState, noticeChannelWrites, type NoticeChannelState } from './notice-channel-rules.js'\n\n@ObjectType({ description: 'Whether a secret is set, and the host it points at - never the secret itself.' })\nexport class NoticeSecretState {\n @Field({ description: 'Whether it is set for this domain.' })\n set: boolean\n\n @Field({ nullable: true, description: 'The host of the address, to tell which flow it is.' })\n host?: string\n}\n\n@ObjectType({ description: 'The channels notices take beyond the app and web push in this domain (ADR-0164).' })\nexport class NoticeChannelSetting {\n @Field({ description: 'Whether notices also go by mail.' })\n email: boolean\n\n @Field(type => NoticeSecretState, { description: 'The Teams channel webhook - where a notice to everyone in the domain is posted.' })\n teamsChannel: NoticeSecretState\n}\n\n@InputType({ description: 'A change to the notice channels. A field left out stays as it is; an empty webhook clears it.' })\nexport class NoticeChannelSettingPatch {\n @Field({ nullable: true, description: 'Whether notices also go by mail.' })\n email?: boolean\n\n @Field({ nullable: true, description: 'The Teams channel webhook address (https), or empty to clear it.' })\n teamsChannelWebhook?: string\n}\n\n/** The notice attributes the domain sees - its own, then its ancestors', the closest one kept - read through `manager`. */\nasync function noticeAttributes(domain: any, manager: any): Promise<Record<string, string>> {\n const ids = await getDomainIdsWithAncestors(domain)\n const all: EnvVar[] = await manager.getRepository(EnvVar).find({ where: { domain: { id: In(ids) }, active: true } })\n const visible = all.filter(one => one.name.startsWith(NOTICE_ATTRIBUTE_PREFIX))\n const result: Record<string, string> = {}\n for (const one of dedupByClosestDomain(visible, ids, one => one.name)) result[one.name] = one.value\n return result\n}\n\n/** Writes the patch as the domain's own attributes. A cleared one is removed from this domain. */\nexport async function writeNoticeChannels(domain: any, patch: any, manager: any): Promise<NoticeChannelState> {\n const repository = manager.getRepository(EnvVar)\n\n for (const { name, value } of noticeChannelWrites(patch)) {\n const own = await repository.findOne({ where: { domain: { id: domain.id }, name } })\n\n if (value === null) {\n if (own) await repository.delete(own.id)\n } else if (own) {\n await repository.save({ ...own, value, active: true })\n } else {\n await repository.save({ domain, name, value, active: true, description: 'Notice channel (ADR-0164) - set on the Settings screen' })\n }\n }\n\n return noticeChannelState(await noticeAttributes(domain, manager))\n}\n\n/**\n * The Settings screen's door to the notice channels. They are domain attributes (EnvVar), so the same privilege as the\n * EnvVar screen opens them; what comes back never carries a webhook address.\n */\n@Resolver()\n@UseMiddleware(SpeakRefusals)\nexport class NoticeChannelResolver {\n @Directive('@privilege(category: \"env-var\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => NoticeChannelSetting, { description: 'The notice channels of this domain - set or not, never the webhook addresses.' })\n async noticeChannelSetting(@Ctx() context: ResolverContext): Promise<NoticeChannelSetting> {\n const { domain, tx } = context.state as any\n return noticeChannelState(await noticeAttributes(domain, tx ?? getDataSource().manager))\n }\n\n @Directive('@transaction')\n @Directive('@privilege(category: \"env-var\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Mutation(returns => NoticeChannelSetting, { description: 'To change the notice channels of this domain.' })\n async updateNoticeChannelSetting(\n @Arg('patch', type => NoticeChannelSettingPatch) patch: NoticeChannelSettingPatch,\n @Ctx() context: ResolverContext\n ): Promise<NoticeChannelSetting> {\n const { domain, tx } = context.state as any\n return await writeNoticeChannels(domain, patch, tx)\n }\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SecretState = {
|
|
2
|
+
set: boolean;
|
|
3
|
+
host?: string;
|
|
4
|
+
};
|
|
5
|
+
export type NoticeChannelState = {
|
|
6
|
+
email: boolean;
|
|
7
|
+
teamsChannel: SecretState;
|
|
8
|
+
};
|
|
9
|
+
/** What may change - a field left out stays as it is; an empty webhook clears it. */
|
|
10
|
+
export type NoticeChannelPatch = {
|
|
11
|
+
email?: boolean | null;
|
|
12
|
+
teamsChannelWebhook?: string | null;
|
|
13
|
+
};
|
|
14
|
+
/** Set or not, and the host - enough to tell which flow it is without handing out the address. */
|
|
15
|
+
export declare function secretState(value: string | null | undefined): SecretState;
|
|
16
|
+
export declare function noticeChannelState(attributes: Record<string, string | undefined> | null | undefined): NoticeChannelState;
|
|
17
|
+
export declare const WEBHOOK_NOT_HTTPS = "notice-webhook-not-https";
|
|
18
|
+
/**
|
|
19
|
+
* The domain attributes a patch writes: a value to keep, or null to remove the domain's own. A webhook that is not an
|
|
20
|
+
* https address is refused by name - the channels would ignore it, and the person would think it was set.
|
|
21
|
+
*/
|
|
22
|
+
export declare function noticeChannelWrites(patch: NoticeChannelPatch): {
|
|
23
|
+
name: string;
|
|
24
|
+
value: string | null;
|
|
25
|
+
}[];
|