@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.
Files changed (60) hide show
  1. package/dist-client/bootstrap.js +8 -3
  2. package/dist-client/bootstrap.js.map +1 -1
  3. package/dist-client/index.d.ts +1 -0
  4. package/dist-client/index.js +1 -0
  5. package/dist-client/index.js.map +1 -1
  6. package/dist-client/store-once.d.ts +14 -0
  7. package/dist-client/store-once.js +32 -0
  8. package/dist-client/store-once.js.map +1 -0
  9. package/dist-client/tsconfig.tsbuildinfo +1 -1
  10. package/dist-client/viewparts/notice-channel-setting-let.d.ts +32 -0
  11. package/dist-client/viewparts/notice-channel-setting-let.js +175 -0
  12. package/dist-client/viewparts/notice-channel-setting-let.js.map +1 -0
  13. package/dist-server/controllers/index.d.ts +1 -0
  14. package/dist-server/controllers/index.js +1 -0
  15. package/dist-server/controllers/index.js.map +1 -1
  16. package/dist-server/controllers/notice/deliver-notice.d.ts +78 -0
  17. package/dist-server/controllers/notice/deliver-notice.js +101 -0
  18. package/dist-server/controllers/notice/deliver-notice.js.map +1 -0
  19. package/dist-server/controllers/notice/index.d.ts +4 -0
  20. package/dist-server/controllers/notice/index.js +8 -0
  21. package/dist-server/controllers/notice/index.js.map +1 -0
  22. package/dist-server/controllers/notice/notice-channels.d.ts +67 -0
  23. package/dist-server/controllers/notice/notice-channels.js +62 -0
  24. package/dist-server/controllers/notice/notice-channels.js.map +1 -0
  25. package/dist-server/controllers/notice/notice-email.d.ts +28 -0
  26. package/dist-server/controllers/notice/notice-email.js +125 -0
  27. package/dist-server/controllers/notice/notice-email.js.map +1 -0
  28. package/dist-server/controllers/notice/send-notice.d.ts +16 -0
  29. package/dist-server/controllers/notice/send-notice.js +72 -0
  30. package/dist-server/controllers/notice/send-notice.js.map +1 -0
  31. package/dist-server/middlewares/index.d.ts +1 -0
  32. package/dist-server/middlewares/index.js +3 -0
  33. package/dist-server/middlewares/index.js.map +1 -1
  34. package/dist-server/middlewares/notification-middleware.d.ts +19 -0
  35. package/dist-server/middlewares/notification-middleware.js +9 -3
  36. package/dist-server/middlewares/notification-middleware.js.map +1 -1
  37. package/dist-server/service/index.d.ts +1 -1
  38. package/dist-server/service/index.js +3 -1
  39. package/dist-server/service/index.js.map +1 -1
  40. package/dist-server/service/notice-channel/index.d.ts +2 -0
  41. package/dist-server/service/notice-channel/index.js +6 -0
  42. package/dist-server/service/notice-channel/index.js.map +1 -0
  43. package/dist-server/service/notice-channel/notice-channel-resolver.d.ts +23 -0
  44. package/dist-server/service/notice-channel/notice-channel-resolver.js +118 -0
  45. package/dist-server/service/notice-channel/notice-channel-resolver.js.map +1 -0
  46. package/dist-server/service/notice-channel/notice-channel-rules.d.ts +25 -0
  47. package/dist-server/service/notice-channel/notice-channel-rules.js +60 -0
  48. package/dist-server/service/notice-channel/notice-channel-rules.js.map +1 -0
  49. package/dist-server/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +9 -7
  51. package/tests/notice-channel-setting.test.ts +107 -0
  52. package/tests/notice-delivery.test.ts +328 -0
  53. package/tests/open-doors.test.ts +5 -2
  54. package/tests/store-once.test.ts +88 -0
  55. package/translations/en.json +10 -1
  56. package/translations/es.json +10 -1
  57. package/translations/ja.json +10 -1
  58. package/translations/ko.json +10 -1
  59. package/translations/ms.json +10 -1
  60. package/translations/zh.json +10 -1
@@ -6,6 +6,7 @@ import { navigate, notificationStore, store } from '@operato/shell';
6
6
  import { subscribe as graphqlSubscribe } from '@operato/graphql';
7
7
  import { subscribe, unsubscribe, UPDATE_NOTIFICATION } from './actions/notification-fcm';
8
8
  import notification from './reducers/notification';
9
+ import { storeOnce } from './store-once';
9
10
  var graphqlSubscription;
10
11
  /** IndexedDB 알림 이력에는 Error 객체가 아닌, 언제나 저장 가능한 원문만 남긴다. */
11
12
  function technicalText(ex) {
@@ -25,9 +26,13 @@ function technicalText(ex) {
25
26
  export default async function bootstrap() {
26
27
  /* initialize reducers */
27
28
  store.addReducers({ notification });
28
- async function onnotification(notification) {
29
+ /** `received` - a notice the server published, which every open tab receives alike, so it is kept once. */
30
+ async function onnotification(notification, received = false) {
29
31
  store.dispatch((async (dispatch) => {
30
- if (notification) {
32
+ if (notification && received) {
33
+ await storeOnce(notificationStore, notification);
34
+ }
35
+ else if (notification) {
31
36
  await notificationStore.add(notification);
32
37
  await notificationStore.limit();
33
38
  }
@@ -81,7 +86,7 @@ export default async function bootstrap() {
81
86
  onnotification({
82
87
  timestamp: Date.now(),
83
88
  ...notification
84
- });
89
+ }, true);
85
90
  }
86
91
  });
87
92
  });
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\n\nimport { notify } from '@operato/layout'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\n\nimport { i18next } from '@operato/i18n'\nimport { navigate, notificationStore, store } from '@operato/shell'\nimport { subscribe as graphqlSubscribe } from '@operato/graphql'\n\nimport { subscribe, unsubscribe, UPDATE_NOTIFICATION } from './actions/notification-fcm'\nimport notification from './reducers/notification'\n\nvar graphqlSubscription: any\n\n/** IndexedDB 알림 이력에는 Error 객체가 아닌, 언제나 저장 가능한 원문만 남긴다. */\nfunction technicalText(ex: any): string | undefined {\n if (!ex) return undefined\n if (typeof ex === 'string') return ex\n if (ex instanceof Error) return ex.stack || `${ex.name}: ${ex.message}`\n\n try {\n return JSON.stringify(ex, null, 2)\n } catch {\n return String(ex)\n }\n}\n\nexport default async function bootstrap() {\n /* initialize reducers */\n store.addReducers({ notification })\n\n async function onnotification(notification?: any) {\n store.dispatch((async dispatch => {\n if (notification) {\n await notificationStore.add(notification)\n await notificationStore.limit()\n }\n\n const history = await notificationStore.getAll()\n\n dispatch({\n type: UPDATE_NOTIFICATION,\n history: [...history]\n })\n }) as any)\n }\n\n auth.on('presignout', async () => {\n try {\n await unsubscribe()\n /* await */ notificationStore.clear()\n } catch (err) {\n console.warn(err)\n }\n })\n\n auth.on('profile', async ({ credential }) => {\n try {\n await subscribe()\n } catch (err) {\n console.warn(err)\n }\n\n /*\n * subscription graphql notifications\n */\n var query = gql`\n subscription notification($subjects: [String!]) {\n notification(subjects: $subjects) {\n subject\n type\n title\n body\n url\n image\n timestamp\n }\n }\n `\n\n graphqlSubscription = await graphqlSubscribe(\n {\n query,\n variables: {\n subjects: ['info', 'warn', 'error', 'reload']\n }\n },\n {\n next({ data }) {\n const { notification } = data || {}\n onnotification({\n timestamp: Date.now(),\n ...notification\n })\n }\n }\n )\n })\n\n /* subscription web-push notification */\n const channel = new BroadcastChannel('notification')\n channel.addEventListener('message', async ({ data }) => {\n const { type } = data || {}\n\n switch (type) {\n case 'notificationclick':\n const { action, notification } = data\n\n // TODO how to follow action with notification.data\n notification.url && navigate(notification.url)\n break\n\n case 'show-reload-snackbar':\n notify({\n level: 'info',\n message: i18next.t('text.the application has been updated'),\n option: {\n action: {\n label: i18next.t('button.reload'),\n callback: () => window.location.reload()\n },\n timer: -1\n }\n })\n break\n\n case 'notification':\n default:\n onnotification()\n }\n })\n\n document.addEventListener('notify', (event: Event) => {\n const { message, level, detail, ex } = (event as CustomEvent).detail\n\n /* toast와 마찬가지로 오류만 이력에 남긴다. 성공·정보 토스트까지 쌓이면 목록이 작업 로그가 된다. */\n if (level === 'error') {\n onnotification({\n timestamp: Date.now(),\n type: 'ERROR',\n title: message,\n body: detail || '',\n ex: technicalText(ex)\n })\n }\n })\n\n await onnotification()\n}\n"]}
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../client/bootstrap.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\n\nimport { notify } from '@operato/layout'\nimport { auth } from '@things-factory/auth-base/dist-client/auth.js'\n\nimport { i18next } from '@operato/i18n'\nimport { navigate, notificationStore, store } from '@operato/shell'\nimport { subscribe as graphqlSubscribe } from '@operato/graphql'\n\nimport { subscribe, unsubscribe, UPDATE_NOTIFICATION } from './actions/notification-fcm'\nimport notification from './reducers/notification'\nimport { storeOnce } from './store-once'\n\nvar graphqlSubscription: any\n\n/** IndexedDB 알림 이력에는 Error 객체가 아닌, 언제나 저장 가능한 원문만 남긴다. */\nfunction technicalText(ex: any): string | undefined {\n if (!ex) return undefined\n if (typeof ex === 'string') return ex\n if (ex instanceof Error) return ex.stack || `${ex.name}: ${ex.message}`\n\n try {\n return JSON.stringify(ex, null, 2)\n } catch {\n return String(ex)\n }\n}\n\nexport default async function bootstrap() {\n /* initialize reducers */\n store.addReducers({ notification })\n\n /** `received` - a notice the server published, which every open tab receives alike, so it is kept once. */\n async function onnotification(notification?: any, received = false) {\n store.dispatch((async dispatch => {\n if (notification && received) {\n await storeOnce(notificationStore as any, notification)\n } else if (notification) {\n await notificationStore.add(notification)\n await notificationStore.limit()\n }\n\n const history = await notificationStore.getAll()\n\n dispatch({\n type: UPDATE_NOTIFICATION,\n history: [...history]\n })\n }) as any)\n }\n\n auth.on('presignout', async () => {\n try {\n await unsubscribe()\n /* await */ notificationStore.clear()\n } catch (err) {\n console.warn(err)\n }\n })\n\n auth.on('profile', async ({ credential }) => {\n try {\n await subscribe()\n } catch (err) {\n console.warn(err)\n }\n\n /*\n * subscription graphql notifications\n */\n var query = gql`\n subscription notification($subjects: [String!]) {\n notification(subjects: $subjects) {\n subject\n type\n title\n body\n url\n image\n timestamp\n }\n }\n `\n\n graphqlSubscription = await graphqlSubscribe(\n {\n query,\n variables: {\n subjects: ['info', 'warn', 'error', 'reload']\n }\n },\n {\n next({ data }) {\n const { notification } = data || {}\n onnotification(\n {\n timestamp: Date.now(),\n ...notification\n },\n true\n )\n }\n }\n )\n })\n\n /* subscription web-push notification */\n const channel = new BroadcastChannel('notification')\n channel.addEventListener('message', async ({ data }) => {\n const { type } = data || {}\n\n switch (type) {\n case 'notificationclick':\n const { action, notification } = data\n\n // TODO how to follow action with notification.data\n notification.url && navigate(notification.url)\n break\n\n case 'show-reload-snackbar':\n notify({\n level: 'info',\n message: i18next.t('text.the application has been updated'),\n option: {\n action: {\n label: i18next.t('button.reload'),\n callback: () => window.location.reload()\n },\n timer: -1\n }\n })\n break\n\n case 'notification':\n default:\n onnotification()\n }\n })\n\n document.addEventListener('notify', (event: Event) => {\n const { message, level, detail, ex } = (event as CustomEvent).detail\n\n /* toast와 마찬가지로 오류만 이력에 남긴다. 성공·정보 토스트까지 쌓이면 목록이 작업 로그가 된다. */\n if (level === 'error') {\n onnotification({\n timestamp: Date.now(),\n type: 'ERROR',\n title: message,\n body: detail || '',\n ex: technicalText(ex)\n })\n }\n })\n\n await onnotification()\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  export * from './viewparts/notification-badge';
2
2
  export * from './viewparts/notification-list';
3
3
  export * from './viewparts/notification-setting-let';
4
+ export * from './viewparts/notice-channel-setting-let';
4
5
  export * from './viewparts/notification-sender';
5
6
  export * from './actions/notification-fcm';
@@ -1,6 +1,7 @@
1
1
  export * from './viewparts/notification-badge';
2
2
  export * from './viewparts/notification-list';
3
3
  export * from './viewparts/notification-setting-let';
4
+ export * from './viewparts/notice-channel-setting-let';
4
5
  export * from './viewparts/notification-sender';
5
6
  export * from './actions/notification-fcm';
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,sCAAsC,CAAA;AACpD,cAAc,iCAAiC,CAAA;AAE/C,cAAc,4BAA4B,CAAA","sourcesContent":["export * from './viewparts/notification-badge'\nexport * from './viewparts/notification-list'\nexport * from './viewparts/notification-setting-let'\nexport * from './viewparts/notification-sender'\n\nexport * from './actions/notification-fcm'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAA;AAC9C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,sCAAsC,CAAA;AACpD,cAAc,wCAAwC,CAAA;AACtD,cAAc,iCAAiC,CAAA;AAE/C,cAAc,4BAA4B,CAAA","sourcesContent":["export * from './viewparts/notification-badge'\nexport * from './viewparts/notification-list'\nexport * from './viewparts/notification-setting-let'\nexport * from './viewparts/notice-channel-setting-let'\nexport * from './viewparts/notification-sender'\n\nexport * from './actions/notification-fcm'\n"]}
@@ -0,0 +1,14 @@
1
+ /** The fields every tab receives alike for one published notice - the server stamps its time once. */
2
+ export declare function sameNotice(a: any, b: any): boolean;
3
+ export type NoticeStore = {
4
+ getAll(): Promise<any[]>;
5
+ add(notice: any): Promise<unknown>;
6
+ limit(): Promise<unknown>;
7
+ };
8
+ /** The Web Locks API, or what a test hands in. Without it (an old browser) the tab keeps the notice as before. */
9
+ export type NoticeLocks = {
10
+ request<T>(name: string, callback: () => Promise<T>): Promise<T>;
11
+ };
12
+ export declare const NOTICE_STORE_LOCK = "notification-store";
13
+ /** Keeps `notice` unless a tab has kept it already. Answers whether this tab kept it. */
14
+ export declare function storeOnce(store: NoticeStore, notice: any, locks?: NoticeLocks | undefined): Promise<boolean>;
@@ -0,0 +1,32 @@
1
+ /*
2
+ * A notice is kept once in the inbox however many tabs of the app are open.
3
+ *
4
+ * Every open tab subscribes to the notices and all of them write the same IndexedDB store, so with three tabs a
5
+ * single notice was kept three times (seen on :4700, 2026-10-26: one subscription per page, three rows). The tabs
6
+ * now take turns on one lock, and a tab keeps the notice only when no tab has kept it yet.
7
+ */
8
+ /** The fields every tab receives alike for one published notice - the server stamps its time once. */
9
+ export function sameNotice(a, b) {
10
+ return (!!a &&
11
+ !!b &&
12
+ a.timestamp === b.timestamp &&
13
+ a.subject === b.subject &&
14
+ a.type === b.type &&
15
+ a.title === b.title &&
16
+ a.body === b.body &&
17
+ a.url === b.url);
18
+ }
19
+ export const NOTICE_STORE_LOCK = 'notification-store';
20
+ /** Keeps `notice` unless a tab has kept it already. Answers whether this tab kept it. */
21
+ export async function storeOnce(store, notice, locks = globalThis.navigator?.locks) {
22
+ const keep = async () => {
23
+ const kept = await store.getAll();
24
+ if (kept.some(one => sameNotice(one, notice)))
25
+ return false;
26
+ await store.add(notice);
27
+ await store.limit();
28
+ return true;
29
+ };
30
+ return locks ? await locks.request(NOTICE_STORE_LOCK, keep) : await keep();
31
+ }
32
+ //# sourceMappingURL=store-once.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-once.js","sourceRoot":"","sources":["../client/store-once.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,sGAAsG;AACtG,MAAM,UAAU,UAAU,CAAC,CAAM,EAAE,CAAM;IACvC,OAAO,CACL,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;QAC3B,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;QACvB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QACnB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAChB,CAAA;AACH,CAAC;AAWD,MAAM,CAAC,MAAM,iBAAiB,GAAG,oBAAoB,CAAA;AAErD,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAkB,EAClB,MAAW,EACX,QAAkC,UAAkB,CAAC,SAAS,EAAE,KAAK;IAErE,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAA;QACjC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;QAE3D,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACvB,MAAM,KAAK,CAAC,KAAK,EAAE,CAAA;QACnB,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAA;AAC5E,CAAC","sourcesContent":["/*\n * A notice is kept once in the inbox however many tabs of the app are open.\n *\n * Every open tab subscribes to the notices and all of them write the same IndexedDB store, so with three tabs a\n * single notice was kept three times (seen on :4700, 2026-10-26: one subscription per page, three rows). The tabs\n * now take turns on one lock, and a tab keeps the notice only when no tab has kept it yet.\n */\n\n/** The fields every tab receives alike for one published notice - the server stamps its time once. */\nexport function sameNotice(a: any, b: any): boolean {\n return (\n !!a &&\n !!b &&\n a.timestamp === b.timestamp &&\n a.subject === b.subject &&\n a.type === b.type &&\n a.title === b.title &&\n a.body === b.body &&\n a.url === b.url\n )\n}\n\nexport type NoticeStore = {\n getAll(): Promise<any[]>\n add(notice: any): Promise<unknown>\n limit(): Promise<unknown>\n}\n\n/** The Web Locks API, or what a test hands in. Without it (an old browser) the tab keeps the notice as before. */\nexport type NoticeLocks = { request<T>(name: string, callback: () => Promise<T>): Promise<T> }\n\nexport const NOTICE_STORE_LOCK = 'notification-store'\n\n/** Keeps `notice` unless a tab has kept it already. Answers whether this tab kept it. */\nexport async function storeOnce(\n store: NoticeStore,\n notice: any,\n locks: NoticeLocks | undefined = (globalThis as any).navigator?.locks\n): Promise<boolean> {\n const keep = async () => {\n const kept = await store.getAll()\n if (kept.some(one => sameNotice(one, notice))) return false\n\n await store.add(notice)\n await store.limit()\n return true\n }\n\n return locks ? await locks.request(NOTICE_STORE_LOCK, keep) : await keep()\n}\n"]}