@abtnode/blocklet-services 1.16.39-beta-20250215-130252-43410928 → 1.16.39-beta-20250218-132815-6baaf20e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/index.js +6 -2
- package/api/libs/email.js +2 -2
- package/api/services/notification/index.js +4 -2
- package/api/services/notification/notification-queue.js +671 -0
- package/api/socket/channel/did.js +146 -473
- package/dist/assets/{Add-TglZpKpw.js → Add-meGhscd6.js} +1 -1
- package/dist/assets/{ArrowDropDown-DCM27ed6.js → ArrowDropDown-B7S4xnEe.js} +1 -1
- package/dist/assets/{CheckCircle-CH8SYy4d.js → CheckCircle-CnvZ4oB1.js} +1 -1
- package/dist/assets/{ChevronLeft-B7m8AFzR.js → ChevronLeft-D1qkw0FM.js} +1 -1
- package/dist/assets/{ChevronRight-DeRklga5.js → ChevronRight-CEkTH0up.js} +1 -1
- package/dist/assets/{DeleteOutline-DZA1YKE4.js → DeleteOutline-BjrgG_HY.js} +1 -1
- package/dist/assets/{Done-CVdTMvGH.js → Done-7hvoRcVZ.js} +1 -1
- package/dist/assets/{Download-DECMRaRC.js → Download-CMvqPsK9.js} +1 -1
- package/dist/assets/{Error-B61ZB0Rr.js → Error-D3I1nNnO.js} +1 -1
- package/dist/assets/{Google-D_UbrpMW.js → Google-TNOfJ3uU.js} +1 -1
- package/dist/assets/{InfoOutlined-QBw5AmWP.js → InfoOutlined-CwoytfGr.js} +1 -1
- package/dist/assets/{Launch-Cpp3LVnV.js → Launch-wyBVNKfb.js} +1 -1
- package/dist/assets/{LaunchOutlined-WdAKKV2H.js → LaunchOutlined-B2ZU9fkg.js} +1 -1
- package/dist/assets/{Loop-CbPhaPlF.js → Loop-CQAI5rIS.js} +1 -1
- package/dist/assets/{MoreHoriz-BsTY94ce.js → MoreHoriz-CyCcv2bV.js} +1 -1
- package/dist/assets/{PlayArrow-BgVzxA9Q.js → PlayArrow-B5ZFUKPq.js} +1 -1
- package/dist/assets/{ViewList-DBUe2NSG.js → ViewList-C8AcRueZ.js} +1 -1
- package/dist/assets/{access-control-DeqFMOm5.js → access-control-ClVHa3J1.js} +1 -1
- package/dist/assets/{actions-DP_Rm54e.js → actions-C0a0eUYM.js} +1 -1
- package/dist/assets/{add-component-core-BnP0_g-C.js → add-component-core-CK3SAdlQ.js} +1 -1
- package/dist/assets/{add-resource-B26HHRU4.js → add-resource-hs_iM1xk.js} +1 -1
- package/dist/assets/{addon-DH34oI6i.js → addon-C7fx4MJ0.js} +1 -1
- package/dist/assets/{analytics-DPn53GmC.js → analytics-CjaAB5m2.js} +1 -1
- package/dist/assets/{ar-BJD4HTM3.js → ar-BFUPmTkR.js} +1 -1
- package/dist/assets/{audit-logs-C1oUmr2S.js → audit-logs-bl5st5Lr.js} +1 -1
- package/dist/assets/{base32-CwwDjweV.js → base32-9oQoOkFy.js} +1 -1
- package/dist/assets/{branding-DFfBkuOI.js → branding-5S3zjZlC.js} +2 -2
- package/dist/assets/{bundle-avatar-Cv2R2iWI.js → bundle-avatar-Dnk7mw7b.js} +1 -1
- package/dist/assets/{button-4qfNeG4u.js → button-BTVoaDd7.js} +1 -1
- package/dist/assets/{click-to-copy-886R1sA9.js → click-to-copy-C9X-UQyL.js} +1 -1
- package/dist/assets/{complete-Cz6Vt9yr.js → complete-C3yyjx_N.js} +2 -2
- package/dist/assets/{component-BOGOOyWw.js → component-B0WI6_H4.js} +1 -1
- package/dist/assets/{config-BBRmjpUL.js → config-C0wq7rMr.js} +1 -1
- package/dist/assets/{config-CE9eJ5Ce.js → config-DINcJX6A.js} +1 -1
- package/dist/assets/{config-navigation-ZGyHDU_V.js → config-navigation-Dskr7Yic.js} +3 -3
- package/dist/assets/config-space-BYYAVRcS.js +1 -0
- package/dist/assets/{confirm-f60NfF3X.js → confirm-BmLsHbj9.js} +1 -1
- package/dist/assets/{connect-D9S16wyT.js → connect-06lWrPrR.js} +1 -1
- package/dist/assets/{connect-B2VVyCQY.js → connect-hhGPDSK8.js} +1 -1
- package/dist/assets/{connect-to-BSmtnPV9.js → connect-to-DIJ57-hM.js} +1 -1
- package/dist/assets/{dashboard-BoV1-CrH.js → dashboard-PlqPcVrd.js} +4 -4
- package/dist/assets/{de-CaPBInxb.js → de-hV0yD3gq.js} +1 -1
- package/dist/assets/{did-address-CgDvtQbT.js → did-address-Bl2N44vc.js} +1 -1
- package/dist/assets/{domain-DLGsAByD.js → domain-DDa_Ldun.js} +1 -1
- package/dist/assets/{domain-list-BLV11Y6_.js → domain-list-DXbfS21Z.js} +2 -2
- package/dist/assets/{email-Fgr58FLV.js → email-9CbD02fM.js} +1 -1
- package/dist/assets/{es-hhXhgxGX.js → es-BcSAobJW.js} +1 -1
- package/dist/assets/{exchange-passport-CcsLEfpo.js → exchange-passport-ZTeqENxf.js} +1 -1
- package/dist/assets/{form-text-input-CbMAGh7u.js → form-text-input-DEadnM80.js} +1 -1
- package/dist/assets/{fr-BwQvAhhM.js → fr-8qti32p1.js} +1 -1
- package/dist/assets/{fuel-B8r20ueU.js → fuel-AxStjHWS.js} +1 -1
- package/dist/assets/{fullpage-DLk_Ap1u.js → fullpage-BXvfrD1C.js} +1 -1
- package/dist/assets/{get-safe-url-BzE2b8Zw.js → get-safe-url-BVjMxmwp.js} +1 -1
- package/dist/assets/{get-safe-url-C2hTfmjV.js → get-safe-url-BxZD1oA_.js} +1 -1
- package/dist/assets/{hi-CRWmR5oy.js → hi-DDEUhQcX.js} +1 -1
- package/dist/assets/{home-B30MMEWW.js → home-CrkVy1DG.js} +1 -1
- package/dist/assets/{id-Dlr6Q1sk.js → id-DkUYINLx.js} +1 -1
- package/dist/assets/{iframe-DvysnY8O.js → iframe-BcWepstA.js} +1 -1
- package/dist/assets/{index-B4_ve2gt.js → index-BBOSdbE7.js} +1 -1
- package/dist/assets/{index-EDdyNc1F.js → index-BJA4YPaC.js} +1 -1
- package/dist/assets/{index-DFM7u7wl.js → index-BSC2apzE.js} +1 -1
- package/dist/assets/{index-B6CyLMos.js → index-BXlJ03I_.js} +1 -1
- package/dist/assets/index-Be9vrMiA.js +5 -0
- package/dist/assets/{index-uSZNIR4c.js → index-BtFCAsqh.js} +1 -1
- package/dist/assets/{index-j5s7Rpz_.js → index-CDvqNewi.js} +1 -1
- package/dist/assets/{index-RoF9rzF3.js → index-CoxJ8G1A.js} +1 -1
- package/dist/assets/{index-C-yw0A3M.js → index-DIQ33Q8Z.js} +1 -1
- package/dist/assets/{index-V7v6XM-s.js → index-DfBNyFsV.js} +1 -1
- package/dist/assets/{index-EIsfexf1.js → index-DgaUl_lv.js} +1 -1
- package/dist/assets/{index-CM-Ew9KZ.js → index-DkVoxpR-.js} +1 -1
- package/dist/assets/index-DkufOgYs.js +125 -0
- package/dist/assets/{index-U8kTDDEm.js → index-MxM82EtU.js} +4 -4
- package/dist/assets/{index-r2Lf0YIw.js → index-uYcwdGZL.js} +46 -46
- package/dist/assets/{invitation-ZW3n52Yc.js → invitation-BzHMoDRH.js} +1 -1
- package/dist/assets/{invite-B8cpjqrT.js → invite-BBcuJaUv.js} +1 -1
- package/dist/assets/{issue-passport-DrbTLcdD.js → issue-passport-sgBfFZ4Y.js} +1 -1
- package/dist/assets/{item-BVrq9ZQD.js → item-C5sAxGxS.js} +1 -1
- package/dist/assets/{ja-CmKylH6K.js → ja-C2LP4WQG.js} +1 -1
- package/dist/assets/{ko-Blp8936a.js → ko-C-DMeONa.js} +1 -1
- package/dist/assets/{layout-Dw2Qx7hs.js → layout-CGas_HfS.js} +1 -1
- package/dist/assets/{list-BwH5q_Zc.js → list-cZ-OXxN0.js} +1 -1
- package/dist/assets/{list-header-BBHpD8dC.js → list-header-Dd4BEf4y.js} +1 -1
- package/dist/assets/{localization-Ciwo-y3M.js → localization-Z_CTe1Z8.js} +1 -1
- package/dist/assets/{log-Ba7iTEJj.js → log-BFj6TDz5.js} +1 -1
- package/dist/assets/{login-BewdlPwi.js → login-B-6s411x.js} +1 -1
- package/dist/assets/{login-oauth-callback-D_bP2yYx.js → login-oauth-callback-aKbzfltj.js} +1 -1
- package/dist/assets/{logo-uploader-CneV6CnS.js → logo-uploader-pyVqJwKp.js} +3 -3
- package/dist/assets/{lost-passport-CNuZ281r.js → lost-passport-DKkxzCH8.js} +1 -1
- package/dist/assets/{open-window-Q0PVNWdu.js → open-window-Cvetj9Mn.js} +1 -1
- package/dist/assets/{overview-giDiT6rf.js → overview-BxbJCUbS.js} +1 -1
- package/dist/assets/{page-header-BDwGXuEW.js → page-header--y63DY5K.js} +1 -1
- package/dist/assets/{permission-Cqed8iP-.js → permission-Be_APIVZ.js} +1 -1
- package/dist/assets/{preferences-CBm0eML7.js → preferences-D-NDmLhf.js} +1 -1
- package/dist/assets/{pt-BbwAvpfn.js → pt-CbxvF__Z.js} +1 -1
- package/dist/assets/publish-resource-CxkiI4_n.js +1 -0
- package/dist/assets/{react-beautiful-dnd.esm-BTWrOgJp.js → react-beautiful-dnd.esm-Czr1mmuu.js} +1 -1
- package/dist/assets/{relative-time-Bt6RtG4o.js → relative-time-D0P0AJUI.js} +1 -1
- package/dist/assets/{ru-DgfLeWul.js → ru-DLHrAeE7.js} +1 -1
- package/dist/assets/sdk-NKQJT0FS.js +1 -0
- package/dist/assets/{session-BKdZFtFw.js → session-CV6ez46v.js} +1 -1
- package/dist/assets/{setup-BdFkCCIb.js → setup-CF2VhAxD.js} +3 -3
- package/dist/assets/{start-Cy0MNYb6.js → start-BjFFihjS.js} +1 -1
- package/dist/assets/{step-actions-CPEQpBG3.js → step-actions-qXq3r5tX.js} +1 -1
- package/dist/assets/{studio-BzK2t7MU.js → studio-BNGylnt7.js} +1 -1
- package/dist/assets/{switch-control-BSKk9hbF.js → switch-control-DmKC0hpm.js} +1 -1
- package/dist/assets/{th-DXRi_ppC.js → th-Dil9kwjH.js} +1 -1
- package/dist/assets/{traffic-BQRcq8gg.js → traffic-Djmt2Ph8.js} +1 -1
- package/dist/assets/{transfer-DhNfOwE1.js → transfer-B9ohZjja.js} +1 -1
- package/dist/assets/{unsubscribe-C0PztwA5.js → unsubscribe-6xRksnNz.js} +1 -1
- package/dist/assets/{useLocalStorage-Bx8dJwZw.js → useLocalStorage-B5t6MEw6.js} +1 -1
- package/dist/assets/{user-center-BshF32Jc.js → user-center-DEGm9vzv.js} +1 -1
- package/dist/assets/{util-BPubgk5p.js → util-Bl4vyFsm.js} +1 -1
- package/dist/assets/{util-CWVLbRLp.js → util-PzCAH0nE.js} +1 -1
- package/dist/assets/{vendor-arcblock-HzAExiTd.js → vendor-arcblock-BFUPp1Ez.js} +7 -7
- package/dist/assets/{vi-nO7JrO24.js → vi-B46hyfUM.js} +1 -1
- package/dist/assets/wrap-locale-6MxBCfA2.js +1 -0
- package/dist/assets/{zh-CEvYioAt.js → zh-DWWuPzr1.js} +1 -1
- package/dist/assets/{zh-tw-BjfRYnXu.js → zh-tw-Ci21sS2Y.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/service-worker.js +1 -1
- package/package.json +31 -31
- package/dist/assets/config-space-Bgt2MtPU.js +0 -1
- package/dist/assets/index-4zAH1uuX.js +0 -5
- package/dist/assets/index-BkZLaapf.js +0 -123
- package/dist/assets/publish-resource-CQLawfAh.js +0 -1
- package/dist/assets/sdk-B4wNFenI.js +0 -1
- package/dist/assets/wrap-locale-DrPh3OVK.js +0 -1
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
const createQueue = require('@abtnode/core/lib/util/queue');
|
|
2
|
+
const { validateNotification } = require('@blocklet/sdk/lib/validators/notification');
|
|
3
|
+
const states = require('@abtnode/core/lib/states');
|
|
4
|
+
const JWT = require('@arcblock/jwt');
|
|
5
|
+
const md5 = require('@abtnode/util/lib/md5');
|
|
6
|
+
const {
|
|
7
|
+
NODE_MODES,
|
|
8
|
+
EVENTS,
|
|
9
|
+
NOTIFICATION_SEND_CHANNEL,
|
|
10
|
+
NOTIFICATION_SEND_STATUS,
|
|
11
|
+
NOTIFICATION_SEND_FAILED_REASON,
|
|
12
|
+
} = require('@abtnode/constant');
|
|
13
|
+
const get = require('lodash/get');
|
|
14
|
+
const uniqBy = require('lodash/uniqBy');
|
|
15
|
+
const { getWalletDid } = require('@blocklet/meta/lib/did-utils');
|
|
16
|
+
const { getBlockletInfo } = require('../../cache');
|
|
17
|
+
const { updateNotificationSendStatus } = require('../../socket/channel/did');
|
|
18
|
+
const eventHub =
|
|
19
|
+
process.env.NODE_ENV === 'test' ? require('@arcblock/event-hub/single') : require('@arcblock/event-hub');
|
|
20
|
+
|
|
21
|
+
const logger = require('../../libs/logger')('notification');
|
|
22
|
+
|
|
23
|
+
const createNotificationQueue = (name, options, handler) => {
|
|
24
|
+
if (!handler || typeof handler !== 'function') {
|
|
25
|
+
throw new Error('Handler is required');
|
|
26
|
+
}
|
|
27
|
+
return createQueue({
|
|
28
|
+
name,
|
|
29
|
+
model: states.job,
|
|
30
|
+
daemon: true,
|
|
31
|
+
options: {
|
|
32
|
+
maxRetries: 3,
|
|
33
|
+
retryDelay: 10 * 1000,
|
|
34
|
+
maxTimeout: 60 * 1000, // throw timeout error after 1 minutes
|
|
35
|
+
...(options ?? {}),
|
|
36
|
+
},
|
|
37
|
+
onJob: async (job) => {
|
|
38
|
+
await handler(job);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const init = ({ node, notificationService }) => {
|
|
44
|
+
const webhookState = states.webhook;
|
|
45
|
+
|
|
46
|
+
const getServerWebhooks = async () => {
|
|
47
|
+
const webhookList = (await webhookState.list()) ?? [];
|
|
48
|
+
return webhookList.flatMap((item) =>
|
|
49
|
+
item.params.filter((param) => param.name === 'url').map((param) => ({ type: item.type, url: param.value }))
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* wallet 推送队列
|
|
55
|
+
*/
|
|
56
|
+
const walletPushQueue = createNotificationQueue(
|
|
57
|
+
'send-notification-wallet',
|
|
58
|
+
{
|
|
59
|
+
maxRetries: 1,
|
|
60
|
+
retryDelay: 0,
|
|
61
|
+
},
|
|
62
|
+
async (job) => {
|
|
63
|
+
const { receiver, notification, sender, options } = job;
|
|
64
|
+
|
|
65
|
+
if (!receiver) {
|
|
66
|
+
logger.error('Invalid receiver', { did: receiver });
|
|
67
|
+
throw new Error('Invalid receiver');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const nodeInfo = await node.getNodeInfo({ useCache: true });
|
|
71
|
+
|
|
72
|
+
if (nodeInfo.mode !== NODE_MODES.DEBUG) {
|
|
73
|
+
try {
|
|
74
|
+
await validateNotification(notification);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error('Failed to validate notification', { error });
|
|
77
|
+
// 抛出错误,阻止后续执行
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (sender.type === 'server' && !sender.token) {
|
|
83
|
+
sender.token = JWT.sign(sender.appDid, sender.appSk);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 发送钱包通知
|
|
87
|
+
await notificationService.sendToApp.exec({
|
|
88
|
+
sender,
|
|
89
|
+
receiver,
|
|
90
|
+
notification,
|
|
91
|
+
options,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Push Kit 推送队列
|
|
98
|
+
*/
|
|
99
|
+
const pushKitPushQueue = createNotificationQueue('send-notification-push', {}, async (job) => {
|
|
100
|
+
const { notification, receiver, sender } = job;
|
|
101
|
+
|
|
102
|
+
if (!receiver) {
|
|
103
|
+
logger.error('Invalid receiver', { did: receiver });
|
|
104
|
+
throw new Error('Invalid receiver');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const nodeInfo = await node.getNodeInfo({ useCache: true });
|
|
108
|
+
|
|
109
|
+
if (nodeInfo.mode !== NODE_MODES.DEBUG) {
|
|
110
|
+
try {
|
|
111
|
+
await validateNotification(notification);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.error('Failed to validate notification', { error });
|
|
114
|
+
// 抛出错误,阻止后续执行
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (sender.type === 'server' && !sender.token) {
|
|
120
|
+
sender.token = JWT.sign(sender.appDid, sender.appSk);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 发送 push kit 通知
|
|
124
|
+
await notificationService.sendToPush.exec({
|
|
125
|
+
sender,
|
|
126
|
+
receiver,
|
|
127
|
+
notification,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* email 推送队列
|
|
133
|
+
*/
|
|
134
|
+
const emailPushQueue = createNotificationQueue(
|
|
135
|
+
'send-notification-email',
|
|
136
|
+
{
|
|
137
|
+
maxRetries: 1,
|
|
138
|
+
retryDelay: 0,
|
|
139
|
+
maxTimeout: 60 * 1000,
|
|
140
|
+
id: (job) => (job ? md5(`${job.email}_${job.notificationId}`) : ''),
|
|
141
|
+
enableScheduledJob: true,
|
|
142
|
+
},
|
|
143
|
+
async (job) => {
|
|
144
|
+
const { input, email } = job;
|
|
145
|
+
if (!input) {
|
|
146
|
+
logger.error('Job input is missing or invalid', { notificationId: job.notificationId });
|
|
147
|
+
throw new Error('Job input is missing or invalid');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!email) {
|
|
151
|
+
logger.error('Email address is missing. Unable to send email notification', {
|
|
152
|
+
notificationId: job.notificationId,
|
|
153
|
+
});
|
|
154
|
+
throw new Error('Email address is missing. Unable to send email notification');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const { notification, sender, teamDid, receivers = [], userInfo } = input;
|
|
158
|
+
|
|
159
|
+
if (!userInfo) {
|
|
160
|
+
logger.error('Invalid receiver', { dids: userInfo.did });
|
|
161
|
+
throw new Error('Invalid receiver');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const nodeInfo = await node.getNodeInfo({ useCache: true });
|
|
165
|
+
|
|
166
|
+
if (nodeInfo.mode !== NODE_MODES.DEBUG) {
|
|
167
|
+
try {
|
|
168
|
+
await validateNotification(notification);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.error('Failed to validate notification', { error });
|
|
171
|
+
// 抛出错误,阻止后续执行
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (sender.type === 'server' && !sender.token) {
|
|
177
|
+
sender.token = JWT.sign(sender.appDid, sender.appSk);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let wallet;
|
|
181
|
+
|
|
182
|
+
if (sender.type !== 'server') {
|
|
183
|
+
const blockletInfo = await getBlockletInfo({ did: teamDid, node });
|
|
184
|
+
wallet = blockletInfo.wallet;
|
|
185
|
+
}
|
|
186
|
+
const now = Math.floor(Date.now() / 1000);
|
|
187
|
+
const unsubscribeToken = JWT.signV2(wallet.address, wallet.secretKey, {
|
|
188
|
+
userDid: userInfo.did,
|
|
189
|
+
channel: 'email',
|
|
190
|
+
exp: String(now + 30 * 24 * 60 * 60), // 30 days
|
|
191
|
+
});
|
|
192
|
+
notificationService.sendToMail.exec({
|
|
193
|
+
sender,
|
|
194
|
+
receiver: email,
|
|
195
|
+
notification: {
|
|
196
|
+
...notification,
|
|
197
|
+
appInfo: {
|
|
198
|
+
...(notification.appInfo || {}),
|
|
199
|
+
receivers: receivers.length > 0 ? receivers : [userInfo.did],
|
|
200
|
+
unsubscribeToken,
|
|
201
|
+
userInfo: {
|
|
202
|
+
did: userInfo.did,
|
|
203
|
+
fullName: userInfo.fullName,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* webhook 推送队列
|
|
213
|
+
*/
|
|
214
|
+
const webhookQueue = createNotificationQueue(
|
|
215
|
+
'send-notification-webhook',
|
|
216
|
+
{
|
|
217
|
+
maxRetries: 3,
|
|
218
|
+
retryDelay: 10 * 1000,
|
|
219
|
+
maxTimeout: 5 * 60 * 1000, // throw timeout error after 5 minutes
|
|
220
|
+
id: (job) => (job ? md5(`${job.url}_${job.notificationId}`) : ''),
|
|
221
|
+
enableScheduledJob: true,
|
|
222
|
+
},
|
|
223
|
+
async (job) => {
|
|
224
|
+
const { input } = job;
|
|
225
|
+
if (!input) {
|
|
226
|
+
logger.error('Invalid job', { url: job.url });
|
|
227
|
+
throw new Error('Invalid job');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const { notification, webhook, sender, receivers = [] } = input;
|
|
231
|
+
|
|
232
|
+
if (!receivers.length) {
|
|
233
|
+
logger.error('Invalid receiver', { dids: receivers });
|
|
234
|
+
throw new Error('Invalid receiver');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const nodeInfo = await node.getNodeInfo({ useCache: true });
|
|
238
|
+
|
|
239
|
+
if (nodeInfo.mode !== NODE_MODES.DEBUG) {
|
|
240
|
+
try {
|
|
241
|
+
await validateNotification(notification);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
logger.error('Failed to validate notification', { error });
|
|
244
|
+
// 抛出错误,阻止后续执行
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!webhook || !webhook.url) {
|
|
250
|
+
logger.error('Invalid webhook', { webhook });
|
|
251
|
+
throw new Error('Invalid webhook');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (sender.type === 'server' && !sender.token) {
|
|
255
|
+
sender.token = JWT.sign(sender.appDid, sender.appSk);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
notificationService.sendToWebhook.exec({
|
|
259
|
+
sender,
|
|
260
|
+
receiver: receivers,
|
|
261
|
+
notification: {
|
|
262
|
+
...notification,
|
|
263
|
+
appInfo: {
|
|
264
|
+
...(notification.appInfo || {}),
|
|
265
|
+
webhooks: [webhook],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const insertToEmailPushQueue = (props, nodeInfo, emailMap) => {
|
|
273
|
+
const { channels, notification, sender, userInfo, teamDid } = props;
|
|
274
|
+
|
|
275
|
+
const commonParams = {
|
|
276
|
+
node,
|
|
277
|
+
teamDid: teamDid ?? nodeInfo.did,
|
|
278
|
+
notificationId: notification.id,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const emailEnabled = get(userInfo, 'extra.notifications.email', true);
|
|
282
|
+
|
|
283
|
+
const email = userInfo?.email;
|
|
284
|
+
|
|
285
|
+
const receiverDid = getWalletDid(userInfo) || userInfo.did;
|
|
286
|
+
|
|
287
|
+
const channelEnabled = emailEnabled && channels.includes(NOTIFICATION_SEND_CHANNEL.EMAIL);
|
|
288
|
+
|
|
289
|
+
const { receivers, pushUser } = emailMap.get(email) || { receivers: [], pushUser: {} };
|
|
290
|
+
|
|
291
|
+
const pushUserDid = getWalletDid(pushUser) || pushUser.did;
|
|
292
|
+
|
|
293
|
+
// 如果 receivers 中已经存在 receiverDid,并且 pushUserDid 和 receiverDid 不一致,则不发送
|
|
294
|
+
if (receivers.includes(receiverDid) && pushUserDid !== receiverDid) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (channelEnabled && email) {
|
|
299
|
+
const _userInfo = {
|
|
300
|
+
did: receiverDid,
|
|
301
|
+
fullName: userInfo.fullName,
|
|
302
|
+
};
|
|
303
|
+
// 这里可以根据 email 获取到需要发送的 userInfo 和 receivers
|
|
304
|
+
emailPushQueue.push({
|
|
305
|
+
job: {
|
|
306
|
+
email,
|
|
307
|
+
notificationId: notification.id,
|
|
308
|
+
input: {
|
|
309
|
+
notification,
|
|
310
|
+
sender,
|
|
311
|
+
teamDid,
|
|
312
|
+
receivers,
|
|
313
|
+
userInfo: _userInfo,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
delay: 5,
|
|
317
|
+
});
|
|
318
|
+
} else {
|
|
319
|
+
updateNotificationSendStatus({
|
|
320
|
+
...commonParams,
|
|
321
|
+
receivers: receivers.length > 0 ? receivers : [receiverDid],
|
|
322
|
+
channel: NOTIFICATION_SEND_CHANNEL.EMAIL,
|
|
323
|
+
status: NOTIFICATION_SEND_STATUS.FAILED,
|
|
324
|
+
failedReason:
|
|
325
|
+
!emailEnabled || !email
|
|
326
|
+
? NOTIFICATION_SEND_FAILED_REASON.USER_DISABLED
|
|
327
|
+
: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const insertToWebhookPushQueue = async (props, nodeInfo, webhookMap) => {
|
|
333
|
+
const { channels, notification, sender, userInfo, teamDid } = props;
|
|
334
|
+
|
|
335
|
+
const receiverDid = getWalletDid(userInfo) || userInfo.did;
|
|
336
|
+
|
|
337
|
+
const commonParams = {
|
|
338
|
+
node,
|
|
339
|
+
teamDid: teamDid ?? nodeInfo.did,
|
|
340
|
+
notificationId: notification.id,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const isServer = teamDid === nodeInfo.did;
|
|
344
|
+
|
|
345
|
+
let webhooks = get(userInfo, 'extra.webhooks', []);
|
|
346
|
+
if (isServer) {
|
|
347
|
+
webhooks = await getServerWebhooks();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const webhookList = uniqBy(webhooks, 'url')
|
|
351
|
+
.map((webhook) => {
|
|
352
|
+
const { url, type } = webhook;
|
|
353
|
+
const { receivers, pushUser } = webhookMap.get(url) || {
|
|
354
|
+
type: type ?? 'api',
|
|
355
|
+
receivers: [],
|
|
356
|
+
pushUser: {},
|
|
357
|
+
};
|
|
358
|
+
const pushUserDid = getWalletDid(pushUser) || pushUser.did;
|
|
359
|
+
if (receivers.includes(receiverDid) && pushUserDid !== receiverDid) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
url,
|
|
364
|
+
type,
|
|
365
|
+
receivers,
|
|
366
|
+
pushUser,
|
|
367
|
+
};
|
|
368
|
+
})
|
|
369
|
+
.filter(Boolean);
|
|
370
|
+
|
|
371
|
+
if (webhookList.length > 0 && channels.includes(NOTIFICATION_SEND_CHANNEL.WEBHOOK)) {
|
|
372
|
+
for (const webhook of webhookList) {
|
|
373
|
+
const { url, type, receivers } = webhook;
|
|
374
|
+
webhookQueue.push({
|
|
375
|
+
job: {
|
|
376
|
+
url,
|
|
377
|
+
notificationId: notification.id,
|
|
378
|
+
input: {
|
|
379
|
+
notification,
|
|
380
|
+
sender,
|
|
381
|
+
receivers: receivers.length > 0 ? receivers : [receiverDid],
|
|
382
|
+
webhook: {
|
|
383
|
+
url,
|
|
384
|
+
type,
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
delay: 5,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
// eslint-disable-next-line no-lonely-if
|
|
393
|
+
if (webhookList.length > 0) {
|
|
394
|
+
const webhookParams = {};
|
|
395
|
+
|
|
396
|
+
for (const { url, type, receivers } of webhookList) {
|
|
397
|
+
webhookParams[url] = {
|
|
398
|
+
type,
|
|
399
|
+
sendAt: new Date(),
|
|
400
|
+
status: NOTIFICATION_SEND_STATUS.FAILED,
|
|
401
|
+
failedReason: NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// eslint-disable-next-line no-await-in-loop
|
|
405
|
+
await updateNotificationSendStatus({
|
|
406
|
+
...commonParams,
|
|
407
|
+
receivers: receivers.length > 0 ? receivers : [receiverDid],
|
|
408
|
+
channel: NOTIFICATION_SEND_CHANNEL.WEBHOOK,
|
|
409
|
+
status: NOTIFICATION_SEND_STATUS.FAILED,
|
|
410
|
+
webhookParams: { [url]: webhookParams[url] },
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const insertToPushKitPushQueue = (props, nodeInfo) => {
|
|
418
|
+
const { channels, notification, sender, userInfo, teamDid, options = {} } = props;
|
|
419
|
+
|
|
420
|
+
const receiverDid = getWalletDid(userInfo) || userInfo.did;
|
|
421
|
+
|
|
422
|
+
const commonParams = {
|
|
423
|
+
node,
|
|
424
|
+
teamDid: teamDid ?? nodeInfo.did,
|
|
425
|
+
notificationId: notification.id,
|
|
426
|
+
};
|
|
427
|
+
const pushEnabled = get(userInfo, 'extra.notifications.push', true);
|
|
428
|
+
if (pushEnabled && channels.includes(NOTIFICATION_SEND_CHANNEL.PUSH)) {
|
|
429
|
+
pushKitPushQueue.push({
|
|
430
|
+
notification,
|
|
431
|
+
sender,
|
|
432
|
+
options,
|
|
433
|
+
receiver: receiverDid,
|
|
434
|
+
});
|
|
435
|
+
} else {
|
|
436
|
+
updateNotificationSendStatus({
|
|
437
|
+
...commonParams,
|
|
438
|
+
receivers: [receiverDid],
|
|
439
|
+
channel: NOTIFICATION_SEND_CHANNEL.PUSH,
|
|
440
|
+
status: NOTIFICATION_SEND_STATUS.FAILED,
|
|
441
|
+
failedReason: !pushEnabled
|
|
442
|
+
? NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED
|
|
443
|
+
: NOTIFICATION_SEND_FAILED_REASON.USER_DISABLED,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const insertToWalletPushQueue = (props, nodeInfo) => {
|
|
449
|
+
const { channels, notification, sender, userInfo, teamDid, options = {} } = props;
|
|
450
|
+
|
|
451
|
+
const receiverDid = getWalletDid(userInfo) || userInfo.did;
|
|
452
|
+
|
|
453
|
+
const commonParams = {
|
|
454
|
+
node,
|
|
455
|
+
teamDid: teamDid ?? nodeInfo.did,
|
|
456
|
+
notificationId: notification.id,
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const walletEnabled = get(userInfo, 'extra.notifications.wallet', true);
|
|
460
|
+
if (walletEnabled && channels.includes(NOTIFICATION_SEND_CHANNEL.WALLET)) {
|
|
461
|
+
walletPushQueue.push({
|
|
462
|
+
notification,
|
|
463
|
+
sender,
|
|
464
|
+
options,
|
|
465
|
+
receiver: receiverDid,
|
|
466
|
+
});
|
|
467
|
+
} else {
|
|
468
|
+
updateNotificationSendStatus({
|
|
469
|
+
...commonParams,
|
|
470
|
+
receivers: [receiverDid],
|
|
471
|
+
channel: NOTIFICATION_SEND_CHANNEL.WALLET,
|
|
472
|
+
status: NOTIFICATION_SEND_STATUS.FAILED,
|
|
473
|
+
failedReason: !walletEnabled
|
|
474
|
+
? NOTIFICATION_SEND_FAILED_REASON.CHANNEL_DISABLED
|
|
475
|
+
: NOTIFICATION_SEND_FAILED_REASON.USER_DISABLED,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const insertToNotificationReceiver = async (nodeInfo, userInfo, teamDid, notificationId, channels) => {
|
|
481
|
+
const receiverDid = getWalletDid(userInfo) || userInfo.did;
|
|
482
|
+
|
|
483
|
+
const receiverInstance = {
|
|
484
|
+
receiver: receiverDid,
|
|
485
|
+
notificationId,
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const emailEnabled = get(userInfo, 'extra.notifications.email', true);
|
|
489
|
+
const email = userInfo?.email;
|
|
490
|
+
|
|
491
|
+
if (emailEnabled && email && channels.includes(NOTIFICATION_SEND_CHANNEL.EMAIL)) {
|
|
492
|
+
receiverInstance.email = email;
|
|
493
|
+
}
|
|
494
|
+
const isServer = teamDid === nodeInfo.did;
|
|
495
|
+
|
|
496
|
+
let webhooks = get(userInfo, 'extra.webhooks', []);
|
|
497
|
+
if (isServer) {
|
|
498
|
+
webhooks = await getServerWebhooks();
|
|
499
|
+
}
|
|
500
|
+
const webhookMap = new Map();
|
|
501
|
+
webhooks.forEach((webhook) => {
|
|
502
|
+
if (!webhookMap.has(webhook.url)) {
|
|
503
|
+
webhookMap.set(webhook.url, webhook.type ?? 'api');
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
if (webhookMap.size > 0 && channels.includes(NOTIFICATION_SEND_CHANNEL.WEBHOOK)) {
|
|
508
|
+
receiverInstance.webhookUrls = Array.from(webhookMap.keys()).join('#');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
await node.createNotificationReceiver({
|
|
512
|
+
teamDid,
|
|
513
|
+
receiverInstance,
|
|
514
|
+
});
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* job 格式
|
|
519
|
+
* {
|
|
520
|
+
* channels: 需要发送到的 channels,
|
|
521
|
+
* notification: {},
|
|
522
|
+
* receivers: 接收的 receiver 列表
|
|
523
|
+
* }
|
|
524
|
+
*/
|
|
525
|
+
const queue = createNotificationQueue('notification-receivers', {}, async (job) => {
|
|
526
|
+
const { teamDid, channels, receiver, sender, notification, nodeInfo, userInfo, emailMap, webhookMap, ...rest } =
|
|
527
|
+
job;
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
if (!userInfo) {
|
|
531
|
+
logger.error('Invalid receiver user', { receiver });
|
|
532
|
+
throw new Error('Invalid receiver user');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (!job.pushOnly) {
|
|
536
|
+
// 如果添加失败,那么需要终止执行推送
|
|
537
|
+
await insertToNotificationReceiver(nodeInfo, userInfo, teamDid, notification.id, channels);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const baseParams = {
|
|
541
|
+
...rest,
|
|
542
|
+
teamDid,
|
|
543
|
+
channels,
|
|
544
|
+
receiver,
|
|
545
|
+
sender,
|
|
546
|
+
notification,
|
|
547
|
+
userInfo,
|
|
548
|
+
nodeInfo,
|
|
549
|
+
};
|
|
550
|
+
// 每个渠道之间的推送不会影响
|
|
551
|
+
// 添加到 wallet 推送队列
|
|
552
|
+
try {
|
|
553
|
+
insertToWalletPushQueue(baseParams, nodeInfo);
|
|
554
|
+
} catch (error) {
|
|
555
|
+
logger.error('Failed to insert to wallet push queue', { error });
|
|
556
|
+
}
|
|
557
|
+
// 添加到 push 推送队列
|
|
558
|
+
try {
|
|
559
|
+
insertToPushKitPushQueue(baseParams, nodeInfo);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
logger.error('Failed to insert to push kit push queue', { error });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 添加到 email 推送队列
|
|
565
|
+
try {
|
|
566
|
+
insertToEmailPushQueue(baseParams, nodeInfo, emailMap);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
logger.error('Failed to insert to email push queue', { error });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
//
|
|
572
|
+
try {
|
|
573
|
+
insertToWebhookPushQueue(baseParams, nodeInfo, webhookMap);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
logger.error('Failed to insert to webhook push queue', { error });
|
|
576
|
+
}
|
|
577
|
+
} catch (error) {
|
|
578
|
+
logger.error('Failed to create notification receiver', { error });
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// 监听 notification_create 事件
|
|
583
|
+
eventHub.on(EVENTS.NOTIFICATION_CREATE_QUEUED, async (data) => {
|
|
584
|
+
// Only first worker process handle blocklet event
|
|
585
|
+
try {
|
|
586
|
+
if (process.env.NODE_ENV !== 'test' && process.env.NODE_APP_INSTANCE !== '0') {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (!data.notification) {
|
|
590
|
+
logger.error('Invalid notification', { data });
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const nodeInfo = await node.getNodeInfo({ useCache: true });
|
|
594
|
+
if (nodeInfo.mode !== NODE_MODES.DEBUG) {
|
|
595
|
+
try {
|
|
596
|
+
await validateNotification(data.notification);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
logger.error('Failed to validate notification', { error });
|
|
599
|
+
// 抛出错误,阻止后续执行
|
|
600
|
+
throw error;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 按照 receiver 添加到推送队列
|
|
605
|
+
const { receivers, teamDid, channels, ...rest } = data;
|
|
606
|
+
const sender = data.sender ?? {
|
|
607
|
+
appDid: nodeInfo.did,
|
|
608
|
+
appSk: nodeInfo.sk,
|
|
609
|
+
type: 'server',
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const isServer = teamDid === nodeInfo.did;
|
|
613
|
+
|
|
614
|
+
const users = await node.getNotificationReceivers({
|
|
615
|
+
teamDid,
|
|
616
|
+
userDids: receivers,
|
|
617
|
+
enableConnectedAccounts: true,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const webhookMap = new Map();
|
|
621
|
+
const emailMap = new Map();
|
|
622
|
+
const serverWebhooks = await getServerWebhooks();
|
|
623
|
+
|
|
624
|
+
users.forEach((userInfo) => {
|
|
625
|
+
const receiverDid = getWalletDid(userInfo) || userInfo.did;
|
|
626
|
+
|
|
627
|
+
// 获取 email 列表
|
|
628
|
+
const { email } = userInfo;
|
|
629
|
+
const enabledEmail = get(userInfo, 'extra.notifications.email', true);
|
|
630
|
+
if (enabledEmail && email) {
|
|
631
|
+
const emailEntry = emailMap.get(email) || { receivers: [], pushUser: userInfo };
|
|
632
|
+
emailEntry.receivers = [...emailEntry.receivers, receiverDid];
|
|
633
|
+
emailEntry.pushUser = userInfo;
|
|
634
|
+
emailMap.set(email, emailEntry);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// 获取 webhook 列表
|
|
638
|
+
const webhooks = isServer ? serverWebhooks : get(userInfo, 'extra.webhooks', []);
|
|
639
|
+
webhooks.forEach((webhook) => {
|
|
640
|
+
const webhookEntry = webhookMap.get(webhook.url) || {
|
|
641
|
+
type: webhook.type ?? 'api',
|
|
642
|
+
receivers: [],
|
|
643
|
+
pushUser: userInfo,
|
|
644
|
+
};
|
|
645
|
+
webhookEntry.receivers = [...webhookEntry.receivers, receiverDid];
|
|
646
|
+
webhookEntry.pushUser = userInfo;
|
|
647
|
+
webhookMap.set(webhook.url, webhookEntry);
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
users.forEach((userInfo) => {
|
|
652
|
+
const receiverDid = getWalletDid(userInfo) || userInfo.did;
|
|
653
|
+
queue.push({
|
|
654
|
+
...rest,
|
|
655
|
+
teamDid,
|
|
656
|
+
channels,
|
|
657
|
+
receiver: receiverDid,
|
|
658
|
+
sender,
|
|
659
|
+
nodeInfo,
|
|
660
|
+
userInfo,
|
|
661
|
+
emailMap,
|
|
662
|
+
webhookMap,
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
} catch (error) {
|
|
666
|
+
logger.error('Failed to create notification receiver', { error });
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
module.exports = { init };
|