@abtnode/blocklet-services 1.16.18-beta-aa01bd8e → 1.16.18-beta-bb4ebacc

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 (97) hide show
  1. package/api/index.js +1 -7
  2. package/api/routes/env.js +4 -2
  3. package/api/services/auth/index.js +77 -13
  4. package/api/services/auth/session.js +48 -1
  5. package/api/services/notification/blocklet-events-notifier.js +1 -0
  6. package/api/socket/channel/component.js +6 -5
  7. package/api/socket/channel/did.js +51 -20
  8. package/api/socket/util.js +1 -0
  9. package/build/asset-manifest.json +99 -87
  10. package/build/index.html +1 -1
  11. package/build/service-worker.js +1 -1
  12. package/build/service-worker.js.map +1 -1
  13. package/build/static/css/{4448.de9040c4.chunk.css → 1233.dba37e64.chunk.css} +1 -1
  14. package/build/static/js/{1148.e614f706.chunk.js → 1148.5ccba08e.chunk.js} +2 -2
  15. package/build/static/js/{4448.f0c62086.chunk.js → 1233.276cf2d0.chunk.js} +3 -3
  16. package/build/static/js/{4448.f0c62086.chunk.js.LICENSE.txt → 1233.276cf2d0.chunk.js.LICENSE.txt} +6 -0
  17. package/build/static/js/2653.980158ea.chunk.js +2 -0
  18. package/build/static/js/2664.da443f31.chunk.js +2 -0
  19. package/build/static/js/2801.33d2f238.chunk.js +2 -0
  20. package/build/static/js/2940.ce32ab3f.chunk.js +2 -0
  21. package/build/static/js/3025.7c99c058.chunk.js +2 -0
  22. package/build/static/js/{3033.6623d19d.chunk.js → 3033.9fe46d7f.chunk.js} +2 -2
  23. package/build/static/js/{3131.672170fb.chunk.js → 3131.99541aca.chunk.js} +2 -2
  24. package/build/static/js/3430.dc830483.chunk.js +2 -0
  25. package/build/static/js/{3688.48e7b69c.chunk.js → 3688.679796d1.chunk.js} +2 -2
  26. package/build/static/js/3708.7e2ad66b.chunk.js +3 -0
  27. package/build/static/js/3842.e1bb702b.chunk.js +3 -0
  28. package/build/static/js/3953.ddf8be86.chunk.js +2 -0
  29. package/build/static/js/{4023.a2e9db00.chunk.js → 4023.5fe8180a.chunk.js} +2 -2
  30. package/build/static/js/4076.5ca97365.chunk.js +2 -0
  31. package/build/static/js/4160.e45b5ba1.chunk.js +2 -0
  32. package/build/static/js/4587.9a042d46.chunk.js +2 -0
  33. package/build/static/js/4802.217e3956.chunk.js +2 -0
  34. package/build/static/js/5070.31a74ba7.chunk.js +2 -0
  35. package/build/static/js/5468.b54ce65b.chunk.js +2 -0
  36. package/build/static/js/5547.d68bfde3.chunk.js +3 -0
  37. package/build/static/js/556.e1875260.chunk.js +3 -0
  38. package/build/static/js/6032.1001afd3.chunk.js +2 -0
  39. package/build/static/js/{6139.5867193a.chunk.js → 6139.161b2e7f.chunk.js} +2 -2
  40. package/build/static/js/6218.9a4feced.chunk.js +2 -0
  41. package/build/static/js/{6658.090d923f.chunk.js → 6658.98f9956d.chunk.js} +2 -2
  42. package/build/static/js/{716.9c76ad65.chunk.js → 716.e68425d7.chunk.js} +3 -3
  43. package/build/static/js/716.e68425d7.chunk.js.LICENSE.txt +5 -0
  44. package/build/static/js/7305.465df4a1.chunk.js +2 -0
  45. package/build/static/js/7313.b53b59d8.chunk.js +2 -0
  46. package/build/static/js/779.73350f02.chunk.js +2 -0
  47. package/build/static/js/7858.52930f63.chunk.js +2 -0
  48. package/build/static/js/8016.3ef64a2c.chunk.js +2 -0
  49. package/build/static/js/{8181.86477cc5.chunk.js → 8181.6c2a7dcb.chunk.js} +2 -2
  50. package/build/static/js/8393.ea7ef05d.chunk.js +2 -0
  51. package/build/static/js/840.5bc210dd.chunk.js +2 -0
  52. package/build/static/js/{8622.66a81bc0.chunk.js → 8622.ebd44812.chunk.js} +2 -2
  53. package/build/static/js/8641.bec13444.chunk.js +2 -0
  54. package/build/static/js/8702.bb84d009.chunk.js +2 -0
  55. package/build/static/js/{8792.8b009f81.chunk.js → 8792.0f55707c.chunk.js} +2 -2
  56. package/build/static/js/8944.559ade67.chunk.js +2 -0
  57. package/build/static/js/9033.ebf65926.chunk.js +2 -0
  58. package/build/static/js/9107.7f32925d.chunk.js +2 -0
  59. package/build/static/js/9314.f0add972.chunk.js +2 -0
  60. package/build/static/js/{9596.5251398b.chunk.js → 9596.a4eead04.chunk.js} +2 -2
  61. package/build/static/js/9865.d5dfa72b.chunk.js +2 -0
  62. package/build/static/js/{9900.cce3f786.chunk.js → 9900.3b72165e.chunk.js} +2 -2
  63. package/build/static/js/main.36b0deea.js +3 -0
  64. package/package.json +27 -26
  65. package/build/static/js/1343.69031e0b.chunk.js +0 -2
  66. package/build/static/js/1581.ed4544c6.chunk.js +0 -2
  67. package/build/static/js/2026.514f995a.chunk.js +0 -2
  68. package/build/static/js/2362.ab5308a8.chunk.js +0 -2
  69. package/build/static/js/2506.dd9284b6.chunk.js +0 -2
  70. package/build/static/js/2653.6610534f.chunk.js +0 -2
  71. package/build/static/js/2972.2d5268c3.chunk.js +0 -2
  72. package/build/static/js/3025.aa129833.chunk.js +0 -2
  73. package/build/static/js/3038.40d611d1.chunk.js +0 -2
  74. package/build/static/js/3683.6f169a32.chunk.js +0 -2
  75. package/build/static/js/3920.5c63a2b9.chunk.js +0 -3
  76. package/build/static/js/4076.bcc72fcc.chunk.js +0 -2
  77. package/build/static/js/4247.c25f1945.chunk.js +0 -2
  78. package/build/static/js/4587.906e3023.chunk.js +0 -2
  79. package/build/static/js/4716.a8a5b75d.chunk.js +0 -2
  80. package/build/static/js/4764.51208c0d.chunk.js +0 -2
  81. package/build/static/js/4802.cd83ecef.chunk.js +0 -2
  82. package/build/static/js/5547.8447edf9.chunk.js +0 -3
  83. package/build/static/js/556.4d5cc702.chunk.js +0 -3
  84. package/build/static/js/6032.48c0a152.chunk.js +0 -2
  85. package/build/static/js/7050.3281ad5f.chunk.js +0 -2
  86. package/build/static/js/779.383b4b4a.chunk.js +0 -2
  87. package/build/static/js/7858.4e5b7c88.chunk.js +0 -2
  88. package/build/static/js/8702.2d858942.chunk.js +0 -2
  89. package/build/static/js/8944.07bcf75f.chunk.js +0 -2
  90. package/build/static/js/8983.19b79a23.chunk.js +0 -2
  91. package/build/static/js/9880.13ac9520.chunk.js +0 -2
  92. package/build/static/js/main.63103d24.js +0 -3
  93. /package/build/static/js/{3920.5c63a2b9.chunk.js.LICENSE.txt → 3708.7e2ad66b.chunk.js.LICENSE.txt} +0 -0
  94. /package/build/static/js/{716.9c76ad65.chunk.js.LICENSE.txt → 3842.e1bb702b.chunk.js.LICENSE.txt} +0 -0
  95. /package/build/static/js/{5547.8447edf9.chunk.js.LICENSE.txt → 5547.d68bfde3.chunk.js.LICENSE.txt} +0 -0
  96. /package/build/static/js/{556.4d5cc702.chunk.js.LICENSE.txt → 556.e1875260.chunk.js.LICENSE.txt} +0 -0
  97. /package/build/static/js/{main.63103d24.js.LICENSE.txt → main.36b0deea.js.LICENSE.txt} +0 -0
package/api/index.js CHANGED
@@ -162,11 +162,6 @@ module.exports = function createServer(node, serverOptions = {}) {
162
162
  logger.error('send to component error', { error });
163
163
  });
164
164
  }
165
-
166
- // backward compatibility, should be removed if BlockletSDK@1.6.14 is not supported anymore
167
- notificationService.sendToApp.exec({ event, appDid, data }).catch((error) => {
168
- logger.error('send to app error', { error });
169
- });
170
165
  });
171
166
  });
172
167
 
@@ -285,7 +280,7 @@ module.exports = function createServer(node, serverOptions = {}) {
285
280
  server.use(
286
281
  `${WELLKNOWN_SERVICE_PATH_PREFIX}${pathname}`,
287
282
  checkMemberPermission,
288
- proxyToDaemon({ proxy, pathname, ...options })
283
+ proxyToDaemon({ proxy, ...options })
289
284
  );
290
285
  });
291
286
 
@@ -463,7 +458,6 @@ module.exports = function createServer(node, serverOptions = {}) {
463
458
 
464
459
  BlockletEventsNotifier.init({ node, notificationService });
465
460
 
466
- server.sendToApp = ({ event, appDid, data }) => notificationService.sendToApp.exec({ event, appDid, data });
467
461
  server.sendToAppComponents = ({ event, appDid, data }) =>
468
462
  notificationService.sendToAppComponents.exec({ event, appDid, data });
469
463
 
package/api/routes/env.js CHANGED
@@ -30,11 +30,13 @@ module.exports = {
30
30
  apiPrefix: "${pathPrefix.replace(/\/+$/, '')}${WELLKNOWN_SERVICE_PATH_PREFIX}",
31
31
  ${groupPathPrefix ? `groupPathPrefix: "${groupPathPrefix}",` : ''}
32
32
  webWalletUrl: "${info.webWalletUrl || config.webWalletUrl || opts.webWalletUrl}",
33
- nftDomainUrl: "${info.nftDomainUrl}",
33
+ nftDomainUrl: "${info.nftDomainUrl || ''}",
34
34
  passportColor: "${passportColor}",
35
35
  serverDid: "${info.did}",
36
36
  serverVersion: "${info.version}",
37
- mode: "${info.mode}"
37
+ mode: "${info.mode}",
38
+ ownerNft: ${JSON.stringify(info.ownerNft || '')},
39
+ launcher: ${JSON.stringify(info.launcher || '')}
38
40
  }`);
39
41
  });
40
42
  },
@@ -84,12 +84,16 @@ const init = ({ node, options }) => {
84
84
  };
85
85
 
86
86
  /**
87
- * @returns {object} res
88
- * {boolean} res.blocked
89
- * {boolean} res.authenticated
90
- * {boolean} res.authorized
91
- * {boolean} res.ignored
92
- * {string} res.payable
87
+ * @typedef {object} CheckAuthRes
88
+ * @property {boolean} blocked
89
+ * @property {boolean} authenticated
90
+ * @property {boolean} authorized
91
+ * @property {boolean} ignored
92
+ * @property {string} payable
93
+ */
94
+
95
+ /**
96
+ * @returns {CheckAuthRes} res
93
97
  */
94
98
  const checkAuth = async ({ req } = {}) => {
95
99
  const config = (await req.getServiceConfig(NODE_SERVICES.AUTH)) || {};
@@ -107,24 +111,74 @@ const init = ({ node, options }) => {
107
111
 
108
112
  const teamDid = req.getBlockletDid();
109
113
 
114
+ // 所有人都可以访问的情况
110
115
  if (!config.whoCanAccess || config.whoCanAccess === WHO_CAN_ACCESS.ALL) {
111
116
  if (config.blockUnauthenticated && !req.user) {
112
- return { blocked: true, authenticated: false };
117
+ return {
118
+ blocked: true,
119
+ authenticated: false,
120
+ };
113
121
  }
114
122
  } else if (!req.user) {
123
+ // 需要被邀请才能访问的情况
115
124
  const rbac = await node.getRBAC(teamDid);
116
125
  const allRoles = await rbac.getRoles(teamDid);
117
126
  const payableRole = allRoles.find((x) => x.extra?.acquire?.pay);
118
- return { blocked: true, authenticated: false, payable: payableRole?.extra?.acquire?.pay };
127
+ return {
128
+ blocked: true,
129
+ authenticated: false,
130
+ payable: payableRole?.extra?.acquire?.pay,
131
+ };
119
132
  } else if (config.whoCanAccess === WHO_CAN_ACCESS.OWNER && req.user.role !== ROLES.OWNER) {
120
- return { blocked: true, authenticated: true, authorized: false };
133
+ // 需要 owner 才能访问
134
+ return {
135
+ blocked: true,
136
+ authenticated: true,
137
+ authorized: false,
138
+ requiredRoles: [
139
+ {
140
+ name: ROLES.OWNER,
141
+ title: 'Owner',
142
+ description: 'Owner',
143
+ },
144
+ ],
145
+ };
121
146
  } else if (config.whoCanAccess.startsWith(WHO_CAN_ACCESS_PREFIX_ROLES)) {
147
+ // 指定 passport 才能访问
148
+ // 需要用户拥有的权限
122
149
  const roles = getRolesFromAuthConfig(config);
123
150
  if (!roles.includes(req.user.role)) {
124
151
  const rbac = await node.getRBAC(teamDid);
152
+ // 系统中的所有权限
125
153
  const allRoles = await rbac.getRoles(teamDid);
126
- const payableRole = allRoles.find((x) => roles.includes(x.name) && x.extra?.acquire?.pay);
127
- return { blocked: true, authenticated: true, authorized: false, payable: payableRole?.extra?.acquire?.pay };
154
+
155
+ return {
156
+ blocked: true,
157
+ authenticated: true,
158
+ authorized: false,
159
+ requiredRoles: roles
160
+ .map((item) => {
161
+ if (item.name === ROLES.OWNER) {
162
+ return {
163
+ name: item.name,
164
+ title: 'Owner',
165
+ description: 'Owner',
166
+ };
167
+ }
168
+ const findRole = allRoles.find((x) => x.name === item);
169
+ if (!findRole) {
170
+ return null;
171
+ }
172
+
173
+ return {
174
+ name: findRole.name,
175
+ title: findRole.title,
176
+ description: findRole.description,
177
+ payable: findRole?.extra?.acquire?.pay,
178
+ };
179
+ })
180
+ .filter((x) => x),
181
+ };
128
182
  }
129
183
  }
130
184
 
@@ -254,7 +308,7 @@ const init = ({ node, options }) => {
254
308
  };
255
309
 
256
310
  middlewares.checkAuth = async (req, res, next) => {
257
- const { blocked, authenticated, authorized, payable } = await checkAuth({ req });
311
+ const { blocked, authenticated, authorized, payable, requiredRoles } = await checkAuth({ req });
258
312
 
259
313
  if (blocked) {
260
314
  if (!authenticated) {
@@ -269,7 +323,17 @@ const init = ({ node, options }) => {
269
323
 
270
324
  if (!authorized) {
271
325
  if (req.accepts(['html', 'json']) === 'html') {
272
- res.redirect(getRedirectUrl({ req, pagePath: '/login', params: { authenticated: 1, payable } }));
326
+ res.redirect(
327
+ getRedirectUrl({
328
+ req,
329
+ pagePath: '/login',
330
+ params: {
331
+ authenticated: 1,
332
+ payable,
333
+ requiredRoles: JSON.stringify(requiredRoles),
334
+ },
335
+ })
336
+ );
273
337
  } else {
274
338
  // Security principles: user should not known the reason
275
339
  res.status(404).json({ code: 404, error: REASON_404 });
@@ -1,6 +1,8 @@
1
1
  const nocache = require('nocache');
2
2
  const joinUrl = require('url-join');
3
3
  const pick = require('lodash/pick');
4
+ const omit = require('lodash/omit');
5
+ const merge = require('lodash/merge');
4
6
  const {
5
7
  WELLKNOWN_SERVICE_PATH_PREFIX,
6
8
  USER_AVATAR_URL_PREFIX,
@@ -9,6 +11,7 @@ const {
9
11
  } = require('@abtnode/constant');
10
12
  const { LOGIN_PROVIDER } = require('@blocklet/constant');
11
13
 
14
+ const isUrl = require('is-url');
12
15
  const { PREFIXES } = require('../../util/constants');
13
16
  const { createTokenFn, getDidConnectVersion } = require('../../util');
14
17
 
@@ -57,7 +60,7 @@ module.exports = {
57
60
  .map((x) => pick(x, ['id', 'name', 'title', 'role']));
58
61
 
59
62
  res.json({
60
- user,
63
+ user: omit(user, ['extra']),
61
64
  provider: req.user.provider || LOGIN_PROVIDER.WALLET,
62
65
  walletOS: req.user.walletOS,
63
66
  });
@@ -67,6 +70,50 @@ module.exports = {
67
70
  router.get(sessionApi, nocache(), sessionBearerToken, handleSession);
68
71
  router.post(sessionApi, nocache(), sessionBearerToken, handleSession);
69
72
 
73
+ // update user extra: settings, webhooks
74
+ const extraApi = `${prefix}/api/user/extra`;
75
+ const checkUser = async (req, res, next) => {
76
+ const { token } = req;
77
+ await ensureUser({ req, token });
78
+ if (!req.user) {
79
+ res.status(403).json(null);
80
+ } else {
81
+ next();
82
+ }
83
+ };
84
+ router.get(extraApi, nocache(), sessionBearerToken, checkUser, async (req, res) => {
85
+ const teamDid = req.getBlockletDid();
86
+ const user = await node.getUser({ teamDid, user: { did: req.user.did } });
87
+ res.json(user.extra || {});
88
+ });
89
+ router.post(extraApi, nocache(), sessionBearerToken, checkUser, async (req, res) => {
90
+ const teamDid = req.getBlockletDid();
91
+ const exist = await node.getUser({ teamDid, user: { did: req.user.did } });
92
+ const user = await node.updateUserExtra({
93
+ teamDid,
94
+ did: req.user.did,
95
+ extra: JSON.stringify(merge({}, exist.extra || {}, req.body)),
96
+ });
97
+ res.json(user.extra);
98
+ });
99
+ router.put(extraApi, nocache(), sessionBearerToken, checkUser, async (req, res) => {
100
+ if (['slack', 'api'].includes(req.body.type) === false) {
101
+ res.status(400).send({ error: 'invalid webhook type' });
102
+ return;
103
+ }
104
+
105
+ if (isUrl(req.body.url) === false) {
106
+ res.status(400).send({ error: 'invalid webhook url' });
107
+ }
108
+
109
+ await node.sendTestMessage({
110
+ webhook: { type: req.body.type, params: [{ name: 'url', value: req.body.url }] },
111
+ message: `This is a test message from user ${req.user.did}`,
112
+ });
113
+
114
+ res.json({ success: true });
115
+ });
116
+
70
117
  router.post(`${prefix}/api/did/refreshSession`, refreshBearerToken, async (req, res) => {
71
118
  const token = req.refreshToken;
72
119
  if (token) {
@@ -189,6 +189,7 @@ const init = ({ node, notificationService }) => {
189
189
  notification: {
190
190
  title: message.title[locale] || message.title[DEFAULT_LOCALE],
191
191
  body: message.body[locale] || message.body[DEFAULT_LOCALE],
192
+ source: 'app',
192
193
  },
193
194
  };
194
195
  notificationService.sendToUser.exec(input);
@@ -100,17 +100,18 @@ const sendToAppComponents = async ({ event, appDid, componentDid: inputComponent
100
100
  const componentDid = component.meta.did;
101
101
 
102
102
  if (!inputComponentDid || componentDid === inputComponentDid) {
103
- // realAppDid is diff with appDid when app development mode
104
- const realAppDid = app.appDid || appDid;
103
+ // appPid is diff with appDid when app development mode
104
+ // appPid is diff with appDid when app has been rotated
105
+ const appPid = app.appPid || appDid;
105
106
 
106
107
  // eslint-disable-next-line no-loop-func
107
- broadcast(wsServer, getComponentChannel(realAppDid, componentDid), event, notification, async (count) => {
108
+ broadcast(wsServer, getComponentChannel(appPid, componentDid), event, notification, async (count) => {
108
109
  // FIXME @linchen 组件以 cluster 模式启动时, 是否确保所有组件实例都收到消息?
109
110
  if (count <= 0) {
110
- logger.info('Online component client was not found', { realAppDid, componentDid });
111
+ logger.info('Online component client was not found', { appPid, componentDid });
111
112
  await lock.acquire();
112
113
  try {
113
- await states.message.insert({ did: getCacheId(realAppDid, componentDid), event, data: notification });
114
+ await states.message.insert({ did: getCacheId(appPid, componentDid), event, data: notification });
114
115
  lock.release();
115
116
  } catch (error) {
116
117
  lock.release();
@@ -8,6 +8,9 @@ const { NODE_MODES } = require('@abtnode/constant');
8
8
  const { getWalletDid } = require('@blocklet/sdk/lib/did');
9
9
  const JWT = require('@arcblock/jwt');
10
10
  const pMap = require('p-map');
11
+ const uniqBy = require('lodash/uniqBy');
12
+ const uniq = require('lodash/uniq');
13
+ const get = require('lodash/get');
11
14
 
12
15
  // eslint-disable-next-line global-require
13
16
  const logger = require('@abtnode/logger')(`${require('../../../package.json').name}:notification`);
@@ -34,21 +37,25 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
34
37
  const { keepForOfflineUser = true } = options || {};
35
38
  const receiver = Array.isArray(rawDid) ? rawDid : [rawDid];
36
39
 
37
- const receiverDidList = [];
38
- const receiverEmailList = [];
40
+ let receiverDidList = [];
41
+ let receiverEmailList = [];
42
+ let webhookList = [];
43
+
44
+ const webhookSenders = new Map();
39
45
 
40
46
  // sender.appDid 就是当前 blockletDid,通知的发送方就是这个 blocklet 本身
41
47
  const teamDid = sender.appDid;
42
48
 
43
49
  await pMap(receiver, async (item) => {
44
- const userInfoItem = await node.getUser({
45
- teamDid,
46
- user: { did: item },
47
- options: { enableConnectedAccount: true },
48
- });
49
- const receiverDid = getWalletDid(userInfoItem) || item;
50
- const receiverEmail = userInfoItem?.email;
51
- if (receiverDid) {
50
+ const user = await node.getUser({ teamDid, user: { did: item }, options: { enableConnectedAccount: true } });
51
+ const walletEnabled = get(user, 'extra.notifications.wallet', true);
52
+ const emailEnabled = get(user, 'extra.notifications.email', true);
53
+ const webhooks = get(user, 'extra.webhooks', []);
54
+
55
+ const receiverDid = getWalletDid(user) || item;
56
+ const receiverEmail = user?.email;
57
+
58
+ if (receiverDid && walletEnabled) {
52
59
  try {
53
60
  await validateReceiver(receiverDid);
54
61
  receiverDidList.push(receiverDid);
@@ -56,17 +63,21 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
56
63
  /* empty */
57
64
  }
58
65
  }
59
- if (receiverEmail) {
66
+ if (receiverEmail && emailEnabled) {
60
67
  try {
61
68
  await validateEmail(receiverEmail);
62
69
  receiverEmailList.push({
63
70
  email: receiverEmail,
64
- locale: userInfoItem?.locale || 'en',
71
+ locale: user?.locale || 'en',
65
72
  });
66
73
  } catch {
67
74
  /* empty */
68
75
  }
69
76
  }
77
+
78
+ if (Array.isArray(webhooks)) {
79
+ webhookList.push(...webhooks.filter((x) => x.type && x.url));
80
+ }
70
81
  });
71
82
 
72
83
  if (receiverDidList.length === 0 && receiverEmailList.length === 0) {
@@ -85,6 +96,11 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
85
96
  // parse notification
86
97
  const notifications = parseNotification(notification, senderInfo);
87
98
 
99
+ // uniq receivers
100
+ receiverDidList = uniq(receiverDidList);
101
+ receiverEmailList = uniqBy(receiverEmailList, 'email');
102
+ webhookList = uniqBy(webhookList, 'url');
103
+
88
104
  // send notification
89
105
  notifications.forEach((data) => {
90
106
  for (const receiverDid of receiverDidList) {
@@ -95,14 +111,29 @@ const sendToUserDid = async ({ sender, receiver: rawDid, notification, options,
95
111
  }
96
112
  });
97
113
  }
98
- // NOTICE: 目前只有 notification 的通知能够发送邮件,并且 type 可能为 undefined
99
- if ([undefined, NOTIFICATION_TYPES.NOTIFICATION].includes(data.type)) {
100
- for (const receiverEmail of receiverEmailList) {
101
- sendEmail(receiverEmail.email, data, { teamDid: sender.appDid, node, locale: receiverEmail.locale }).catch(
102
- (error) => {
103
- logger.error('Failed to send email', { error });
104
- }
105
- );
114
+
115
+ // Do not send email for component activities
116
+ if (data.source !== 'app') {
117
+ // NOTICE: 目前只有 notification 的通知能够发送邮件,并且 type 可能为 undefined
118
+ if ([undefined, NOTIFICATION_TYPES.NOTIFICATION].includes(data.type)) {
119
+ for (const receiverEmail of receiverEmailList) {
120
+ sendEmail(receiverEmail.email, data, { teamDid: sender.appDid, node, locale: receiverEmail.locale }).catch(
121
+ (error) => {
122
+ logger.error('Failed to send email', { error });
123
+ }
124
+ );
125
+ }
126
+ }
127
+
128
+ // send webhook
129
+ for (const webhook of webhookList) {
130
+ let webhookSender = webhookSenders.get(webhook.type);
131
+ if (!webhookSender) {
132
+ webhookSender = node.getMessageSender(webhook.type);
133
+ webhookSenders.set(webhook.type, webhookSender);
134
+ }
135
+
136
+ webhookSender.sendNotification(webhook.url, notification);
106
137
  }
107
138
  }
108
139
  });
@@ -29,6 +29,7 @@ const parseNotification = (notification, senderInfo) => {
29
29
  did: senderInfo.permanentWallet.address,
30
30
  pk: senderInfo.permanentWallet.pk,
31
31
  name: senderInfo.name,
32
+ logo: senderInfo.logo,
32
33
  // actualDid is the did of the application that is used to decrypt the message if needed
33
34
  actualDid: senderInfo.wallet.address,
34
35
  actualPk: senderInfo.wallet.pk,