@abtnode/blocklet-services 1.16.39-beta-20250215-130252-43410928 → 1.16.39-beta-20250218-110004-a308c501

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 (132) hide show
  1. package/api/index.js +6 -2
  2. package/api/libs/email.js +2 -2
  3. package/api/services/notification/index.js +4 -2
  4. package/api/services/notification/notification-queue.js +671 -0
  5. package/api/socket/channel/did.js +146 -473
  6. package/dist/assets/{Add-TglZpKpw.js → Add-eTw9dWVa.js} +1 -1
  7. package/dist/assets/{ArrowDropDown-DCM27ed6.js → ArrowDropDown-D-RD-2MB.js} +1 -1
  8. package/dist/assets/{CheckCircle-CH8SYy4d.js → CheckCircle-fnIa_Tjy.js} +1 -1
  9. package/dist/assets/{ChevronLeft-B7m8AFzR.js → ChevronLeft-B6BBtJvf.js} +1 -1
  10. package/dist/assets/{ChevronRight-DeRklga5.js → ChevronRight-BO6iBxGF.js} +1 -1
  11. package/dist/assets/{DeleteOutline-DZA1YKE4.js → DeleteOutline-6B6Fe5fo.js} +1 -1
  12. package/dist/assets/{Done-CVdTMvGH.js → Done-BpK9makc.js} +1 -1
  13. package/dist/assets/{Download-DECMRaRC.js → Download-CHVAgVy8.js} +1 -1
  14. package/dist/assets/{Error-B61ZB0Rr.js → Error-kGbi4Zxj.js} +1 -1
  15. package/dist/assets/{Google-D_UbrpMW.js → Google-T-9fe6qI.js} +1 -1
  16. package/dist/assets/{InfoOutlined-QBw5AmWP.js → InfoOutlined-BgLmQpjA.js} +1 -1
  17. package/dist/assets/{Launch-Cpp3LVnV.js → Launch-C9zEVLvk.js} +1 -1
  18. package/dist/assets/{LaunchOutlined-WdAKKV2H.js → LaunchOutlined-Cql1NmZZ.js} +1 -1
  19. package/dist/assets/{Loop-CbPhaPlF.js → Loop-CawJEzLG.js} +1 -1
  20. package/dist/assets/{MoreHoriz-BsTY94ce.js → MoreHoriz-M9rI-Gh1.js} +1 -1
  21. package/dist/assets/{PlayArrow-BgVzxA9Q.js → PlayArrow-BAowUCpY.js} +1 -1
  22. package/dist/assets/{ViewList-DBUe2NSG.js → ViewList-BPtSTNst.js} +1 -1
  23. package/dist/assets/{access-control-DeqFMOm5.js → access-control-DEUU0fDz.js} +1 -1
  24. package/dist/assets/{actions-DP_Rm54e.js → actions-wYAFmtYW.js} +1 -1
  25. package/dist/assets/{add-component-core-BnP0_g-C.js → add-component-core-C36Oh0S_.js} +1 -1
  26. package/dist/assets/{add-resource-B26HHRU4.js → add-resource-Xm48tGBR.js} +1 -1
  27. package/dist/assets/{addon-DH34oI6i.js → addon-DJRfOF39.js} +1 -1
  28. package/dist/assets/{analytics-DPn53GmC.js → analytics-BxMLe4sZ.js} +1 -1
  29. package/dist/assets/{ar-BJD4HTM3.js → ar-BFUPmTkR.js} +1 -1
  30. package/dist/assets/{audit-logs-C1oUmr2S.js → audit-logs-QI7Dc290.js} +1 -1
  31. package/dist/assets/{base32-CwwDjweV.js → base32-BMSr-Oor.js} +1 -1
  32. package/dist/assets/{branding-DFfBkuOI.js → branding-BiBArxE7.js} +2 -2
  33. package/dist/assets/{bundle-avatar-Cv2R2iWI.js → bundle-avatar-CcKbW8ZA.js} +1 -1
  34. package/dist/assets/{button-4qfNeG4u.js → button-hN0nkg5h.js} +1 -1
  35. package/dist/assets/{click-to-copy-886R1sA9.js → click-to-copy-DlRwVMBE.js} +1 -1
  36. package/dist/assets/{complete-Cz6Vt9yr.js → complete-BCHhjgui.js} +2 -2
  37. package/dist/assets/{component-BOGOOyWw.js → component---BohhtR.js} +1 -1
  38. package/dist/assets/{config-BBRmjpUL.js → config-BT7DA9eX.js} +1 -1
  39. package/dist/assets/{config-CE9eJ5Ce.js → config-Chtg5c7S.js} +1 -1
  40. package/dist/assets/{config-navigation-ZGyHDU_V.js → config-navigation-DGQXESo8.js} +3 -3
  41. package/dist/assets/config-space-D3_ddjoK.js +1 -0
  42. package/dist/assets/{confirm-f60NfF3X.js → confirm-BUf2FX6y.js} +1 -1
  43. package/dist/assets/{connect-D9S16wyT.js → connect-9ktVrhs9.js} +1 -1
  44. package/dist/assets/{connect-B2VVyCQY.js → connect-Bg1fiX7Q.js} +1 -1
  45. package/dist/assets/{connect-to-BSmtnPV9.js → connect-to-6-yxL1od.js} +1 -1
  46. package/dist/assets/{dashboard-BoV1-CrH.js → dashboard-CRO3eTt1.js} +4 -4
  47. package/dist/assets/{de-CaPBInxb.js → de-hV0yD3gq.js} +1 -1
  48. package/dist/assets/{did-address-CgDvtQbT.js → did-address-Rncailh0.js} +1 -1
  49. package/dist/assets/{domain-DLGsAByD.js → domain-CiQ58Nbb.js} +1 -1
  50. package/dist/assets/{domain-list-BLV11Y6_.js → domain-list-DlMnYtcT.js} +2 -2
  51. package/dist/assets/{email-Fgr58FLV.js → email-C7B2iQRL.js} +1 -1
  52. package/dist/assets/{es-hhXhgxGX.js → es-BcSAobJW.js} +1 -1
  53. package/dist/assets/{exchange-passport-CcsLEfpo.js → exchange-passport-DvgnrFhH.js} +1 -1
  54. package/dist/assets/{form-text-input-CbMAGh7u.js → form-text-input-B3XB3R1O.js} +1 -1
  55. package/dist/assets/{fr-BwQvAhhM.js → fr-8qti32p1.js} +1 -1
  56. package/dist/assets/{fuel-B8r20ueU.js → fuel-De_bc9rw.js} +1 -1
  57. package/dist/assets/{fullpage-DLk_Ap1u.js → fullpage-ClwEgpoB.js} +1 -1
  58. package/dist/assets/{get-safe-url-BzE2b8Zw.js → get-safe-url-3FtlbQXR.js} +1 -1
  59. package/dist/assets/{get-safe-url-C2hTfmjV.js → get-safe-url-DgnUmHPg.js} +1 -1
  60. package/dist/assets/{hi-CRWmR5oy.js → hi-DDEUhQcX.js} +1 -1
  61. package/dist/assets/{home-B30MMEWW.js → home-s4XxyP6G.js} +1 -1
  62. package/dist/assets/{id-Dlr6Q1sk.js → id-DkUYINLx.js} +1 -1
  63. package/dist/assets/{iframe-DvysnY8O.js → iframe-DkAOGH82.js} +1 -1
  64. package/dist/assets/{index-r2Lf0YIw.js → index-AKILhCB1.js} +46 -46
  65. package/dist/assets/{index-CM-Ew9KZ.js → index-BM6MW3P4.js} +1 -1
  66. package/dist/assets/{index-C-yw0A3M.js → index-BQD1ktrC.js} +1 -1
  67. package/dist/assets/{index-V7v6XM-s.js → index-BuAHCrPZ.js} +1 -1
  68. package/dist/assets/{index-EDdyNc1F.js → index-C3Z6Hby2.js} +1 -1
  69. package/dist/assets/{index-j5s7Rpz_.js → index-C3cP3eho.js} +1 -1
  70. package/dist/assets/{index-B4_ve2gt.js → index-CKmE0Sqg.js} +1 -1
  71. package/dist/assets/index-CctdI8X8.js +125 -0
  72. package/dist/assets/{index-EIsfexf1.js → index-DEkiPG8Y.js} +1 -1
  73. package/dist/assets/{index-uSZNIR4c.js → index-DFzh5oL0.js} +1 -1
  74. package/dist/assets/index-DiLNptzT.js +5 -0
  75. package/dist/assets/{index-DFM7u7wl.js → index-IC1Rf7BW.js} +1 -1
  76. package/dist/assets/{index-B6CyLMos.js → index-Pdt8JY75.js} +1 -1
  77. package/dist/assets/{index-RoF9rzF3.js → index-XjSUrEf9.js} +1 -1
  78. package/dist/assets/{index-U8kTDDEm.js → index-vSb5bw1I.js} +4 -4
  79. package/dist/assets/{invitation-ZW3n52Yc.js → invitation-398ugOww.js} +1 -1
  80. package/dist/assets/{invite-B8cpjqrT.js → invite-CxctjCks.js} +1 -1
  81. package/dist/assets/{issue-passport-DrbTLcdD.js → issue-passport-BzEXCFP0.js} +1 -1
  82. package/dist/assets/{item-BVrq9ZQD.js → item-BzigISWG.js} +1 -1
  83. package/dist/assets/{ja-CmKylH6K.js → ja-C2LP4WQG.js} +1 -1
  84. package/dist/assets/{ko-Blp8936a.js → ko-C-DMeONa.js} +1 -1
  85. package/dist/assets/{layout-Dw2Qx7hs.js → layout-N5xbN7Ti.js} +1 -1
  86. package/dist/assets/{list-BwH5q_Zc.js → list-QwzSO7Cs.js} +1 -1
  87. package/dist/assets/{list-header-BBHpD8dC.js → list-header-bKaZ4zsu.js} +1 -1
  88. package/dist/assets/{localization-Ciwo-y3M.js → localization-CNvFSQc0.js} +1 -1
  89. package/dist/assets/{log-Ba7iTEJj.js → log-Bz9TkxcV.js} +1 -1
  90. package/dist/assets/{login-BewdlPwi.js → login-DbJD9Sgu.js} +1 -1
  91. package/dist/assets/{login-oauth-callback-D_bP2yYx.js → login-oauth-callback-BagCPt1j.js} +1 -1
  92. package/dist/assets/{logo-uploader-CneV6CnS.js → logo-uploader-QnBnkpOO.js} +3 -3
  93. package/dist/assets/{lost-passport-CNuZ281r.js → lost-passport-se4N22vE.js} +1 -1
  94. package/dist/assets/{open-window-Q0PVNWdu.js → open-window-BWPlSJZK.js} +1 -1
  95. package/dist/assets/{overview-giDiT6rf.js → overview-u84fbCZU.js} +1 -1
  96. package/dist/assets/{page-header-BDwGXuEW.js → page-header-CVCtfFXY.js} +1 -1
  97. package/dist/assets/{permission-Cqed8iP-.js → permission-CoRqvd18.js} +1 -1
  98. package/dist/assets/{preferences-CBm0eML7.js → preferences-CCm3JuB9.js} +1 -1
  99. package/dist/assets/{pt-BbwAvpfn.js → pt-CbxvF__Z.js} +1 -1
  100. package/dist/assets/publish-resource-Cmlpn7H3.js +1 -0
  101. package/dist/assets/{react-beautiful-dnd.esm-BTWrOgJp.js → react-beautiful-dnd.esm-CrvlS-3X.js} +1 -1
  102. package/dist/assets/{relative-time-Bt6RtG4o.js → relative-time-IDiRzYUd.js} +1 -1
  103. package/dist/assets/{ru-DgfLeWul.js → ru-DLHrAeE7.js} +1 -1
  104. package/dist/assets/sdk-DRntvOGy.js +1 -0
  105. package/dist/assets/{session-BKdZFtFw.js → session-BzY_i5lZ.js} +1 -1
  106. package/dist/assets/{setup-BdFkCCIb.js → setup-CFL-k7o4.js} +3 -3
  107. package/dist/assets/{start-Cy0MNYb6.js → start-Bnol7r-4.js} +1 -1
  108. package/dist/assets/{step-actions-CPEQpBG3.js → step-actions-vFuOlgmX.js} +1 -1
  109. package/dist/assets/{studio-BzK2t7MU.js → studio-D8dNrjXX.js} +1 -1
  110. package/dist/assets/{switch-control-BSKk9hbF.js → switch-control-DTOAJIEb.js} +1 -1
  111. package/dist/assets/{th-DXRi_ppC.js → th-Dil9kwjH.js} +1 -1
  112. package/dist/assets/{traffic-BQRcq8gg.js → traffic-C-Or__6J.js} +1 -1
  113. package/dist/assets/{transfer-DhNfOwE1.js → transfer-tWhnvLV8.js} +1 -1
  114. package/dist/assets/{unsubscribe-C0PztwA5.js → unsubscribe-8PpNeEGq.js} +1 -1
  115. package/dist/assets/{useLocalStorage-Bx8dJwZw.js → useLocalStorage-DDsWbihG.js} +1 -1
  116. package/dist/assets/{user-center-BshF32Jc.js → user-center-FVVJclAc.js} +1 -1
  117. package/dist/assets/{util-BPubgk5p.js → util-CqwLA_zk.js} +1 -1
  118. package/dist/assets/{util-CWVLbRLp.js → util-DfAqJble.js} +1 -1
  119. package/dist/assets/{vendor-arcblock-HzAExiTd.js → vendor-arcblock-naCuSDgJ.js} +5 -5
  120. package/dist/assets/{vi-nO7JrO24.js → vi-B46hyfUM.js} +1 -1
  121. package/dist/assets/wrap-locale-JORvcC3E.js +1 -0
  122. package/dist/assets/{zh-CEvYioAt.js → zh-DWWuPzr1.js} +1 -1
  123. package/dist/assets/{zh-tw-BjfRYnXu.js → zh-tw-Ci21sS2Y.js} +1 -1
  124. package/dist/index.html +2 -2
  125. package/dist/service-worker.js +1 -1
  126. package/package.json +24 -24
  127. package/dist/assets/config-space-Bgt2MtPU.js +0 -1
  128. package/dist/assets/index-4zAH1uuX.js +0 -5
  129. package/dist/assets/index-BkZLaapf.js +0 -123
  130. package/dist/assets/publish-resource-CQLawfAh.js +0 -1
  131. package/dist/assets/sdk-B4wNFenI.js +0 -1
  132. package/dist/assets/wrap-locale-DrPh3OVK.js +0 -1
@@ -1,31 +1,34 @@
1
- const {
2
- validateReceiver,
3
- validateNotification,
4
- validateMessage,
5
- NOTIFICATION_TYPES,
6
- } = require('@blocklet/sdk/lib/validators/notification');
1
+ const { validateNotification, validateMessage } = require('@blocklet/sdk/lib/validators/notification');
7
2
  const { Joi } = require('@arcblock/validator');
8
- const { NODE_MODES, NOTIFICATION_SEND_STATUS, NOTIFICATION_SEND_FAILED_REASON } = require('@abtnode/constant');
9
- const { getWalletDid } = require('@blocklet/sdk/lib/did');
3
+ const {
4
+ NODE_MODES,
5
+ NOTIFICATION_SEND_STATUS,
6
+ NOTIFICATION_SEND_FAILED_REASON,
7
+ NOTIFICATION_SEND_CHANNEL,
8
+ } = require('@abtnode/constant');
10
9
  const JWT = require('@arcblock/jwt');
11
- const pMap = require('p-map');
12
- const uniqBy = require('lodash/uniqBy');
13
10
  const uniq = require('lodash/uniq');
14
- const get = require('lodash/get');
15
- const pLimit = require('p-limit');
16
11
 
17
12
  const logger = require('../../libs/logger')('notification');
18
- const { ensureSenderApp, getSenderServer, parseNotification, broadcast, EVENTS } = require('../util');
13
+ const { ensureSenderApp, getSenderServer, parseNotification, EVENTS } = require('../util');
19
14
  const states = require('../../state');
20
- const { validateEmail, sendEmail } = require('../../libs/email');
15
+ const { sendEmail } = require('../../libs/email');
21
16
  const { sendPush } = require('../../libs/push-kit');
22
- const { getBlockletInfo } = require('../../cache');
23
- const { nanoid } = require('../../util');
24
17
 
25
18
  const CHANNEL_FIELD_MAP = {
26
- wallet: ['walletSendStatus', 'walletSendAt', 'walletSendFailedReason', 'walletSendRecord'],
27
- pushKit: ['pushKitSendStatus', 'pushKitSendAt', 'pushKitSendFailedReason', 'pushKitSendRecord'],
28
- email: ['emailSendStatus', 'emailSendAt', 'emailSendFailedReason', 'emailSendRecord'],
19
+ [NOTIFICATION_SEND_CHANNEL.WALLET]: [
20
+ 'walletSendStatus',
21
+ 'walletSendAt',
22
+ 'walletSendFailedReason',
23
+ 'walletSendRecord',
24
+ ],
25
+ [NOTIFICATION_SEND_CHANNEL.PUSH]: [
26
+ 'pushKitSendStatus',
27
+ 'pushKitSendAt',
28
+ 'pushKitSendFailedReason',
29
+ 'pushKitSendRecord',
30
+ ],
31
+ [NOTIFICATION_SEND_CHANNEL.EMAIL]: ['emailSendStatus', 'emailSendAt', 'emailSendFailedReason', 'emailSendRecord'],
29
32
  };
30
33
 
31
34
  const updateNotificationSendStatus = async ({
@@ -41,7 +44,7 @@ const updateNotificationSendStatus = async ({
41
44
  if (!node || !notificationId || !receivers.length) {
42
45
  return undefined;
43
46
  }
44
- if (channel === 'webhook' && Object.keys(webhookParams).length > 0) {
47
+ if (channel === NOTIFICATION_SEND_CHANNEL.WEBHOOK && Object.keys(webhookParams).length > 0) {
45
48
  const updateParams = {
46
49
  receivers,
47
50
  teamDid,
@@ -100,427 +103,86 @@ const updateNotificationSendStatus = async ({
100
103
  * @param {ABTNode} options.node
101
104
  * @param {WsServer} options.wsServer
102
105
  */
103
- const sendToUserDid = async ({ sender, receiver: rawDid, notification, options, node, wsServer }) => {
106
+ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options, node }) => {
104
107
  // FIXME: @zhanghan 默认不应该有 push(仅在测试阶段加上),后期需要移除
105
- const { keepForOfflineUser = true, channels = ['app', 'email', 'webhook', 'push'] } = options || {};
108
+ const { channels = ['app', 'email', 'webhook', 'push'], ...rest } = options || {};
109
+
106
110
  if (channels.length === 0) {
107
111
  return;
108
112
  }
109
-
110
- const teamDid = notification?.appInfo?.did || sender.appDid;
111
-
112
- // 要判断是否是 role Did
113
- const userDids = Array.isArray(rawDid) ? rawDid : [rawDid];
114
-
115
- const receivers = await node.getNotificationReceivers({ teamDid, userDids, enableConnectedAccounts: true });
116
- const receiver = receivers.map((r) => r.did);
117
-
118
- let receiverPushDidList = [];
119
- let notEnabledPushReceiverList = [];
120
- let receiverAppDidList = [];
121
- let notEnabledAppReceiverList = [];
122
- let receiverEmailList = [];
123
- let notEnabledEmailReceiverList = [];
124
- let webhookList = [];
125
-
126
- const webhookSenders = new Map();
127
-
128
- await pMap(receivers, async (user) => {
129
- const walletEnabled = get(user, 'extra.notifications.wallet', true);
130
- const emailEnabled = get(user, 'extra.notifications.email', true);
131
- const pushEnabled = get(user, 'extra.notifications.push', true);
132
- const webhooks = get(user, 'extra.webhooks', []);
133
-
134
- const receiverDid = getWalletDid(user) || user.did;
135
- const receiverEmail = user?.email;
136
- if (!walletEnabled) {
137
- notEnabledAppReceiverList.push(receiverDid);
138
- }
139
- if (!pushEnabled) {
140
- notEnabledPushReceiverList.push(receiverDid);
141
- }
142
- if (!emailEnabled) {
143
- notEnabledEmailReceiverList.push(receiverDid);
144
- }
145
-
146
- if (receiverDid && (walletEnabled || pushEnabled)) {
147
- try {
148
- await validateReceiver(receiverDid);
149
- if (walletEnabled) {
150
- receiverAppDidList.push(receiverDid);
151
- }
152
- if (pushEnabled) {
153
- receiverPushDidList.push(receiverDid);
154
- }
155
- } catch (error) {
156
- logger.error(`Skip invalid did receiver: ${receiverDid}`, { error });
157
- }
158
- }
159
- if (receiverEmail && emailEnabled) {
160
- try {
161
- await validateEmail(receiverEmail);
162
- receiverEmailList.push({
163
- email: receiverEmail,
164
- locale: user?.locale || 'en',
165
- did: receiverDid,
166
- fullName: user?.fullName,
167
- avatar: user?.avatar,
168
- });
169
- } catch (error) {
170
- logger.error(`Skip invalid email receiver: ${receiverEmail}`, { error });
171
- }
172
- } else {
173
- notEnabledEmailReceiverList.push(receiverDid);
174
- }
175
-
176
- if (Array.isArray(webhooks)) {
177
- webhookList.push(...webhooks.filter((x) => x.type && x.url));
178
- }
179
- });
180
-
181
- if (
182
- receiverAppDidList.length === 0 &&
183
- receiverEmailList.length === 0 &&
184
- receiverPushDidList.length === 0 &&
185
- webhookList.length === 0
186
- ) {
187
- throw new Error('Invalid receiver');
188
- }
113
+ const teamDid = sender.appDid;
189
114
 
190
115
  const nodeInfo = await node.getNodeInfo({ useCache: true });
191
116
 
192
- const isServer = teamDid === nodeInfo.did;
193
-
194
117
  if (nodeInfo.mode !== NODE_MODES.DEBUG) {
195
- await validateNotification(notification);
118
+ try {
119
+ await validateNotification(notification);
120
+ } catch (error) {
121
+ logger.error('Failed to validate notification', { error });
122
+ // 抛出错误,阻止后续执行
123
+ throw error;
124
+ }
196
125
  }
197
126
 
198
- // parse sender
199
- const senderInfo = await ensureSenderApp({ sender, node, nodeInfo });
200
-
201
- // parse notification
202
- const notifications = parseNotification(notification, senderInfo);
203
-
204
- // uniq receivers
205
- receiverPushDidList = uniq(receiverPushDidList);
206
- receiverAppDidList = uniq(receiverAppDidList);
207
- receiverEmailList = uniqBy(receiverEmailList, 'email');
208
- webhookList = uniqBy(webhookList, 'url');
209
-
210
- notEnabledAppReceiverList = uniq(notEnabledAppReceiverList);
211
- notEnabledPushReceiverList = uniq(notEnabledPushReceiverList);
212
- notEnabledEmailReceiverList = uniq(notEnabledEmailReceiverList);
213
-
214
- const disabledChannelReceivers = [
215
- [notEnabledAppReceiverList, 'wallet'],
216
- [notEnabledPushReceiverList, 'pushKit'],
217
- [notEnabledEmailReceiverList, 'email'],
218
- ];
219
-
220
- let wallet;
221
-
222
- if (sender.type !== 'server') {
223
- const blockletInfo = await getBlockletInfo({ did: teamDid, node });
224
- wallet = blockletInfo.wallet;
127
+ if (!sender.token) {
128
+ throw new Error('Invalid sender: token empty');
225
129
  }
226
130
 
227
- const _teamDid = isServer ? nodeInfo.did : teamDid;
131
+ const notifications = Array.isArray(notification) ? notification : [notification];
228
132
 
229
- // send notification
230
133
  await Promise.all(
231
- notifications.map(async (data) => {
232
- let doc = null;
233
- const isSaveSkipped = data.type?.toLowerCase() === 'passthrough';
234
- if (data.id && !isSaveSkipped) {
235
- doc = await node.getNotificationById({
236
- teamDid: _teamDid,
237
- id: data.id,
238
- });
239
- }
240
- if (!data.id && !isServer && sender.type !== 'server' && !isSaveSkipped) {
241
- doc = await node.createNotification({
242
- teamDid,
243
- ...data,
244
- receiver,
245
- componentDid: sender.componentDid,
246
- // 这两个字段用于 socket 通知
247
- entityType: 'blocklet',
248
- entityId: sender.appDid,
249
- source: 'component',
250
- ignorePush: true,
251
- });
252
- }
253
-
254
- // 为消息添加一个ID,wallet钱包接受消息会使用这个字段
255
- data.id = data.id || doc?.id || nanoid();
256
- const commonParams = {
257
- node,
258
- teamDid: _teamDid,
259
- notificationId: doc?.id,
260
- };
134
+ notifications.map((_notification) => {
135
+ const pushOnly = ['passthrough', 'connect', 'hi'].includes(_notification.type?.toLowerCase());
261
136
 
262
- // 更新用户未开启渠道状态
263
- disabledChannelReceivers.forEach(([_receivers, channel]) => {
264
- // 向用户对应的渠道发送消息但是用户没有开启对应渠道;属于发送失败
265
- updateNotificationSendStatus({
266
- ...commonParams,
267
- receivers: _receivers,
268
- channel,
269
- status: NOTIFICATION_SEND_STATUS.FAILED,
270
- failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
271
- });
137
+ return node.createNotification({
138
+ teamDid,
139
+ notification: _notification,
140
+ receiver: rawDid,
141
+ componentDid: sender.componentDid,
142
+ // 这两个字段用于 socket 通知
143
+ entityType: notification.entityType || 'blocklet',
144
+ entityId: notification.entityId || sender.appDid,
145
+ source: notification.source || 'component',
146
+ channels,
147
+ sender,
148
+ options: { ...rest },
149
+ pushOnly,
272
150
  });
273
-
274
- // 发送钱包消息
275
- if (channels.includes('app')) {
276
- for (const receiverDid of receiverAppDidList) {
277
- broadcast(wsServer, receiverDid, EVENTS.MESSAGE, data, async (count) => {
278
- if (count <= 0 && keepForOfflineUser) {
279
- logger.error('Online client was not found', { userDid: receiverDid });
280
- await states.message.insert({ did: receiverDid, event: EVENTS.MESSAGE, data });
281
- updateNotificationSendStatus({
282
- ...commonParams,
283
- receivers: [receiverDid],
284
- channel: 'wallet',
285
- status: NOTIFICATION_SEND_STATUS.FAILED,
286
- failedReason: NOTIFICATION_SEND_FAILED_REASON.NOT_ONLINE,
287
- });
288
- } else {
289
- updateNotificationSendStatus({
290
- ...commonParams,
291
- receivers: [receiverDid],
292
- channel: 'wallet',
293
- status: NOTIFICATION_SEND_STATUS.SENT,
294
- });
295
- }
296
- });
297
- }
298
- } else {
299
- updateNotificationSendStatus({
300
- ...commonParams,
301
- receivers: receiverAppDidList,
302
- channel: 'wallet',
303
- status: NOTIFICATION_SEND_STATUS.FAILED,
304
- failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
305
- });
306
- }
307
-
308
- if (channels.includes('push')) {
309
- // 发送 pushKit
310
- // 目前保持发送消息跟钱包能收到的消息是一致的(会包含组件的启停通知)
311
- sendPush(receiverPushDidList, data, { teamDid, node })
312
- .then((res) => {
313
- updateNotificationSendStatus({
314
- ...commonParams,
315
- receivers: receiverPushDidList,
316
- channel: 'pushKit',
317
- status: res ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
318
- failedReason: res ? null : NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE,
319
- });
320
- })
321
- .catch((err) => {
322
- // 即使失败也不应该导致程序异常,还是需要发送其他渠道
323
- logger.error('Failed to send pushKit', { error: err });
324
- updateNotificationSendStatus({
325
- ...commonParams,
326
- receivers: receiverPushDidList,
327
- channel: 'pushKit',
328
- status: NOTIFICATION_SEND_STATUS.FAILED,
329
- failedReason: err.message,
330
- });
331
- });
332
- } else {
333
- updateNotificationSendStatus({
334
- ...commonParams,
335
- receivers: receiverPushDidList,
336
- channel: 'pushKit',
337
- status: NOTIFICATION_SEND_STATUS.FAILED,
338
- failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
339
- });
340
- }
341
-
342
- // Do not send email for component activities
343
- // server 的通知暂不支持发送邮箱
344
- // 不建议通过这种方式禁止发送消息,要根据传入的 channel 判断
345
- if (data.source !== 'app' && sender.type !== 'server') {
346
- // 发送邮件消息
347
- // NOTICE: 目前只有 notification 的通知能够发送邮件,并且 type 可能为 undefined
348
- if (channels.includes('email')) {
349
- if ([undefined, NOTIFICATION_TYPES.NOTIFICATION].includes(data.type)) {
350
- const limit = pLimit(10);
351
- const sendEmailList = receiverEmailList.map((receiverItem) => {
352
- return limit(async () => {
353
- try {
354
- const now = Math.floor(Date.now() / 1000);
355
- const userDid = receiverItem?.did;
356
- const unsubscribeToken = userDid
357
- ? JWT.signV2(wallet.address, wallet.secretKey, {
358
- userDid,
359
- channel: 'email',
360
- // 30 days
361
- exp: String(now + 30 * 24 * 60 * 60),
362
- })
363
- : null;
364
- const res = await sendEmail(receiverItem.email, data, {
365
- teamDid,
366
- node,
367
- locale: receiverItem.locale,
368
- unsubscribeToken,
369
- userInfo: {
370
- did: userDid,
371
- fullName: receiverItem.fullName,
372
- // NOTICE: 将来可能还需要头像,先保留该代码
373
- // avatar: getUserAvatarUrl(receiverItem.avatar, blocklet),
374
- },
375
- });
376
- updateNotificationSendStatus({
377
- ...commonParams,
378
- receivers: [userDid],
379
- channel: 'email',
380
- status: res ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
381
- failedReason: res ? null : NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE,
382
- });
383
- } catch (error) {
384
- logger.error('Failed to send email');
385
- updateNotificationSendStatus({
386
- ...commonParams,
387
- receivers: [receiverItem?.did],
388
- channel: 'email',
389
- status: NOTIFICATION_SEND_STATUS.FAILED,
390
- failedReason: error.message,
391
- });
392
- }
393
- });
394
- });
395
- Promise.all(sendEmailList);
396
- }
397
- } else {
398
- updateNotificationSendStatus({
399
- ...commonParams,
400
- receivers: receiverEmailList.map((item) => item.did),
401
- channel: 'email',
402
- status: NOTIFICATION_SEND_STATUS.FAILED,
403
- failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
404
- });
405
- }
406
-
407
- if (channels.includes('webhook') && !isSaveSkipped) {
408
- // 发送 webhook
409
- for (const webhook of webhookList) {
410
- let webhookSender = webhookSenders.get(webhook.type);
411
- if (!webhookSender) {
412
- webhookSender = node.getMessageSender(webhook.type);
413
- webhookSenders.set(webhook.type, webhookSender);
414
- }
415
-
416
- const webhookParams = {
417
- [webhook.url]: {
418
- type: webhook.type ?? 'api',
419
- sendAt: new Date(),
420
- },
421
- };
422
-
423
- webhookSender
424
- .sendNotification(webhook.url, data)
425
- .then((res) => {
426
- webhookParams[webhook.url] = {
427
- ...webhookParams[webhook.url],
428
- status: res ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
429
- failedReason: res ? '' : NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE,
430
- };
431
- updateNotificationSendStatus({
432
- ...commonParams,
433
- receivers: receivers.map((item) => item.did),
434
- channel: 'webhook',
435
- webhookParams,
436
- });
437
- })
438
- .catch((err) => {
439
- logger.error('Failed to send webhook', { error: err });
440
- webhookParams[webhook.url] = {
441
- ...webhookParams[webhook.url],
442
- status: NOTIFICATION_SEND_STATUS.FAILED,
443
- failedReason: err.message,
444
- };
445
- updateNotificationSendStatus({
446
- ...commonParams,
447
- receivers: receivers.map((item) => item.did),
448
- channel: 'webhook',
449
- webhookParams,
450
- });
451
- });
452
- }
453
- } else {
454
- // eslint-disable-next-line no-lonely-if
455
- if (webhookList.length > 0) {
456
- const webhookParams = {};
457
- for (const webhook of webhookList) {
458
- webhookParams[webhook.url] = {
459
- type: webhook.type ?? 'url',
460
- sendAt: new Date(),
461
- status: NOTIFICATION_SEND_STATUS.FAILED,
462
- failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
463
- };
464
- }
465
- updateNotificationSendStatus({
466
- ...commonParams,
467
- receivers: receivers.map((item) => item.did),
468
- channel: 'webhook',
469
- webhookParams,
470
- });
471
- }
472
- }
473
- } else {
474
- updateNotificationSendStatus({
475
- ...commonParams,
476
- receivers: receiverEmailList.map((item) => item.did),
477
- channel: 'email',
478
- status: NOTIFICATION_SEND_STATUS.FAILED,
479
- failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
480
- });
481
- if (webhookList.length > 0) {
482
- const webhookParams = {};
483
- for (const webhook of webhookList) {
484
- webhookParams[webhook.url] = {
485
- type: webhook.type ?? 'url',
486
- sendAt: new Date(),
487
- status: NOTIFICATION_SEND_STATUS.FAILED,
488
- failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
489
- };
490
- }
491
- updateNotificationSendStatus({
492
- ...commonParams,
493
- receivers: receivers.map((item) => item.did),
494
- channel: 'webhook',
495
- webhookParams,
496
- });
497
- }
498
- }
499
151
  })
500
152
  );
501
153
  };
502
154
 
503
155
  // server send notification to app
504
- const sendToAppDid = async ({ event, sender, notification, receiver, appDid, data, node, wsServer }) => {
156
+ const sendToAppDid = async ({ event, sender, notification, receiver, appDid, data, node, options, wsServer }) => {
505
157
  if (notification && sender) {
158
+ const { keepForOfflineUser = true } = options || {};
506
159
  const teamDid = sender.appDid;
507
160
  const { id } = notification;
161
+ if (Array.isArray(receiver)) {
162
+ throw new Error('Invalid receiver: 1 receiver each time');
163
+ }
164
+ if (Array.isArray(notification)) {
165
+ throw new Error('Invalid notification: 1 message each time');
166
+ }
508
167
  const nodeInfo = await node.getNodeInfo({ useCache: true });
509
- const isServer = teamDid === nodeInfo.did;
510
- wsServer.broadcast(receiver, EVENTS.MESSAGE, notification, {}, ({ count } = {}) => {
511
- if (!isServer) {
512
- updateNotificationSendStatus({
513
- node,
514
- teamDid,
515
- notificationId: id,
516
- receivers: [receiver],
517
- channel: 'wallet',
518
- status: count > 0 ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
519
- failedReason: count > 0 ? null : NOTIFICATION_SEND_FAILED_REASON.NOT_ONLINE,
520
- });
521
- }
522
- if (count <= 0) {
168
+ // parse notification
169
+ const senderInfo = await ensureSenderApp({ sender, node, nodeInfo });
170
+ // parse notification
171
+ const [payload] = parseNotification({ ...notification }, senderInfo);
172
+
173
+ wsServer.broadcast(receiver, EVENTS.MESSAGE, payload, {}, async ({ count } = {}) => {
174
+ updateNotificationSendStatus({
175
+ node,
176
+ teamDid,
177
+ notificationId: id,
178
+ receivers: [receiver],
179
+ channel: NOTIFICATION_SEND_CHANNEL.WALLET,
180
+ status: count > 0 ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
181
+ failedReason: count > 0 ? null : NOTIFICATION_SEND_FAILED_REASON.NOT_ONLINE,
182
+ });
183
+ if (count <= 0 && keepForOfflineUser) {
523
184
  logger.error('Online client was not found', { userDid: receiver });
185
+ await states.message.insert({ did: receiver, event: EVENTS.MESSAGE, data: payload });
524
186
  }
525
187
  });
526
188
  return;
@@ -625,6 +287,7 @@ const sendToWebhook = async ({ sender, receiver, notification, node }) => {
625
287
  const teamDid = sender.appDid;
626
288
  const { id, appInfo, ...rest } = notification;
627
289
  const webhooks = appInfo?.webhooks ?? [];
290
+ const receivers = Array.isArray(receiver) ? receiver : [receiver];
628
291
  if (!webhooks.length) {
629
292
  throw new Error('Invalid webhook: empty');
630
293
  }
@@ -643,67 +306,71 @@ const sendToWebhook = async ({ sender, receiver, notification, node }) => {
643
306
  const notifications = parseNotification({ id, ...rest }, senderInfo);
644
307
  const webhookSenderMap = new Map();
645
308
 
646
- return Promise.all(
647
- webhooks.map(async (webhook) => {
648
- let webhookSender = webhookSenderMap.get(webhook.type);
649
- if (!webhookSender) {
650
- webhookSender = node.getMessageSender(webhook.type);
651
- webhookSenderMap.set(webhook.type, webhookSender);
652
- }
653
- const webhookParams = {
654
- [webhook.url]: {
655
- type: webhook.type ?? 'api',
656
- sendAt: new Date(),
657
- },
309
+ // eslint-disable-next-line no-unreachable-loop
310
+ for (const webhook of webhooks) {
311
+ let webhookSender = webhookSenderMap.get(webhook.type);
312
+ if (!webhookSender) {
313
+ webhookSender = node.getMessageSender(webhook.type);
314
+ webhookSenderMap.set(webhook.type, webhookSender);
315
+ }
316
+ const webhookParams = {
317
+ [webhook.url]: {
318
+ type: webhook.type ?? 'api',
319
+ sendAt: new Date(),
320
+ },
321
+ };
322
+ try {
323
+ // eslint-disable-next-line no-await-in-loop
324
+ const res = await webhookSender.sendNotification(webhook.url, notifications[0]);
325
+ webhookParams[webhook.url] = {
326
+ ...webhookParams[webhook.url],
327
+ status: res ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
328
+ failedReason: res ? '' : NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE,
658
329
  };
659
- try {
660
- const res = await webhookSender.sendNotification(webhook.url, notifications[0]);
661
- webhookParams[webhook.url] = {
662
- ...webhookParams[webhook.url],
663
- status: res ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
664
- failedReason: res ? '' : NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE,
665
- };
666
- await updateNotificationSendStatus({
667
- node,
668
- teamDid,
669
- notificationId: id,
670
- receivers: [receiver],
671
- channel: 'webhook',
672
- webhookParams,
673
- });
674
- return res;
675
- } catch (error) {
676
- logger.error('Failed to send webhook', { error });
677
- webhookParams[webhook.url] = {
678
- ...webhookParams[webhook.url],
679
- status: NOTIFICATION_SEND_STATUS.FAILED,
680
- failedReason: error.message,
681
- };
682
- updateNotificationSendStatus({
683
- node,
684
- teamDid,
685
- notificationId: id,
686
- receivers: [receiver],
687
- channel: 'webhook',
688
- webhookParams,
689
- });
690
- const err = new Error('Failed to send webhook');
691
- err.details = error;
692
- throw err;
693
- }
694
- })
695
- );
330
+ // eslint-disable-next-line no-await-in-loop
331
+ await updateNotificationSendStatus({
332
+ node,
333
+ teamDid,
334
+ notificationId: id,
335
+ receivers,
336
+ channel: NOTIFICATION_SEND_CHANNEL.WEBHOOK,
337
+ webhookParams,
338
+ });
339
+ return res;
340
+ } catch (error) {
341
+ logger.error('Failed to send webhook', { error });
342
+ webhookParams[webhook.url] = {
343
+ ...webhookParams[webhook.url],
344
+ status: NOTIFICATION_SEND_STATUS.FAILED,
345
+ failedReason: error.message,
346
+ };
347
+ // eslint-disable-next-line no-await-in-loop
348
+ await updateNotificationSendStatus({
349
+ node,
350
+ teamDid,
351
+ notificationId: id,
352
+ receivers,
353
+ channel: NOTIFICATION_SEND_CHANNEL.WEBHOOK,
354
+ webhookParams,
355
+ });
356
+ const err = new Error('Failed to send webhook');
357
+ err.details = error;
358
+ throw err;
359
+ }
360
+ }
361
+ return undefined;
696
362
  };
697
363
 
698
364
  /**
699
365
  * app send email to user
366
+ * receiver: email
700
367
  */
701
368
  const emailSchema = Joi.string().email().required();
702
369
  const sendToMail = async ({ sender, receiver, notification, options, node }) => {
703
370
  const teamDid = sender.appDid;
704
371
  const { id, appInfo, ...rest } = notification;
705
372
 
706
- const receiverDid = appInfo?.receiverDid;
373
+ const { receiverDid, receivers: sameEmailReceivers = [], unsubscribeToken, userInfo = {} } = appInfo || {};
707
374
 
708
375
  const receivers = uniq(Array.isArray(receiver) ? receiver : [receiver]).filter(Boolean);
709
376
  if (receivers.length === 0) {
@@ -722,7 +389,7 @@ const sendToMail = async ({ sender, receiver, notification, options, node }) =>
722
389
  if (!notification.title) {
723
390
  throw new Error('Invalid notification: title empty');
724
391
  }
725
- if (!notification.body && notification.type !== NOTIFICATION_TYPES.FEED) {
392
+ if (!notification.body && notification.type !== 'feed') {
726
393
  throw new Error('Invalid notification: body empty');
727
394
  }
728
395
 
@@ -745,21 +412,26 @@ const sendToMail = async ({ sender, receiver, notification, options, node }) =>
745
412
  teamDid: sender.appDid,
746
413
  node,
747
414
  locale,
415
+ ...(unsubscribeToken ? { unsubscribeToken } : {}),
416
+ ...(userInfo ? { userInfo } : {}),
748
417
  });
749
418
  })
750
419
  );
751
420
  return results.map((item) => {
752
- if (!isServer && receiverDid) {
421
+ if (!isServer && (receiverDid || sameEmailReceivers.length > 0)) {
753
422
  updateNotificationSendStatus({
754
423
  node,
755
424
  teamDid,
756
425
  notificationId: id,
757
- receivers: [receiverDid],
758
- channel: 'email',
426
+ receivers: sameEmailReceivers.length > 0 ? sameEmailReceivers : [receiverDid],
427
+ channel: NOTIFICATION_SEND_CHANNEL.EMAIL,
759
428
  status: item.status === 'rejected' ? NOTIFICATION_SEND_STATUS.FAILED : NOTIFICATION_SEND_STATUS.SENT,
760
429
  ...(item.status === 'rejected'
761
430
  ? {
762
- failedReason: item.reason instanceof Error ? item.reason?.message : item.reason,
431
+ failedReason:
432
+ item.reason instanceof Error
433
+ ? item.reason?.message
434
+ : item.reason || NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE,
763
435
  }
764
436
  : {}),
765
437
  });
@@ -800,7 +472,7 @@ const sendToPush = async ({ sender, receiver, notification, options, node }) =>
800
472
 
801
473
  const senderInfo = await ensureSenderApp({ sender, node, nodeInfo });
802
474
 
803
- const notifications = parseNotification(notification, senderInfo);
475
+ const notifications = parseNotification({ ...notification }, senderInfo);
804
476
 
805
477
  const { locale = 'en' } = options || {};
806
478
  return sendPush(receivers, notifications[0], {
@@ -815,7 +487,7 @@ const sendToPush = async ({ sender, receiver, notification, options, node }) =>
815
487
  teamDid,
816
488
  notificationId: id,
817
489
  receivers,
818
- channel: 'pushKit',
490
+ channel: NOTIFICATION_SEND_CHANNEL.PUSH,
819
491
  status: res ? NOTIFICATION_SEND_STATUS.SENT : NOTIFICATION_SEND_STATUS.FAILED,
820
492
  failedReason: res ? null : NOTIFICATION_SEND_FAILED_REASON.CHANNEL_UNAVAILABLE,
821
493
  });
@@ -829,7 +501,7 @@ const sendToPush = async ({ sender, receiver, notification, options, node }) =>
829
501
  teamDid,
830
502
  notificationId: id,
831
503
  receivers,
832
- channel: 'pushKit',
504
+ channel: NOTIFICATION_SEND_CHANNEL.PUSH,
833
505
  status: NOTIFICATION_SEND_STATUS.FAILED,
834
506
  failedReason: err.message,
835
507
  });
@@ -846,4 +518,5 @@ module.exports = {
846
518
  sendToPush,
847
519
  emailSchema,
848
520
  sendToWebhook,
521
+ updateNotificationSendStatus,
849
522
  };