@things-factory/notification 10.1.106 → 10.1.107
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/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/fcm.d.ts +1 -2
- package/dist-server/controllers/fcm.js +5 -1
- package/dist-server/controllers/fcm.js.map +1 -1
- 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/receivers.d.ts +11 -0
- package/dist-server/controllers/receivers.js +30 -0
- package/dist-server/controllers/receivers.js.map +1 -0
- package/dist-server/middlewares/notification-middleware.js +3 -3
- package/dist-server/middlewares/notification-middleware.js.map +1 -1
- package/dist-server/routers/notification-router.js +26 -17
- package/dist-server/routers/notification-router.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/notification/directive-notification.js +19 -7
- package/dist-server/service/notification/directive-notification.js.map +1 -1
- package/dist-server/service/notification/index.d.ts +1 -1
- package/dist-server/service/notification/notification-audience.d.ts +24 -0
- package/dist-server/service/notification/notification-audience.js +31 -0
- package/dist-server/service/notification/notification-audience.js.map +1 -0
- package/dist-server/service/notification/notification-subscription.js +2 -14
- package/dist-server/service/notification/notification-subscription.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/tests/notification-audience.test.ts +53 -0
- package/tests/notify-receivers.test.ts +172 -0
|
@@ -21,9 +21,8 @@ export declare function register(user: any, { subscription }: {
|
|
|
21
21
|
export declare function unregister(user: any, { subscription }: {
|
|
22
22
|
subscription: any;
|
|
23
23
|
}): Promise<boolean>;
|
|
24
|
-
export declare function notify({ receivers,
|
|
24
|
+
export declare function notify({ receivers, tokens, topic, title, body, data, image, actions }: {
|
|
25
25
|
receivers: any;
|
|
26
|
-
privileges: any;
|
|
27
26
|
tokens: any;
|
|
28
27
|
topic: any;
|
|
29
28
|
title: any;
|
|
@@ -97,7 +97,11 @@ async function unregister(user, { subscription }) {
|
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
/*
|
|
101
|
+
* `receivers` are user ids. There was a `privileges` parameter until 2026-10-02; it was never read, so a
|
|
102
|
+
* caller that named privileges instead of people reached nobody and was not told. No caller used it.
|
|
103
|
+
*/
|
|
104
|
+
async function notify({ receivers, tokens, topic, title, body, data, image, actions }) {
|
|
101
105
|
if (!messaging) {
|
|
102
106
|
return;
|
|
103
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fcm.js","sourceRoot":"","sources":["../../server/controllers/fcm.ts"],"names":[],"mappings":"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","sourcesContent":["import fetch from 'node-fetch'\n\nimport { config, logger } from '@things-factory/env'\n\nimport { initializeApp, cert } from 'firebase-admin/app'\nimport { getMessaging } from 'firebase-admin/messaging'\n\nconst notificationConfig = config.get('notification') || {}\nconst { fcm, vapidKey } = notificationConfig\nconst { serviceAccount, appConfig, serverKey } = fcm || {}\nconst { publicKey } = vapidKey || {}\n\nvar messaging\n\nif (serviceAccount) {\n try {\n const app = initializeApp({\n credential: cert(serviceAccount)\n })\n\n messaging = getMessaging(app)\n } catch (err) {\n logger.error('incorrect notification configuration')\n logger.error(err)\n }\n}\n\nexport function getConfig() {\n return messaging\n ? {\n appConfig,\n vapidPublicKey: publicKey\n }\n : {}\n}\n\n/**\n * sendNotification\n *\n * @param receiver user.id who will receive notification\n * @param message message object to be sent\n */\nexport async function sendNotification({ receivers, message }) {\n if (!messaging) {\n return\n }\n\n notify({\n receivers,\n ...message\n })\n}\n\nexport async function register(user, { subscription }) {\n if (!messaging || !subscription || !user) {\n return false\n }\n\n try {\n var notification_key = await getDeviceGroup(user.id)\n\n if (!notification_key) {\n await postDeviceGroup({\n operation: 'create',\n notification_key_name: user.id,\n registration_ids: [subscription]\n })\n } else {\n await postDeviceGroup({\n operation: 'add',\n notification_key_name: user.id,\n notification_key,\n registration_ids: [subscription]\n })\n }\n\n return true\n } catch (err) {\n return\n }\n}\n\nexport async function unregister(user, { subscription }) {\n if (!messaging || !subscription || !user) {\n return false\n }\n\n try {\n var notification_key = await getDeviceGroup(user.id)\n\n if (notification_key) {\n await postDeviceGroup({\n operation: 'remove',\n notification_key_name: user.id,\n notification_key,\n registration_ids: [subscription]\n })\n }\n\n return true\n } catch (err) {\n return\n }\n}\n\nexport async function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }) {\n if (!messaging) {\n return\n }\n\n // Caution: non-string attributes are not allowed in FCM payload validation\n var notification = {\n title: title || '',\n body: body || '',\n image: image || ''\n }\n\n if (tokens && tokens instanceof Array && tokens.length > 0) {\n if (tokens.length > 1) {\n const response = await messaging.sendMulticast({\n notification,\n data,\n tokens\n })\n\n if (response.failureCount > 0) {\n const failedTokens = []\n response.responses.forEach((resp, idx) => {\n if (!resp.success) {\n failedTokens.push(tokens[idx])\n }\n })\n logger.error('List of tokens that caused failures: ' + failedTokens)\n }\n } else {\n const token = tokens[0]\n\n try {\n const response = await messaging.send({\n notification,\n data,\n token\n })\n logger.log('Successfully sent message:', response)\n } catch (err) {\n logger.error('Error sending message:', err)\n }\n }\n }\n\n if (topic) {\n try {\n const response = await messaging.send({\n notification,\n data,\n topic\n })\n logger.log('Successfully sent message:', response)\n } catch (err) {\n logger.error('Error sending message:', err)\n }\n }\n\n if (receivers && receivers instanceof Array) {\n for (let receiver of receivers) {\n const notification_key = await getDeviceGroup(receiver)\n\n if (notification_key) {\n await messaging.sendToDeviceGroup(notification_key, {\n notification,\n data\n })\n }\n }\n }\n}\n\nasync function postDeviceGroup(body) {\n var response = await fetch('https://fcm.googleapis.com/fcm/notification', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `key=${serverKey}`,\n project_id: appConfig.messagingSenderId\n },\n body: JSON.stringify(body)\n })\n\n if (response.ok) {\n const { notification_key } = await response.json()\n return notification_key\n } else {\n console.error('postDeviceGroup-notok', response.status, await response.text())\n }\n}\n\nasync function getDeviceGroup(notificationKeyName) {\n const response = await fetch(\n `https://fcm.googleapis.com/fcm/notification?notification_key_name=${notificationKeyName}`,\n {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `key=${serverKey}`,\n project_id: appConfig.messagingSenderId\n }\n }\n )\n\n if (response.ok) {\n const { notification_key } = await response.json()\n return notification_key\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fcm.js","sourceRoot":"","sources":["../../server/controllers/fcm.ts"],"names":[],"mappings":"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","sourcesContent":["import fetch from 'node-fetch'\n\nimport { config, logger } from '@things-factory/env'\n\nimport { initializeApp, cert } from 'firebase-admin/app'\nimport { getMessaging } from 'firebase-admin/messaging'\n\nconst notificationConfig = config.get('notification') || {}\nconst { fcm, vapidKey } = notificationConfig\nconst { serviceAccount, appConfig, serverKey } = fcm || {}\nconst { publicKey } = vapidKey || {}\n\nvar messaging\n\nif (serviceAccount) {\n try {\n const app = initializeApp({\n credential: cert(serviceAccount)\n })\n\n messaging = getMessaging(app)\n } catch (err) {\n logger.error('incorrect notification configuration')\n logger.error(err)\n }\n}\n\nexport function getConfig() {\n return messaging\n ? {\n appConfig,\n vapidPublicKey: publicKey\n }\n : {}\n}\n\n/**\n * sendNotification\n *\n * @param receiver user.id who will receive notification\n * @param message message object to be sent\n */\nexport async function sendNotification({ receivers, message }) {\n if (!messaging) {\n return\n }\n\n notify({\n receivers,\n ...message\n })\n}\n\nexport async function register(user, { subscription }) {\n if (!messaging || !subscription || !user) {\n return false\n }\n\n try {\n var notification_key = await getDeviceGroup(user.id)\n\n if (!notification_key) {\n await postDeviceGroup({\n operation: 'create',\n notification_key_name: user.id,\n registration_ids: [subscription]\n })\n } else {\n await postDeviceGroup({\n operation: 'add',\n notification_key_name: user.id,\n notification_key,\n registration_ids: [subscription]\n })\n }\n\n return true\n } catch (err) {\n return\n }\n}\n\nexport async function unregister(user, { subscription }) {\n if (!messaging || !subscription || !user) {\n return false\n }\n\n try {\n var notification_key = await getDeviceGroup(user.id)\n\n if (notification_key) {\n await postDeviceGroup({\n operation: 'remove',\n notification_key_name: user.id,\n notification_key,\n registration_ids: [subscription]\n })\n }\n\n return true\n } catch (err) {\n return\n }\n}\n\n/*\n * `receivers` are user ids. There was a `privileges` parameter until 2026-10-02; it was never read, so a\n * caller that named privileges instead of people reached nobody and was not told. No caller used it.\n */\nexport async function notify({ receivers, tokens, topic, title, body, data, image, actions }) {\n if (!messaging) {\n return\n }\n\n // Caution: non-string attributes are not allowed in FCM payload validation\n var notification = {\n title: title || '',\n body: body || '',\n image: image || ''\n }\n\n if (tokens && tokens instanceof Array && tokens.length > 0) {\n if (tokens.length > 1) {\n const response = await messaging.sendMulticast({\n notification,\n data,\n tokens\n })\n\n if (response.failureCount > 0) {\n const failedTokens = []\n response.responses.forEach((resp, idx) => {\n if (!resp.success) {\n failedTokens.push(tokens[idx])\n }\n })\n logger.error('List of tokens that caused failures: ' + failedTokens)\n }\n } else {\n const token = tokens[0]\n\n try {\n const response = await messaging.send({\n notification,\n data,\n token\n })\n logger.log('Successfully sent message:', response)\n } catch (err) {\n logger.error('Error sending message:', err)\n }\n }\n }\n\n if (topic) {\n try {\n const response = await messaging.send({\n notification,\n data,\n topic\n })\n logger.log('Successfully sent message:', response)\n } catch (err) {\n logger.error('Error sending message:', err)\n }\n }\n\n if (receivers && receivers instanceof Array) {\n for (let receiver of receivers) {\n const notification_key = await getDeviceGroup(receiver)\n\n if (notification_key) {\n await messaging.sendToDeviceGroup(notification_key, {\n notification,\n data\n })\n }\n }\n }\n}\n\nasync function postDeviceGroup(body) {\n var response = await fetch('https://fcm.googleapis.com/fcm/notification', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `key=${serverKey}`,\n project_id: appConfig.messagingSenderId\n },\n body: JSON.stringify(body)\n })\n\n if (response.ok) {\n const { notification_key } = await response.json()\n return notification_key\n } else {\n console.error('postDeviceGroup-notok', response.status, await response.text())\n }\n}\n\nasync function getDeviceGroup(notificationKeyName) {\n const response = await fetch(\n `https://fcm.googleapis.com/fcm/notification?notification_key_name=${notificationKeyName}`,\n {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `key=${serverKey}`,\n project_id: appConfig.messagingSenderId\n }\n }\n )\n\n if (response.ok) {\n const { notification_key } = await response.json()\n return notification_key\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/controllers/index.ts"],"names":[],"mappings":";;;AAAA,gDAAqB","sourcesContent":["export * from './fcm'"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/controllers/index.ts"],"names":[],"mappings":";;;AAAA,gDAAqB;AACrB,sDAA2B","sourcesContent":["export * from './fcm'\nexport * from './receivers'\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
|
2
|
+
/**
|
|
3
|
+
* The ids of the members of `domain` whose e-mail is one of `emails`, compared without case.
|
|
4
|
+
*
|
|
5
|
+
* A receiver is named by e-mail, and an e-mail names a person of any tenant. Until 2026-10-02 the
|
|
6
|
+
* lookup searched every user, so a member of one domain could push a notification to anyone.
|
|
7
|
+
* Only members of the sending domain are found now; an address outside it reaches nobody.
|
|
8
|
+
*/
|
|
9
|
+
export declare function domainMemberIdsByEmail(domain: Partial<Domain>, emails: string[]): Promise<string[]>;
|
|
10
|
+
/** A comma separated list of e-mails, as the sender form and the integration task write it. */
|
|
11
|
+
export declare function splitEmails(receivers: string | undefined | null): string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.domainMemberIdsByEmail = domainMemberIdsByEmail;
|
|
4
|
+
exports.splitEmails = splitEmails;
|
|
5
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
6
|
+
/**
|
|
7
|
+
* The ids of the members of `domain` whose e-mail is one of `emails`, compared without case.
|
|
8
|
+
*
|
|
9
|
+
* A receiver is named by e-mail, and an e-mail names a person of any tenant. Until 2026-10-02 the
|
|
10
|
+
* lookup searched every user, so a member of one domain could push a notification to anyone.
|
|
11
|
+
* Only members of the sending domain are found now; an address outside it reaches nobody.
|
|
12
|
+
*/
|
|
13
|
+
async function domainMemberIdsByEmail(domain, emails) {
|
|
14
|
+
const wanted = [...new Set(emails.map(email => email.trim().toLowerCase()).filter(Boolean))];
|
|
15
|
+
if (!domain?.id || wanted.length === 0) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const members = await (0, auth_base_1.buildDomainUsersQueryBuilder)(domain.id, 'USER')
|
|
19
|
+
.andWhere('LOWER(USER.email) IN (:...wanted)', { wanted })
|
|
20
|
+
.getMany();
|
|
21
|
+
return members.map(member => member.id);
|
|
22
|
+
}
|
|
23
|
+
/** A comma separated list of e-mails, as the sender form and the integration task write it. */
|
|
24
|
+
function splitEmails(receivers) {
|
|
25
|
+
return String(receivers || '')
|
|
26
|
+
.split(',')
|
|
27
|
+
.map(email => email.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=receivers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receivers.js","sourceRoot":"","sources":["../../server/controllers/receivers.ts"],"names":[],"mappings":";;AAUA,wDAWC;AAGD,kCAKC;AA7BD,yDAAwE;AAGxE;;;;;;GAMG;AACI,KAAK,UAAU,sBAAsB,CAAC,MAAuB,EAAE,MAAgB;IACpF,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAC5F,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,IAAA,wCAA4B,EAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC;SAClE,QAAQ,CAAC,mCAAmC,EAAE,EAAE,MAAM,EAAE,CAAC;SACzD,OAAO,EAAE,CAAA;IAEZ,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AACzC,CAAC;AAED,+FAA+F;AAC/F,SAAgB,WAAW,CAAC,SAAoC;IAC9D,OAAO,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;SAC3B,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC","sourcesContent":["import { buildDomainUsersQueryBuilder } from '@things-factory/auth-base'\nimport { Domain } from '@things-factory/shell'\n\n/**\n * The ids of the members of `domain` whose e-mail is one of `emails`, compared without case.\n *\n * A receiver is named by e-mail, and an e-mail names a person of any tenant. Until 2026-10-02 the\n * lookup searched every user, so a member of one domain could push a notification to anyone.\n * Only members of the sending domain are found now; an address outside it reaches nobody.\n */\nexport async function domainMemberIdsByEmail(domain: Partial<Domain>, emails: string[]): Promise<string[]> {\n const wanted = [...new Set(emails.map(email => email.trim().toLowerCase()).filter(Boolean))]\n if (!domain?.id || wanted.length === 0) {\n return []\n }\n\n const members = await buildDomainUsersQueryBuilder(domain.id, 'USER')\n .andWhere('LOWER(USER.email) IN (:...wanted)', { wanted })\n .getMany()\n\n return members.map(member => member.id)\n}\n\n/** A comma separated list of e-mails, as the sender form and the integration task write it. */\nexport function splitEmails(receivers: string | undefined | null): string[] {\n return String(receivers || '')\n .split(',')\n .map(email => email.trim())\n .filter(Boolean)\n}\n"]}
|
|
@@ -6,14 +6,13 @@ const env_1 = require("@things-factory/env");
|
|
|
6
6
|
const fcm_1 = require("../controllers/fcm");
|
|
7
7
|
const debug = require('debug')('things-factory:notification:notification-middleware');
|
|
8
8
|
function notifier(context) {
|
|
9
|
-
return async function ({ receivers,
|
|
9
|
+
return async function ({ receivers, tokens, topic, type, subject = 'info', title, body, image, url, actions, timestamp = Date.now(), mode = 'background' }) {
|
|
10
10
|
const { domain, user } = context.state;
|
|
11
11
|
try {
|
|
12
12
|
if (mode === 'background') {
|
|
13
13
|
/* for send webpush notification message */
|
|
14
14
|
await (0, fcm_1.notify)({
|
|
15
15
|
receivers,
|
|
16
|
-
privileges,
|
|
17
16
|
tokens,
|
|
18
17
|
topic,
|
|
19
18
|
title,
|
|
@@ -28,11 +27,12 @@ function notifier(context) {
|
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
29
|
else {
|
|
31
|
-
/* for send publish notification to clients */
|
|
30
|
+
/* for send publish notification to clients - to the receivers only, when there are any */
|
|
32
31
|
// TODO CONFIRM data format
|
|
33
32
|
await shell_1.pubsub.publish('notification', {
|
|
34
33
|
notification: {
|
|
35
34
|
domain,
|
|
35
|
+
receivers,
|
|
36
36
|
subject,
|
|
37
37
|
type,
|
|
38
38
|
title,
|
|
@@ -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":";;AAiEA,wDAMC;AAvED,iDAA8C;AAC9C,6CAA4C;AAE5C,4CAA2C;AAE3C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,qDAAqD,CAAC,CAAA;AAErF,SAAS,QAAQ,CAAC,OAAO;IACvB,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,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEtC,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,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC1C,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\nfunction notifier(context) {\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 const { domain, user } = context.state\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 context.state.notify = notifier(context)\n }\n\n await next()\n}\n"]}
|
|
@@ -3,10 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.notificationRouter = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
|
6
|
-
const typeorm_1 = require("typeorm");
|
|
7
6
|
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
|
-
const shell_1 = require("@things-factory/shell");
|
|
9
7
|
const fcm_1 = require("../controllers/fcm");
|
|
8
|
+
const receivers_1 = require("../controllers/receivers");
|
|
10
9
|
const debug = require('debug')('things-factory:notification:notification-router');
|
|
11
10
|
exports.notificationRouter = new koa_router_1.default({
|
|
12
11
|
prefix: '/notification'
|
|
@@ -28,23 +27,33 @@ exports.notificationRouter.post('/unregister', async (context, next) => {
|
|
|
28
27
|
context.body = await (0, fcm_1.unregister)(user, body);
|
|
29
28
|
context.status = 200;
|
|
30
29
|
});
|
|
30
|
+
/*
|
|
31
|
+
* Sending to yourself is the settings page's test and needs nothing more. Sending to anyone else is what
|
|
32
|
+
* a notification rule does, so it asks what changing a rule asks.
|
|
33
|
+
*/
|
|
34
|
+
const SEND_TO_OTHERS = { category: 'notification-rule', privilege: 'mutation', owner: true };
|
|
31
35
|
exports.notificationRouter.post('/notify', async (context, next) => {
|
|
32
|
-
const { user, domain, notify } = context.state;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
const { user, domain, notify, unsafeIP, prohibitedPrivileges } = context.state;
|
|
37
|
+
var { receivers, ...options } = context.request.body || {};
|
|
38
|
+
const emails = (0, receivers_1.splitEmails)(receivers);
|
|
39
|
+
const found = await (0, receivers_1.domainMemberIdsByEmail)(domain, emails);
|
|
40
|
+
debug('post:/notify', emails, found, user.id);
|
|
41
|
+
if (emails.length > 0 && found.length === 0) {
|
|
42
|
+
/* every address named is outside this domain (or nobody) - not a fallback to the sender */
|
|
43
|
+
context.status = 404;
|
|
44
|
+
context.body = { success: false };
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const targets = found.length > 0 ? found : [user.id];
|
|
48
|
+
if (targets.some(id => id !== user.id) &&
|
|
49
|
+
!(await (0, auth_base_1.checkPermission)(SEND_TO_OTHERS, user, domain, unsafeIP, prohibitedPrivileges))) {
|
|
50
|
+
context.status = 403;
|
|
51
|
+
context.body = { success: false };
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
45
54
|
await notify({
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
...options,
|
|
56
|
+
receivers: targets
|
|
48
57
|
});
|
|
49
58
|
context.body = {
|
|
50
59
|
success: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-router.js","sourceRoot":"","sources":["../../server/routers/notification-router.ts"],"names":[],"mappings":";;;;AAAA,oEAA+B;
|
|
1
|
+
{"version":3,"file":"notification-router.js","sourceRoot":"","sources":["../../server/routers/notification-router.ts"],"names":[],"mappings":"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","sourcesContent":["import Router from 'koa-router'\n\nimport { checkPermission } from '@things-factory/auth-base'\n\nimport { getConfig, register, unregister } from '../controllers/fcm'\nimport { domainMemberIdsByEmail, splitEmails } from '../controllers/receivers'\n\nconst debug = require('debug')('things-factory:notification:notification-router')\n\nexport const notificationRouter = new Router({\n prefix: '/notification'\n})\n\nnotificationRouter.get('/config', async (context, next) => {\n const config = getConfig()\n\n context.type = 'application/json'\n context.body = config\n})\n\nnotificationRouter.post('/register', async (context, next) => {\n const { user } = context.state\n const { body } = context.request\n context.body = await register(user, body)\n context.status = 200\n})\n\nnotificationRouter.post('/unregister', async (context, next) => {\n const { user } = context.state\n const { body } = context.request\n context.body = await unregister(user, body)\n context.status = 200\n})\n\n/*\n * Sending to yourself is the settings page's test and needs nothing more. Sending to anyone else is what\n * a notification rule does, so it asks what changing a rule asks.\n */\nconst SEND_TO_OTHERS = { category: 'notification-rule', privilege: 'mutation', owner: true }\n\nnotificationRouter.post('/notify', async (context, next) => {\n const { user, domain, notify, unsafeIP, prohibitedPrivileges } = context.state\n\n var { receivers, ...options } = context.request.body || {}\n\n const emails = splitEmails(receivers)\n const found = await domainMemberIdsByEmail(domain, emails)\n\n debug('post:/notify', emails, found, user.id)\n\n if (emails.length > 0 && found.length === 0) {\n /* every address named is outside this domain (or nobody) - not a fallback to the sender */\n context.status = 404\n context.body = { success: false }\n return\n }\n\n const targets = found.length > 0 ? found : [user.id]\n\n if (\n targets.some(id => id !== user.id) &&\n !(await checkPermission(SEND_TO_OTHERS, user, domain, unsafeIP, prohibitedPrivileges))\n ) {\n context.status = 403\n context.body = { success: false }\n return\n }\n\n await notify({\n ...options,\n receivers: targets\n })\n\n context.body = {\n success: true\n }\n})\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export * from './notification-rule/notification-rule';
|
|
2
2
|
export * from './notification/notification';
|
|
3
|
-
export declare const entities: (typeof import("./notification
|
|
3
|
+
export declare const entities: (typeof import("./notification/notification").Notification | typeof import("./notification-rule/notification-rule").NotificationRule)[];
|
|
4
4
|
export declare const subscribers: typeof import("./notification-rule/event-subscriber").NotificationRuleHistoryEntitySubscriber[];
|
|
5
5
|
export declare const schema: {
|
|
6
6
|
typeDefs: {
|
|
7
7
|
notificationDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
8
8
|
};
|
|
9
|
-
resolverClasses: (typeof import("./notification
|
|
9
|
+
resolverClasses: (typeof import("./notification/notification-query").NotificationQuery | typeof import("./notification-rule/notification-rule-query").NotificationRuleQuery | typeof import("./notification/notification-subscription").NotificationSubscription | typeof import("./notification-rule/notification-rule-mutation").NotificationRuleMutation | typeof import("./notification/notification-mutation").NotificationMutation)[];
|
|
10
10
|
directives: {
|
|
11
11
|
notification: (schema: import("graphql").GraphQLSchema) => import("graphql").GraphQLSchema;
|
|
12
12
|
};
|
|
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const graphql_1 = require("graphql");
|
|
6
6
|
const graphql_tag_1 = tslib_1.__importDefault(require("graphql-tag"));
|
|
7
7
|
const utils_1 = require("@graphql-tools/utils");
|
|
8
|
+
const receivers_1 = require("../../controllers/receivers");
|
|
8
9
|
const debug = require('debug')('things-factory:notification:directive-notification');
|
|
9
10
|
const DIRECTIVE = 'notification';
|
|
10
11
|
exports.notificationDirectiveTypeDefs = (0, graphql_tag_1.default) `
|
|
@@ -28,8 +29,7 @@ const directiveNotification = (schema) => (0, utils_1.mapSchema)(schema, {
|
|
|
28
29
|
if (!args) {
|
|
29
30
|
throw new Error(`Unexpected Error. args should be defined in @notification directive for field ${fieldName}.`);
|
|
30
31
|
}
|
|
31
|
-
const { receivers,
|
|
32
|
-
const timestamp = Date.now();
|
|
32
|
+
const { receivers, ...declared } = notificationDirective;
|
|
33
33
|
fieldConfig.resolve = async function (source, args, context, info) {
|
|
34
34
|
const result = await resolve.call(this, source, args, context, info);
|
|
35
35
|
const { domain, user, notify } = context.state;
|
|
@@ -38,11 +38,23 @@ const directiveNotification = (schema) => (0, utils_1.mapSchema)(schema, {
|
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
40
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
/*
|
|
42
|
+
* The directive's arguments are the notification. Until 2026-10-02 they were passed nested as
|
|
43
|
+
* `notificationDirective`, which the notifier does not read: every field sent an empty push to the caller.
|
|
44
|
+
* Receivers are e-mails of this domain's members; none named means the caller.
|
|
45
|
+
*/
|
|
46
|
+
const named = (0, receivers_1.splitEmails)(receivers);
|
|
47
|
+
const targets = named.length > 0 ? await (0, receivers_1.domainMemberIdsByEmail)(domain, named) : [user.id];
|
|
48
|
+
if (targets.length === 0) {
|
|
49
|
+
debug('no receiver is a member of this domain', named);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
await notify({
|
|
53
|
+
...declared,
|
|
54
|
+
receivers: targets,
|
|
55
|
+
timestamp: Date.now()
|
|
56
|
+
});
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
catch (err) {
|
|
48
60
|
debug(err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directive-notification.js","sourceRoot":"","sources":["../../../server/service/notification/directive-notification.ts"],"names":[],"mappings":";;;;AAAA,qCAA6D;AAC7D,sEAA6B;AAE7B,gDAA0E;AAE1E,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,oDAAoD,CAAC,CAAA;AAEpF,MAAM,SAAS,GAAG,cAAc,CAAA;AAEnB,QAAA,6BAA6B,GAAG,IAAA,qBAAG,EAAA;;;;;;;;;;;;CAY/C,CAAA;AACM,MAAM,qBAAqB,GAAG,CAAC,MAAqB,EAAE,EAAE,CAC7D,IAAA,iBAAS,EAAC,MAAM,EAAE;IAChB,CAAC,kBAAU,CAAC,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACtE,MAAM,qBAAqB,GAAG,IAAA,oBAAY,EAAC,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC/E,IAAI,qBAAqB,EAAE,CAAC;YAC1B,MAAM,EAAE,OAAO,GAAG,8BAAoB,EAAE,IAAI,EAAE,GAAG,WAAW,CAAA;YAE5D,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,iFAAiF,SAAS,GAAG,CAAC,CAAA;YAChH,CAAC;YAED,MAAM,
|
|
1
|
+
{"version":3,"file":"directive-notification.js","sourceRoot":"","sources":["../../../server/service/notification/directive-notification.ts"],"names":[],"mappings":";;;;AAAA,qCAA6D;AAC7D,sEAA6B;AAE7B,gDAA0E;AAE1E,2DAAiF;AAEjF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,oDAAoD,CAAC,CAAA;AAEpF,MAAM,SAAS,GAAG,cAAc,CAAA;AAEnB,QAAA,6BAA6B,GAAG,IAAA,qBAAG,EAAA;;;;;;;;;;;;CAY/C,CAAA;AACM,MAAM,qBAAqB,GAAG,CAAC,MAAqB,EAAE,EAAE,CAC7D,IAAA,iBAAS,EAAC,MAAM,EAAE;IAChB,CAAC,kBAAU,CAAC,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACtE,MAAM,qBAAqB,GAAG,IAAA,oBAAY,EAAC,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC/E,IAAI,qBAAqB,EAAE,CAAC;YAC1B,MAAM,EAAE,OAAO,GAAG,8BAAoB,EAAE,IAAI,EAAE,GAAG,WAAW,CAAA;YAE5D,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,iFAAiF,SAAS,GAAG,CAAC,CAAA;YAChH,CAAC;YAED,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,GAAG,qBAAqB,CAAA;YAExD,WAAW,CAAC,OAAO,GAAG,KAAK,WAAW,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI;gBAC/D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;gBAEpE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;gBAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;oBACZ,KAAK,CAAC,gBAAgB,CAAC,CAAA;gBACzB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC;wBACH;;;;2BAIG;wBACH,MAAM,KAAK,GAAG,IAAA,uBAAW,EAAC,SAAS,CAAC,CAAA;wBACpC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,IAAA,kCAAsB,EAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;wBAE1F,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;4BACzB,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAA;wBACxD,CAAC;6BAAM,CAAC;4BACN,MAAM,MAAM,CAAC;gCACX,GAAG,QAAQ;gCACX,SAAS,EAAE,OAAO;gCAClB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;6BACtB,CAAC,CAAA;wBACJ,CAAC;oBACH,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,KAAK,CAAC,GAAG,CAAC,CAAA;oBACZ,CAAC;gBACH,CAAC;gBAED,OAAO,MAAM,CAAA;YACf,CAAC,CAAA;YAED,OAAO,WAAW,CAAA;QACpB,CAAC;IACH,CAAC;CACF,CAAC,CAAA;AAlDS,QAAA,qBAAqB,yBAkD9B","sourcesContent":["import { defaultFieldResolver, GraphQLSchema } from 'graphql'\nimport gql from 'graphql-tag'\n\nimport { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils'\n\nimport { domainMemberIdsByEmail, splitEmails } from '../../controllers/receivers'\n\nconst debug = require('debug')('things-factory:notification:directive-notification')\n\nconst DIRECTIVE = 'notification'\n\nexport const notificationDirectiveTypeDefs = gql`\n directive @notification(\n receivers: String\n topic: String\n subject: String # info\n title: String\n body: String\n image: String\n url: String\n # timestamp = String(Date.now())\n mode: String # background\n ) on FIELD_DEFINITION\n`\nexport const directiveNotification = (schema: GraphQLSchema) =>\n mapSchema(schema, {\n [MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName, schema) => {\n const notificationDirective = getDirective(schema, fieldConfig, DIRECTIVE)?.[0]\n if (notificationDirective) {\n const { resolve = defaultFieldResolver, args } = fieldConfig\n\n if (!args) {\n throw new Error(`Unexpected Error. args should be defined in @notification directive for field ${fieldName}.`)\n }\n\n const { receivers, ...declared } = notificationDirective\n\n fieldConfig.resolve = async function (source, args, context, info) {\n const result = await resolve.call(this, source, args, context, info)\n\n const { domain, user, notify } = context.state\n\n if (!notify) {\n debug('notify not set')\n } else {\n try {\n /*\n * The directive's arguments are the notification. Until 2026-10-02 they were passed nested as\n * `notificationDirective`, which the notifier does not read: every field sent an empty push to the caller.\n * Receivers are e-mails of this domain's members; none named means the caller.\n */\n const named = splitEmails(receivers)\n const targets = named.length > 0 ? await domainMemberIdsByEmail(domain, named) : [user.id]\n\n if (targets.length === 0) {\n debug('no receiver is a member of this domain', named)\n } else {\n await notify({\n ...declared,\n receivers: targets,\n timestamp: Date.now()\n })\n }\n } catch (err) {\n debug(err)\n }\n }\n\n return result\n }\n\n return fieldConfig\n }\n }\n })\n"]}
|
|
@@ -6,7 +6,7 @@ export declare const typeDefs: {
|
|
|
6
6
|
notificationDirectiveTypeDefs: import("graphql").DocumentNode;
|
|
7
7
|
};
|
|
8
8
|
export declare const entities: (typeof Notification)[];
|
|
9
|
-
export declare const resolvers: (typeof NotificationQuery | typeof
|
|
9
|
+
export declare const resolvers: (typeof NotificationQuery | typeof NotificationSubscription | typeof NotificationMutation)[];
|
|
10
10
|
export declare const directives: {
|
|
11
11
|
notification: (schema: import("graphql").GraphQLSchema) => import("graphql").GraphQLSchema;
|
|
12
12
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a published 'notification' reaches one subscriber.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the subscription resolver so it can be tested without type-graphql.
|
|
5
|
+
*/
|
|
6
|
+
type DomainLike = {
|
|
7
|
+
subdomain?: string;
|
|
8
|
+
} | null | undefined;
|
|
9
|
+
export type PublishedNotification = {
|
|
10
|
+
domain?: DomainLike;
|
|
11
|
+
subject?: string;
|
|
12
|
+
receivers?: string[];
|
|
13
|
+
};
|
|
14
|
+
export type Subscriber = {
|
|
15
|
+
domain?: DomainLike;
|
|
16
|
+
user?: {
|
|
17
|
+
id?: string;
|
|
18
|
+
domains?: {
|
|
19
|
+
subdomain?: string;
|
|
20
|
+
}[];
|
|
21
|
+
} | null;
|
|
22
|
+
};
|
|
23
|
+
export declare function reaches(notification: PublishedNotification, { domain, user }: Subscriber, subjects?: string[]): boolean;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a published 'notification' reaches one subscriber.
|
|
4
|
+
*
|
|
5
|
+
* Kept apart from the subscription resolver so it can be tested without type-graphql.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.reaches = reaches;
|
|
9
|
+
function reaches(notification, { domain, user }, subjects = []) {
|
|
10
|
+
const subdomain = notification.domain?.subdomain;
|
|
11
|
+
if (subdomain) {
|
|
12
|
+
if (domain?.subdomain) {
|
|
13
|
+
if (subdomain !== domain.subdomain) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else if (!user?.domains?.find(d => d.subdomain === subdomain)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/*
|
|
22
|
+
* A notification addressed to people reaches only them. Until 2026-10-02 the receivers were dropped on
|
|
23
|
+
* the way to pubsub, so a data archive's download link went to every subscriber of the domain.
|
|
24
|
+
*/
|
|
25
|
+
const { receivers } = notification;
|
|
26
|
+
if (Array.isArray(receivers) && receivers.length > 0 && !receivers.includes(user?.id)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return !notification.subject || subjects.indexOf(notification.subject) !== -1;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=notification-audience.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-audience.js","sourceRoot":"","sources":["../../../server/service/notification/notification-audience.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAgBH,0BAuBC;AAvBD,SAAgB,OAAO,CAAC,YAAmC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAc,EAAE,WAAqB,EAAE;IAChH,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAA;IAEhD,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,MAAM,EAAE,SAAS,EAAE,CAAC;YACtB,IAAI,SAAS,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;gBACnC,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,CAAC;YAChE,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,MAAM,EAAE,SAAS,EAAE,GAAG,YAAY,CAAA;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;QACtF,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,CAAC,YAAY,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;AAC/E,CAAC","sourcesContent":["/**\n * Whether a published 'notification' reaches one subscriber.\n *\n * Kept apart from the subscription resolver so it can be tested without type-graphql.\n */\n\ntype DomainLike = { subdomain?: string } | null | undefined\n\nexport type PublishedNotification = {\n domain?: DomainLike\n subject?: string\n /* user ids; absent or empty means everyone in the domain */\n receivers?: string[]\n}\n\nexport type Subscriber = {\n domain?: DomainLike\n user?: { id?: string; domains?: { subdomain?: string }[] } | null\n}\n\nexport function reaches(notification: PublishedNotification, { domain, user }: Subscriber, subjects: string[] = []): boolean {\n const subdomain = notification.domain?.subdomain\n\n if (subdomain) {\n if (domain?.subdomain) {\n if (subdomain !== domain.subdomain) {\n return false\n }\n } else if (!user?.domains?.find(d => d.subdomain === subdomain)) {\n return false\n }\n }\n\n /*\n * A notification addressed to people reaches only them. Until 2026-10-02 the receivers were dropped on\n * the way to pubsub, so a data archive's download link went to every subscriber of the domain.\n */\n const { receivers } = notification\n if (Array.isArray(receivers) && receivers.length > 0 && !receivers.includes(user?.id)) {\n return false\n }\n\n return !notification.subject || subjects.indexOf(notification.subject) !== -1\n}\n"]}
|
|
@@ -6,6 +6,7 @@ const graphql_yoga_1 = require("graphql-yoga");
|
|
|
6
6
|
const type_graphql_1 = require("type-graphql");
|
|
7
7
|
const shell_1 = require("@things-factory/shell");
|
|
8
8
|
const notification_1 = require("./notification");
|
|
9
|
+
const notification_audience_1 = require("./notification-audience");
|
|
9
10
|
let NotificationSubscription = class NotificationSubscription {
|
|
10
11
|
notification(payload, subjects) {
|
|
11
12
|
return payload.notification;
|
|
@@ -18,20 +19,7 @@ tslib_1.__decorate([
|
|
|
18
19
|
const { subjects = [] } = args;
|
|
19
20
|
return (0, graphql_yoga_1.pipe)(shell_1.pubsub.subscribe('notification'), (0, graphql_yoga_1.filter)(async (payload) => {
|
|
20
21
|
/* normally, subscription context doesn't have domain */
|
|
21
|
-
|
|
22
|
-
const { notification: { subject, domain: pdomain } } = payload;
|
|
23
|
-
const subdomain = pdomain?.subdomain;
|
|
24
|
-
if (subdomain) {
|
|
25
|
-
if (domain?.subdomain) {
|
|
26
|
-
if (subdomain !== domain.subdomain) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
else if (!user.domains.find(d => d.subdomain === subdomain)) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return !subject || subjects.indexOf(subject) !== -1;
|
|
22
|
+
return (0, notification_audience_1.reaches)(payload.notification, context.state, subjects);
|
|
35
23
|
}));
|
|
36
24
|
}
|
|
37
25
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-subscription.js","sourceRoot":"","sources":["../../../server/service/notification/notification-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAA+E;AAE/E,
|
|
1
|
+
{"version":3,"file":"notification-subscription.js","sourceRoot":"","sources":["../../../server/service/notification/notification-subscription.ts"],"names":[],"mappings":";;;;AAAA,+CAA2C;AAC3C,+CAA+E;AAE/E,iDAA8C;AAE9C,iDAA6C;AAC7C,mEAAwE;AAGjE,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IAcnC,YAAY,CAAS,OAAuC,EAAyD,QAAkB;QACrI,OAAO,OAAO,CAAC,YAAY,CAAA;IAC7B,CAAC;CACF,CAAA;AAjBY,4DAAwB;AAcnC;IAbC,IAAA,2BAAY,EAAC;QACZ,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;YACrC,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,IAAI,CAAA;YAE9B,OAAO,IAAA,mBAAI,EACT,cAAM,CAAC,SAAS,CAAC,cAAc,CAAC,EAChC,IAAA,qBAAM,EAAC,KAAK,EAAE,OAAgD,EAAE,EAAE;gBAChE,wDAAwD;gBACxD,OAAO,IAAA,+BAAO,EAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;YAC/D,CAAC,CAAC,CACH,CAAA;QACH,CAAC;KACF,CAAC;IACY,mBAAA,IAAA,mBAAI,GAAE,CAAA;IAA2C,mBAAA,IAAA,kBAAG,EAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;4CAAsB,2BAAY;4DAErJ;mCAhBU,wBAAwB;IADpC,IAAA,uBAAQ,EAAC,2BAAY,CAAC;GACV,wBAAwB,CAiBpC","sourcesContent":["import { filter, pipe } from 'graphql-yoga'\nimport { Arg, Resolver, FieldResolver, Root, Subscription } from 'type-graphql'\n\nimport { pubsub } from '@things-factory/shell'\n\nimport { Notification } from './notification'\nimport { PublishedNotification, reaches } from './notification-audience'\n\n@Resolver(Notification)\nexport class NotificationSubscription {\n @Subscription({\n subscribe: ({ args, context, info }) => {\n const { subjects = [] } = args\n\n return pipe(\n pubsub.subscribe('notification'),\n filter(async (payload: { notification: PublishedNotification }) => {\n /* normally, subscription context doesn't have domain */\n return reaches(payload.notification, context.state, subjects)\n })\n )\n }\n })\n notification(@Root() payload: { notification: Notification }, @Arg('subjects', type => [String], { nullable: true }) subjects: string[]): Notification {\n return payload.notification\n }\n}\n"]}
|