@things-factory/notification 8.0.88 → 8.0.94

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.
@@ -15,10 +15,10 @@ export declare function sendNotification({ receivers, message }: {
15
15
  receivers: any;
16
16
  message: any;
17
17
  }): Promise<void>;
18
- export declare function register(user: any, { subscription }: {
18
+ export declare function register(domain: any, user: any, { subscription }: {
19
19
  subscription: any;
20
20
  }): Promise<boolean>;
21
- export declare function unregister(user: any, { subscription }: {
21
+ export declare function unregister(domain: any, user: any, { subscription }: {
22
22
  subscription: any;
23
23
  }): Promise<boolean>;
24
24
  export declare function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }: {
@@ -5,15 +5,22 @@ exports.sendNotification = sendNotification;
5
5
  exports.register = register;
6
6
  exports.unregister = unregister;
7
7
  exports.notify = notify;
8
- const tslib_1 = require("tslib");
9
- const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
8
+ const typeorm_1 = require("typeorm");
10
9
  const env_1 = require("@things-factory/env");
10
+ const shell_1 = require("@things-factory/shell");
11
11
  const app_1 = require("firebase-admin/app");
12
12
  const messaging_1 = require("firebase-admin/messaging");
13
+ const push_subscription_1 = require("../service/push-subscription/push-subscription");
13
14
  const notificationConfig = env_1.config.get('notification') || {};
14
15
  const { fcm, vapidKey } = notificationConfig;
15
- const { serviceAccount, appConfig, serverKey } = fcm || {};
16
+ const { serviceAccount, appConfig } = fcm || {};
16
17
  const { publicKey } = vapidKey || {};
18
+ // FCM 전송 시 만료/무효로 판정되어 DB에서 정리할 토큰 에러 코드
19
+ const INVALID_TOKEN_CODES = [
20
+ 'messaging/registration-token-not-registered',
21
+ 'messaging/invalid-registration-token',
22
+ 'messaging/invalid-argument'
23
+ ];
17
24
  var messaging;
18
25
  if (serviceAccount) {
19
26
  try {
@@ -47,95 +54,96 @@ async function sendNotification({ receivers, message }) {
47
54
  }
48
55
  notify(Object.assign({ receivers }, message));
49
56
  }
50
- async function register(user, { subscription }) {
51
- if (!messaging || !subscription || !user) {
57
+ // 푸시 구독 등록: FCM 토큰을 DB에 저장 (device-group 대체). (domain, token) 기준 upsert
58
+ async function register(domain, user, { subscription }) {
59
+ if (!messaging || !subscription || !user || !domain) {
52
60
  return false;
53
61
  }
54
62
  try {
55
- var notification_key = await getDeviceGroup(user.id);
56
- if (!notification_key) {
57
- await postDeviceGroup({
58
- operation: 'create',
59
- notification_key_name: user.id,
60
- registration_ids: [subscription]
61
- });
63
+ const repository = (0, shell_1.getRepository)(push_subscription_1.PushSubscription);
64
+ const existing = await repository.findOneBy({ domain: { id: domain.id }, token: subscription });
65
+ if (existing) {
66
+ // 재구독/기기 재할당: 소유 user만 갱신
67
+ await repository.save(Object.assign(Object.assign({}, existing), { user: { id: user.id }, updater: { id: user.id } }));
62
68
  }
63
69
  else {
64
- await postDeviceGroup({
65
- operation: 'add',
66
- notification_key_name: user.id,
67
- notification_key,
68
- registration_ids: [subscription]
70
+ await repository.save({
71
+ domain: { id: domain.id },
72
+ user: { id: user.id },
73
+ token: subscription,
74
+ creator: { id: user.id },
75
+ updater: { id: user.id }
69
76
  });
70
77
  }
71
78
  return true;
72
79
  }
73
80
  catch (err) {
74
- return;
81
+ env_1.logger.error('[FCM] register 실패', err);
82
+ return false;
75
83
  }
76
84
  }
77
- async function unregister(user, { subscription }) {
78
- if (!messaging || !subscription || !user) {
85
+ // 푸시 구독 해제: 해당 토큰 행을 하드 삭제
86
+ async function unregister(domain, user, { subscription }) {
87
+ if (!messaging || !subscription || !user || !domain) {
79
88
  return false;
80
89
  }
81
90
  try {
82
- var notification_key = await getDeviceGroup(user.id);
83
- if (notification_key) {
84
- await postDeviceGroup({
85
- operation: 'remove',
86
- notification_key_name: user.id,
87
- notification_key,
88
- registration_ids: [subscription]
89
- });
90
- }
91
+ await (0, shell_1.getRepository)(push_subscription_1.PushSubscription).delete({ domain: { id: domain.id }, token: subscription });
91
92
  return true;
92
93
  }
93
94
  catch (err) {
95
+ env_1.logger.error('[FCM] unregister 실패', err);
96
+ return false;
97
+ }
98
+ }
99
+ // 토큰 배열로 FCM HTTP v1 멀티캐스트 전송 + 무효 토큰 DB 정리
100
+ async function sendToTokens(tokens, notification, data) {
101
+ if (!tokens || tokens.length === 0) {
94
102
  return;
95
103
  }
104
+ // sendEachForMulticast 는 1회 최대 500토큰. 초과 시 청크 (실제 초과 도메인 생기면 대응)
105
+ const response = await messaging.sendEachForMulticast({ notification, data, tokens });
106
+ env_1.logger.info(`[FCM] sent ${response.successCount}/${tokens.length}, failed ${response.failureCount}`);
107
+ if (response.failureCount > 0) {
108
+ const deadTokens = [];
109
+ response.responses.forEach((resp, idx) => {
110
+ if (!resp.success) {
111
+ const code = resp.error && resp.error.code;
112
+ if (INVALID_TOKEN_CODES.includes(code)) {
113
+ deadTokens.push(tokens[idx]);
114
+ }
115
+ else {
116
+ // 일시적 오류 등 → 토큰 보존, 로그만
117
+ env_1.logger.error('[FCM] send failure (token kept):', code, resp.error && resp.error.message);
118
+ }
119
+ }
120
+ });
121
+ if (deadTokens.length > 0) {
122
+ await (0, shell_1.getRepository)(push_subscription_1.PushSubscription).delete({ token: (0, typeorm_1.In)(deadTokens) });
123
+ env_1.logger.warn(`[FCM] pruned ${deadTokens.length} invalid token(s)`);
124
+ }
125
+ }
126
+ return response;
96
127
  }
97
128
  async function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }) {
98
129
  if (!messaging) {
130
+ env_1.logger.error('[FCM] messaging 미초기화 → 전송 중단 (serviceAccount/FCM_PRIVATE_KEY 확인)');
99
131
  return;
100
132
  }
101
- // Caution: non-string attributes are not allowed in FCM payload validation
133
+ // Caution: non-string attributes are not allowed in FCM payload validation.
134
+ // image은 빈 문자열이면 HTTP v1이 "invalid URL"로 거부하므로 값이 있을 때만 포함
102
135
  var notification = {
103
136
  title: title || '',
104
- body: body || '',
105
- image: image || ''
137
+ body: body || ''
106
138
  };
139
+ if (image) {
140
+ notification.image = image;
141
+ }
142
+ // 1) 명시적 토큰 전송
107
143
  if (tokens && tokens instanceof Array && tokens.length > 0) {
108
- if (tokens.length > 1) {
109
- const response = await messaging.sendMulticast({
110
- notification,
111
- data,
112
- tokens
113
- });
114
- if (response.failureCount > 0) {
115
- const failedTokens = [];
116
- response.responses.forEach((resp, idx) => {
117
- if (!resp.success) {
118
- failedTokens.push(tokens[idx]);
119
- }
120
- });
121
- env_1.logger.error('List of tokens that caused failures: ' + failedTokens);
122
- }
123
- }
124
- else {
125
- const token = tokens[0];
126
- try {
127
- const response = await messaging.send({
128
- notification,
129
- data,
130
- token
131
- });
132
- env_1.logger.log('Successfully sent message:', response);
133
- }
134
- catch (err) {
135
- env_1.logger.error('Error sending message:', err);
136
- }
137
- }
144
+ await sendToTokens(tokens, notification, data);
138
145
  }
146
+ // 2) 토픽 전송
139
147
  if (topic) {
140
148
  try {
141
149
  const response = await messaging.send({
@@ -149,48 +157,18 @@ async function notify({ receivers, privileges, tokens, topic, title, body, data,
149
157
  env_1.logger.error('Error sending message:', err);
150
158
  }
151
159
  }
152
- if (receivers && receivers instanceof Array) {
153
- for (let receiver of receivers) {
154
- const notification_key = await getDeviceGroup(receiver);
155
- if (notification_key) {
156
- await messaging.sendToDeviceGroup(notification_key, {
157
- notification,
158
- data
159
- });
160
- }
160
+ // 3) 수신자(user.id 배열) 저장된 구독 토큰 조회 후 전송
161
+ if (receivers && receivers instanceof Array && receivers.length > 0) {
162
+ const rows = await (0, shell_1.getRepository)(push_subscription_1.PushSubscription).find({
163
+ where: { user: { id: (0, typeorm_1.In)(receivers) } }
164
+ });
165
+ const receiverTokens = [...new Set(rows.map(r => r.token))];
166
+ if (receiverTokens.length === 0) {
167
+ env_1.logger.warn('[FCM] no push subscriptions for receivers:', JSON.stringify(receivers));
161
168
  }
162
- }
163
- }
164
- async function postDeviceGroup(body) {
165
- var response = await (0, node_fetch_1.default)('https://fcm.googleapis.com/fcm/notification', {
166
- method: 'POST',
167
- headers: {
168
- 'Content-Type': 'application/json',
169
- Authorization: `key=${serverKey}`,
170
- project_id: appConfig.messagingSenderId
171
- },
172
- body: JSON.stringify(body)
173
- });
174
- if (response.ok) {
175
- const { notification_key } = await response.json();
176
- return notification_key;
177
- }
178
- else {
179
- console.error('postDeviceGroup-notok', response.status, await response.text());
180
- }
181
- }
182
- async function getDeviceGroup(notificationKeyName) {
183
- const response = await (0, node_fetch_1.default)(`https://fcm.googleapis.com/fcm/notification?notification_key_name=${notificationKeyName}`, {
184
- method: 'GET',
185
- headers: {
186
- 'Content-Type': 'application/json',
187
- Authorization: `key=${serverKey}`,
188
- project_id: appConfig.messagingSenderId
169
+ else {
170
+ await sendToTokens(receiverTokens, notification, data);
189
171
  }
190
- });
191
- if (response.ok) {
192
- const { notification_key } = await response.json();
193
- return notification_key;
194
172
  }
195
173
  }
196
174
  //# sourceMappingURL=fcm.js.map
@@ -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 { In } from 'typeorm'\n\nimport { config, logger } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\n\nimport { initializeApp, cert } from 'firebase-admin/app'\nimport { getMessaging } from 'firebase-admin/messaging'\n\nimport { PushSubscription } from '../service/push-subscription/push-subscription'\n\nconst notificationConfig = config.get('notification') || {}\nconst { fcm, vapidKey } = notificationConfig\nconst { serviceAccount, appConfig } = fcm || {}\nconst { publicKey } = vapidKey || {}\n\n// FCM 전송 시 만료/무효로 판정되어 DB에서 정리할 토큰 에러 코드\nconst INVALID_TOKEN_CODES = [\n 'messaging/registration-token-not-registered',\n 'messaging/invalid-registration-token',\n 'messaging/invalid-argument'\n]\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\n// 푸시 구독 등록: FCM 토큰을 DB에 저장 (device-group 대체). (domain, token) 기준 upsert\nexport async function register(domain, user, { subscription }) {\n if (!messaging || !subscription || !user || !domain) {\n return false\n }\n\n try {\n const repository = getRepository(PushSubscription)\n const existing = await repository.findOneBy({ domain: { id: domain.id }, token: subscription })\n\n if (existing) {\n // 재구독/기기 재할당: 소유 user만 갱신\n await repository.save({ ...existing, user: { id: user.id }, updater: { id: user.id } })\n } else {\n await repository.save({\n domain: { id: domain.id },\n user: { id: user.id },\n token: subscription,\n creator: { id: user.id },\n updater: { id: user.id }\n })\n }\n\n return true\n } catch (err) {\n logger.error('[FCM] register 실패', err)\n return false\n }\n}\n\n// 푸시 구독 해제: 해당 토큰 행을 하드 삭제\nexport async function unregister(domain, user, { subscription }) {\n if (!messaging || !subscription || !user || !domain) {\n return false\n }\n\n try {\n await getRepository(PushSubscription).delete({ domain: { id: domain.id }, token: subscription })\n return true\n } catch (err) {\n logger.error('[FCM] unregister 실패', err)\n return false\n }\n}\n\n// 토큰 배열로 FCM HTTP v1 멀티캐스트 전송 + 무효 토큰 DB 정리\nasync function sendToTokens(tokens, notification, data) {\n if (!tokens || tokens.length === 0) {\n return\n }\n\n // sendEachForMulticast 는 1회 최대 500토큰. 초과 시 청크 (실제 초과 도메인 생기면 대응)\n const response = await messaging.sendEachForMulticast({ notification, data, tokens })\n logger.info(`[FCM] sent ${response.successCount}/${tokens.length}, failed ${response.failureCount}`)\n\n if (response.failureCount > 0) {\n const deadTokens = []\n response.responses.forEach((resp, idx) => {\n if (!resp.success) {\n const code = resp.error && resp.error.code\n if (INVALID_TOKEN_CODES.includes(code)) {\n deadTokens.push(tokens[idx])\n } else {\n // 일시적 오류 등 → 토큰 보존, 로그만\n logger.error('[FCM] send failure (token kept):', code, resp.error && resp.error.message)\n }\n }\n })\n\n if (deadTokens.length > 0) {\n await getRepository(PushSubscription).delete({ token: In(deadTokens) })\n logger.warn(`[FCM] pruned ${deadTokens.length} invalid token(s)`)\n }\n }\n\n return response\n}\n\nexport async function notify({ receivers, privileges, tokens, topic, title, body, data, image, actions }) {\n if (!messaging) {\n logger.error('[FCM] messaging 미초기화 → 전송 중단 (serviceAccount/FCM_PRIVATE_KEY 확인)')\n return\n }\n\n // Caution: non-string attributes are not allowed in FCM payload validation.\n // image은 빈 문자열이면 HTTP v1이 \"invalid URL\"로 거부하므로 값이 있을 때만 포함\n var notification: { title: string; body: string; image?: string } = {\n title: title || '',\n body: body || ''\n }\n if (image) {\n notification.image = image\n }\n\n // 1) 명시적 토큰 전송\n if (tokens && tokens instanceof Array && tokens.length > 0) {\n await sendToTokens(tokens, notification, data)\n }\n\n // 2) 토픽 전송\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 // 3) 수신자(user.id 배열) → 저장된 구독 토큰 조회 후 전송\n if (receivers && receivers instanceof Array && receivers.length > 0) {\n const rows = await getRepository(PushSubscription).find({\n where: { user: { id: In(receivers) } }\n })\n const receiverTokens = [...new Set(rows.map(r => r.token))]\n\n if (receiverTokens.length === 0) {\n logger.warn('[FCM] no push subscriptions for receivers:', JSON.stringify(receivers))\n } else {\n await sendToTokens(receiverTokens, notification, data)\n }\n }\n}\n"]}
@@ -17,15 +17,15 @@ exports.notificationRouter.get('/config', async (context, next) => {
17
17
  context.body = config;
18
18
  });
19
19
  exports.notificationRouter.post('/register', async (context, next) => {
20
- const { user } = context.state;
20
+ const { user, domain } = context.state;
21
21
  const { body } = context.request;
22
- context.body = await (0, fcm_1.register)(user, body);
22
+ context.body = await (0, fcm_1.register)(domain, user, body);
23
23
  context.status = 200;
24
24
  });
25
25
  exports.notificationRouter.post('/unregister', async (context, next) => {
26
- const { user } = context.state;
26
+ const { user, domain } = context.state;
27
27
  const { body } = context.request;
28
- context.body = await (0, fcm_1.unregister)(user, body);
28
+ context.body = await (0, fcm_1.unregister)(domain, user, body);
29
29
  context.status = 200;
30
30
  });
31
31
  exports.notificationRouter.post('/notify', async (context, next) => {
@@ -1 +1 @@
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'\nimport { ILike } from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getConfig, register, unregister } from '../controllers/fcm'\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\nnotificationRouter.post('/notify', async (context, next) => {\n const { user, domain, notify } = context.state\n const repository = getRepository(User)\n\n var { receivers, ...options } = context.request.body\n\n debug('post:/notify', receivers, receivers.split(','), options)\n\n // TODO filter only users having current domain privilege\n receivers = (\n await Promise.all(\n receivers\n .split(',')\n .map(email => email.trim())\n .map(async email => {\n var receiver: User = await repository.findOneBy({ email: ILike(email) })\n return receiver && receiver.id\n })\n )\n ).filter(receiver => !!receiver)\n\n debug('post:/notify', receivers, user.id)\n\n await notify({\n receivers: receivers.length > 0 ? receivers : [user.id],\n ...options\n })\n\n context.body = {\n success: true\n }\n})\n"]}
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'\nimport { ILike } from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getConfig, register, unregister } from '../controllers/fcm'\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, domain } = context.state\n const { body } = context.request\n context.body = await register(domain, user, body)\n context.status = 200\n})\n\nnotificationRouter.post('/unregister', async (context, next) => {\n const { user, domain } = context.state\n const { body } = context.request\n context.body = await unregister(domain, user, body)\n context.status = 200\n})\n\nnotificationRouter.post('/notify', async (context, next) => {\n const { user, domain, notify } = context.state\n const repository = getRepository(User)\n\n var { receivers, ...options } = context.request.body\n\n debug('post:/notify', receivers, receivers.split(','), options)\n\n // TODO filter only users having current domain privilege\n receivers = (\n await Promise.all(\n receivers\n .split(',')\n .map(email => email.trim())\n .map(async email => {\n var receiver: User = await repository.findOneBy({ email: ILike(email) })\n return receiver && receiver.id\n })\n )\n ).filter(receiver => !!receiver)\n\n debug('post:/notify', receivers, user.id)\n\n await notify({\n receivers: receivers.length > 0 ? receivers : [user.id],\n ...options\n })\n\n context.body = {\n success: true\n }\n})\n"]}
@@ -1,6 +1,7 @@
1
1
  export * from './notification-rule/notification-rule';
2
2
  export * from './notification/notification';
3
- export declare const entities: (typeof import("./notification-rule/notification-rule").NotificationRule | typeof import("./notification/notification").Notification)[];
3
+ export * from './push-subscription/push-subscription';
4
+ export declare const entities: (typeof import("./notification-rule/notification-rule").NotificationRule | typeof import("./notification/notification").Notification | typeof import("./push-subscription/push-subscription").PushSubscription)[];
4
5
  export declare const subscribers: typeof import("./notification-rule/event-subscriber").NotificationRuleHistoryEntitySubscriber[];
5
6
  export declare const schema: {
6
7
  typeDefs: {
@@ -5,13 +5,16 @@ const tslib_1 = require("tslib");
5
5
  /* EXPORT ENTITY TYPES */
6
6
  tslib_1.__exportStar(require("./notification-rule/notification-rule"), exports);
7
7
  tslib_1.__exportStar(require("./notification/notification"), exports);
8
+ tslib_1.__exportStar(require("./push-subscription/push-subscription"), exports);
8
9
  /* IMPORT ENTITIES AND RESOLVERS */
9
10
  const notification_rule_1 = require("./notification-rule");
10
11
  const notification_1 = require("./notification");
12
+ const push_subscription_1 = require("./push-subscription");
11
13
  exports.entities = [
12
14
  /* ENTITIES */
13
15
  ...notification_rule_1.entities,
14
- ...notification_1.entities
16
+ ...notification_1.entities,
17
+ ...push_subscription_1.entities
15
18
  ];
16
19
  exports.subscribers = [
17
20
  /* SUBSCRIBERS */
@@ -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;AAEV,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,oBACH,uBAAoB,CACxB;IACD,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,6BAAyB;QAC5B,GAAG,wBAAqB;QACxB,GAAG,wBAAqB;KACzB;IACD,UAAU,oBACL,yBAAsB,CAC1B;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'\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 ],\n directives: {\n ...NotificationDirectives\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,gFAAqD;AACrD,sEAA2C;AAC3C,gFAAqD;AAErD,mCAAmC;AACnC,2DAI4B;AAC5B,iDAKuB;AACvB,2DAA0E;AAE7D,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,4BAAwB;IAC3B,GAAG,uBAAoB;IACvB,GAAG,4BAAwB;CAC5B,CAAA;AAEY,QAAA,WAAW,GAAG;IACzB,iBAAiB;IACjB,GAAG,+BAA2B;CAC/B,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,QAAQ,oBACH,uBAAoB,CACxB;IACD,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,6BAAyB;QAC5B,GAAG,wBAAqB;QACxB,GAAG,wBAAqB;KACzB;IACD,UAAU,oBACL,yBAAsB,CAC1B;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './notification-rule/notification-rule'\nexport * from './notification/notification'\nexport * from './push-subscription/push-subscription'\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 { entities as PushSubscriptionEntities } from './push-subscription'\n\nexport const entities = [\n /* ENTITIES */\n ...NotificationRuleEntities,\n ...NotificationEntities,\n ...PushSubscriptionEntities\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 ],\n directives: {\n ...NotificationDirectives\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import { PushSubscription } from './push-subscription';
2
+ export declare const entities: (typeof PushSubscription)[];
3
+ export declare const resolvers: any[];
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvers = exports.entities = void 0;
4
+ const push_subscription_1 = require("./push-subscription");
5
+ exports.entities = [push_subscription_1.PushSubscription];
6
+ exports.resolvers = [];
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/service/push-subscription/index.ts"],"names":[],"mappings":";;;AAAA,2DAAsD;AAEzC,QAAA,QAAQ,GAAG,CAAC,oCAAgB,CAAC,CAAA;AAC7B,QAAA,SAAS,GAAG,EAAE,CAAA","sourcesContent":["import { PushSubscription } from './push-subscription'\n\nexport const entities = [PushSubscription]\nexport const resolvers = []\n"]}
@@ -0,0 +1,17 @@
1
+ import { Domain } from '@things-factory/shell';
2
+ import { User } from '@things-factory/auth-base';
3
+ export declare class PushSubscription {
4
+ readonly id: string;
5
+ domain?: Domain;
6
+ domainId?: string;
7
+ user?: User;
8
+ userId?: string;
9
+ token: string;
10
+ createdAt?: Date;
11
+ updatedAt?: Date;
12
+ deletedAt?: Date;
13
+ creator?: User;
14
+ creatorId?: string;
15
+ updater?: User;
16
+ updaterId?: string;
17
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PushSubscription = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const typeorm_1 = require("typeorm");
6
+ const type_graphql_1 = require("type-graphql");
7
+ const shell_1 = require("@things-factory/shell");
8
+ const auth_base_1 = require("@things-factory/auth-base");
9
+ // FCM 웹 푸시 구독 토큰 저장 엔티티 (레거시 device-group 대체, HTTP v1 토큰 직접 전송용)
10
+ let PushSubscription = class PushSubscription {
11
+ };
12
+ exports.PushSubscription = PushSubscription;
13
+ tslib_1.__decorate([
14
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
15
+ (0, type_graphql_1.Field)(type => type_graphql_1.ID),
16
+ tslib_1.__metadata("design:type", String)
17
+ ], PushSubscription.prototype, "id", void 0);
18
+ tslib_1.__decorate([
19
+ (0, typeorm_1.ManyToOne)(type => shell_1.Domain),
20
+ (0, type_graphql_1.Field)(type => shell_1.Domain),
21
+ tslib_1.__metadata("design:type", shell_1.Domain)
22
+ ], PushSubscription.prototype, "domain", void 0);
23
+ tslib_1.__decorate([
24
+ (0, typeorm_1.RelationId)((ps) => ps.domain),
25
+ tslib_1.__metadata("design:type", String)
26
+ ], PushSubscription.prototype, "domainId", void 0);
27
+ tslib_1.__decorate([
28
+ (0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
29
+ (0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
30
+ tslib_1.__metadata("design:type", auth_base_1.User)
31
+ ], PushSubscription.prototype, "user", void 0);
32
+ tslib_1.__decorate([
33
+ (0, typeorm_1.RelationId)((ps) => ps.user),
34
+ tslib_1.__metadata("design:type", String)
35
+ ], PushSubscription.prototype, "userId", void 0);
36
+ tslib_1.__decorate([
37
+ (0, typeorm_1.Column)(),
38
+ (0, type_graphql_1.Field)(),
39
+ tslib_1.__metadata("design:type", String)
40
+ ], PushSubscription.prototype, "token", void 0);
41
+ tslib_1.__decorate([
42
+ (0, typeorm_1.CreateDateColumn)(),
43
+ (0, type_graphql_1.Field)({ nullable: true }),
44
+ tslib_1.__metadata("design:type", Date)
45
+ ], PushSubscription.prototype, "createdAt", void 0);
46
+ tslib_1.__decorate([
47
+ (0, typeorm_1.UpdateDateColumn)(),
48
+ (0, type_graphql_1.Field)({ nullable: true }),
49
+ tslib_1.__metadata("design:type", Date)
50
+ ], PushSubscription.prototype, "updatedAt", void 0);
51
+ tslib_1.__decorate([
52
+ (0, typeorm_1.DeleteDateColumn)(),
53
+ (0, type_graphql_1.Field)({ nullable: true }),
54
+ tslib_1.__metadata("design:type", Date)
55
+ ], PushSubscription.prototype, "deletedAt", void 0);
56
+ tslib_1.__decorate([
57
+ (0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
58
+ (0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
59
+ tslib_1.__metadata("design:type", auth_base_1.User)
60
+ ], PushSubscription.prototype, "creator", void 0);
61
+ tslib_1.__decorate([
62
+ (0, typeorm_1.RelationId)((ps) => ps.creator),
63
+ tslib_1.__metadata("design:type", String)
64
+ ], PushSubscription.prototype, "creatorId", void 0);
65
+ tslib_1.__decorate([
66
+ (0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
67
+ (0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
68
+ tslib_1.__metadata("design:type", auth_base_1.User)
69
+ ], PushSubscription.prototype, "updater", void 0);
70
+ tslib_1.__decorate([
71
+ (0, typeorm_1.RelationId)((ps) => ps.updater),
72
+ tslib_1.__metadata("design:type", String)
73
+ ], PushSubscription.prototype, "updaterId", void 0);
74
+ exports.PushSubscription = PushSubscription = tslib_1.__decorate([
75
+ (0, typeorm_1.Entity)(),
76
+ (0, typeorm_1.Index)('ix_push_subscription_0', (ps) => [ps.domain, ps.token], { unique: true }),
77
+ (0, typeorm_1.Index)('ix_push_subscription_1', (ps) => [ps.user], { unique: false }),
78
+ (0, type_graphql_1.ObjectType)({ description: 'Entity for PushSubscription (FCM registration token)' })
79
+ ], PushSubscription);
80
+ //# sourceMappingURL=push-subscription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-subscription.js","sourceRoot":"","sources":["../../../server/service/push-subscription/push-subscription.ts"],"names":[],"mappings":";;;;AAAA,qCAUgB;AAChB,+CAAoD;AAEpD,iDAA8C;AAC9C,yDAAgD;AAEhD,iEAAiE;AAK1D,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;CAgD5B,CAAA;AAhDY,4CAAgB;AAGlB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,CAAC;;4CACC;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;sCACb,cAAM;gDAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC;;kDAC/B;AAIjB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACjC,gBAAI;8CAAA;AAGX;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC;;gDAC/B;AAIf;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,GAAE;;+CACK;AAIb;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;mDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;mDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCACd,IAAI;mDAAA;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;iDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC;;mDAC/B;AAIlB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;sCAC9B,gBAAI;iDAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,EAAoB,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC;;mDAC/B;2BA/CP,gBAAgB;IAJ5B,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,wBAAwB,EAAE,CAAC,EAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAClG,IAAA,eAAK,EAAC,wBAAwB,EAAE,CAAC,EAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACvF,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,sDAAsD,EAAE,CAAC;GACvE,gBAAgB,CAgD5B","sourcesContent":["import {\n CreateDateColumn,\n UpdateDateColumn,\n DeleteDateColumn,\n Entity,\n Index,\n Column,\n RelationId,\n ManyToOne,\n PrimaryGeneratedColumn\n} from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n// FCM 웹 푸시 구독 토큰 저장 엔티티 (레거시 device-group 대체, HTTP v1 토큰 직접 전송용)\n@Entity()\n@Index('ix_push_subscription_0', (ps: PushSubscription) => [ps.domain, ps.token], { unique: true })\n@Index('ix_push_subscription_1', (ps: PushSubscription) => [ps.user], { unique: false })\n@ObjectType({ description: 'Entity for PushSubscription (FCM registration token)' })\nexport class PushSubscription {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID)\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain)\n domain?: Domain\n\n @RelationId((ps: PushSubscription) => ps.domain)\n domainId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n user?: User\n\n @RelationId((ps: PushSubscription) => ps.user)\n userId?: string\n\n @Column()\n @Field()\n token: string // FCM registration token\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((ps: PushSubscription) => ps.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true })\n updater?: User\n\n @RelationId((ps: PushSubscription) => ps.updater)\n updaterId?: string\n}\n"]}