@abtnode/core 1.16.50 → 1.16.51-beta-20250905-023351-70af144b

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.
@@ -51,7 +51,7 @@ const init = ({ states, teamManager }) => {
51
51
  const { event } = data;
52
52
  const { type, source, object_id: objectId, object_type: objectType } = event;
53
53
 
54
- const request = event.data;
54
+ const request = { ...event.data, $eventName: type };
55
55
  logger.info('create webhook event', { type, source, request });
56
56
 
57
57
  const { webhookEventState } = await teamManager.getWebhookState(appDid);
@@ -11,12 +11,12 @@ const { updateConnectedAccount } = require('@abtnode/util/lib/user');
11
11
  const { LOGIN_PROVIDER } = require('@blocklet/constant');
12
12
  const { CustomError } = require('@blocklet/error');
13
13
  const { Joi } = require('@arcblock/validator');
14
-
15
14
  const logger = require('@abtnode/logger')('@abtnode/core:states:user');
16
15
 
17
16
  const { validateOwner } = require('../util');
18
17
  const { loginSchema, disconnectAccountSchema } = require('../validators/user');
19
18
  const ExtendBase = require('./base');
19
+ const { isInDashboard, isUserPrivacyEnabled, isAdminUser } = require('../util/verify-user-private');
20
20
 
21
21
  const isNullOrUndefined = (x) => x === undefined || x === null;
22
22
 
@@ -962,6 +962,8 @@ SELECT did,inviter,generation FROM UserTree`.trim();
962
962
 
963
963
  const { user: contextUser } = context;
964
964
 
965
+ const contextUserDid = contextUser?.userInfo?.did || contextUser?.did;
966
+
965
967
  await this._validateUsersExist([targetDid]);
966
968
 
967
969
  const isFollowers = type === 'followers';
@@ -977,15 +979,15 @@ SELECT did,inviter,generation FROM UserTree`.trim();
977
979
  const userDids = [...new Set(result.list.map((follow) => (isFollowers ? follow.followerDid : follow.userDid)))];
978
980
  const users = await this.find({
979
981
  where: { did: { [Op.in]: userDids } },
980
- attributes: ['did', 'fullName', 'avatar', 'email'],
982
+ attributes: ['did', 'fullName', 'avatar'],
981
983
  });
982
984
  userMap = new Map(users.map((user) => [user.did, user]));
983
985
  }
984
986
 
985
987
  // 准备关注状态映射
986
988
  let followingStatusMap = {};
987
- if (includeFollowStatus && contextUser?.did) {
988
- if (contextUser.did !== targetDid) {
989
+ if (includeFollowStatus && contextUserDid) {
990
+ if (contextUserDid !== targetDid) {
989
991
  // 查询当前用户对列表中所有用户的关注状态
990
992
  const targetUserDids = result.list.map((follow) => (isFollowers ? follow.followerDid : follow.userDid));
991
993
  followingStatusMap = await this.isFollowing(contextUser.did, targetUserDids);
@@ -1008,8 +1010,8 @@ SELECT did,inviter,generation FROM UserTree`.trim();
1008
1010
  }
1009
1011
 
1010
1012
  // 添加关注状态
1011
- if (includeFollowStatus && contextUser?.did) {
1012
- if (contextUser.did !== targetDid) {
1013
+ if (includeFollowStatus && contextUserDid) {
1014
+ if (contextUserDid !== targetDid) {
1013
1015
  // 如果查看的是别人的关注列表,需要添加是否已关注的信息
1014
1016
  enrichedFollow.isFollowing = followingStatusMap[userDid] || false;
1015
1017
  } else if (isFollowers) {
@@ -1087,7 +1089,7 @@ SELECT did,inviter,generation FROM UserTree`.trim();
1087
1089
  * @param {string[]} userDids - 用户的 DID 数组
1088
1090
  * @returns {Promise<{[userDid: string]: {followers: number, following: number}}>} - 返回对象,键为用户DID,值为统计信息
1089
1091
  */
1090
- async getFollowStats(userDids = []) {
1092
+ async getFollowStats({ userDids = [], teamDid, prefix }, context = {}) {
1091
1093
  if (!this.userFollowers || !Array.isArray(userDids) || userDids.length === 0) {
1092
1094
  return userDids.reduce(
1093
1095
  (acc, userDid) => ({
@@ -1099,6 +1101,20 @@ SELECT did,inviter,generation FROM UserTree`.trim();
1099
1101
  }
1100
1102
 
1101
1103
  try {
1104
+ const { user: contextUser } = context;
1105
+ const contextUserDid = contextUser?.userInfo?.did || contextUser?.did;
1106
+
1107
+ const privacyMap = new Map();
1108
+ if (!(isInDashboard(teamDid, prefix, context) && isAdminUser(context))) {
1109
+ // 批量获取用户隐私设置
1110
+ const usersInfo = await this.find({ did: { $in: userDids } }, { did: 1, extra: 1 });
1111
+
1112
+ usersInfo.forEach((userInfo) => {
1113
+ const isPrivate = isUserPrivacyEnabled(userInfo);
1114
+ privacyMap.set(userInfo.did, isPrivate);
1115
+ });
1116
+ }
1117
+
1102
1118
  // 使用原生 SQL 查询进行批量统计,提高性能
1103
1119
  const followersQuery = `
1104
1120
  SELECT "userDid", COUNT(*) as count
@@ -1123,15 +1139,25 @@ SELECT did,inviter,generation FROM UserTree`.trim();
1123
1139
  const followersMap = new Map(followersResults.map((row) => [row.userDid, Number(row.count)]));
1124
1140
  const followingMap = new Map(followingResults.map((row) => [row.followerDid, Number(row.count)]));
1125
1141
 
1126
- // 使用 Object.fromEntries 优化结果对象构建
1142
+ // 使用 Object.fromEntries 优化结果对象构建,并进行隐私判断
1127
1143
  const result = Object.fromEntries(
1128
- userDids.map((userDid) => [
1129
- userDid,
1130
- {
1131
- followers: followersMap.get(userDid) || 0,
1132
- following: followingMap.get(userDid) || 0,
1133
- },
1134
- ])
1144
+ userDids.map((userDid) => {
1145
+ const isPrivate = privacyMap.get(userDid) || false;
1146
+ const isOwner = contextUserDid === userDid;
1147
+
1148
+ // 如果用户设置了隐私且查看者不是用户本人,返回 0
1149
+ if (isPrivate && !isOwner) {
1150
+ return [userDid, { followers: 0, following: 0 }];
1151
+ }
1152
+
1153
+ return [
1154
+ userDid,
1155
+ {
1156
+ followers: followersMap.get(userDid) || 0,
1157
+ following: followingMap.get(userDid) || 0,
1158
+ },
1159
+ ];
1160
+ })
1135
1161
  );
1136
1162
 
1137
1163
  return result;
@@ -226,10 +226,14 @@ class TeamManager extends EventEmitter {
226
226
  userDids.map((did) => userState.getUser(did, { includePassports: true, enableConnectedAccount: true }))
227
227
  );
228
228
 
229
- const validUsers = queryUsers.filter((user) => {
229
+ const validUsers = queryUsers.filter((user, index) => {
230
+ if (!user) {
231
+ logger.warn('receiver is not exist: ', { userDid: userDids[index] });
232
+ return false;
233
+ }
230
234
  const { passports = [] } = user;
231
235
  // 如果用户没有 passport,或者 有一个 passport 没有过期,则返回 true
232
- if (!passports.length || passports.some((x) => !this.isPassportExpired(x))) {
236
+ if (!passports?.length || passports.some((x) => !this.isPassportExpired(x))) {
233
237
  return true;
234
238
  }
235
239
  logger.warn(`user's passports are all expired: ${user.did}`);
@@ -480,7 +484,7 @@ class TeamManager extends EventEmitter {
480
484
  ...(actorInfo ? { actorInfo } : {}),
481
485
  options: typeof payload.options === 'object' && payload.options !== null ? payload.options : {},
482
486
  });
483
- logger.info('notification has been added to the push queue', { notificationId: doc.id });
487
+ logger.info('notification has been added to the push queue', { teamDid, notificationId: doc.id });
484
488
  } catch (error) {
485
489
  logger.error('Failed to emit notification events', { error });
486
490
  throw new Error(`Failed to emit notification events: ${error.message}`);
@@ -538,14 +542,14 @@ class TeamManager extends EventEmitter {
538
542
 
539
543
  const doc = await this._createNotificationDoc(notificationState, payload, receivers, source, isServices, teamDid);
540
544
 
541
- logger.info('notification created successfully', { notificationId: doc.id });
545
+ logger.info('notification created successfully', { teamDid, notificationId: doc.id });
542
546
 
543
547
  const defaultChannel = this._getDefaultChannels(isServices, source);
544
548
  await this._emitNotificationEvents(doc, payload, receivers, teamDid, isServices, defaultChannel, actorInfo);
545
549
 
546
550
  return doc;
547
551
  } catch (error) {
548
- logger.error('notification create failed', { error });
552
+ logger.error('notification create failed', { teamDid, error });
549
553
  throw error;
550
554
  }
551
555
  }
@@ -104,7 +104,7 @@ const reportComponentsEvent = async ({ blocklet, dids, type, time }) => {
104
104
  }
105
105
  };
106
106
 
107
- const notifyBlockletUpdated = async (blocklet) => {
107
+ const notifyLauncher = async (type, blocklet) => {
108
108
  try {
109
109
  const { controller } = blocklet;
110
110
 
@@ -120,7 +120,7 @@ const notifyBlockletUpdated = async (blocklet) => {
120
120
  const { appLogo, appLogoRect } = getBlockletLogos(blocklet);
121
121
 
122
122
  const payload = {
123
- type: 'serverless.blocklet.updated',
123
+ type,
124
124
  payload: {
125
125
  did: blocklet.appDid,
126
126
  appId: blocklet.appDid,
@@ -152,6 +152,10 @@ const notifyBlockletUpdated = async (blocklet) => {
152
152
  }
153
153
  };
154
154
 
155
+ const notifyBlockletUpdated = (blocklet) => notifyLauncher('serverless.blocklet.updated', blocklet);
156
+ const notifyBlockletStarted = (blocklet) => notifyLauncher('serverless.blocklet.started', blocklet);
157
+ const notifyBlockletStopped = (blocklet) => notifyLauncher('serverless.blocklet.stopped', blocklet);
158
+
155
159
  const consumeLauncherSession = async ({ params, blocklet }) => {
156
160
  try {
157
161
  const info = await states.node.read();
@@ -634,6 +638,8 @@ module.exports = {
634
638
  isBlockletExpired,
635
639
  isBlockletTerminated,
636
640
  notifyBlockletUpdated,
641
+ notifyBlockletStarted,
642
+ notifyBlockletStopped,
637
643
  launchBlockletByLauncher,
638
644
  launchBlockletWithoutWallet,
639
645
  };
@@ -0,0 +1,27 @@
1
+ const { CustomError } = require('@blocklet/error');
2
+
3
+ const validateUserRolePassport = ({ role, passports }) => {
4
+ if (!Array.isArray(passports)) {
5
+ throw new CustomError(400, 'Invalid passports: must be an array');
6
+ }
7
+
8
+ if (!role || typeof role !== 'string') {
9
+ throw new CustomError(400, 'Invalid role: must be a non-empty string');
10
+ }
11
+
12
+ const filterPassports = passports.filter((x) => x.status === 'valid' && x.role === role);
13
+
14
+ if (filterPassports.length === 0) {
15
+ throw new CustomError(400, `No valid passport found: you don't have the required role "${role}".`);
16
+ }
17
+
18
+ const isValid = filterPassports.every((x) => x.expirationDate);
19
+ if (isValid) {
20
+ throw new CustomError(
21
+ 400,
22
+ `The passport for role "${role}" is only temporary and cannot be used to invite new members.`
23
+ );
24
+ }
25
+ };
26
+
27
+ module.exports = { validateUserRolePassport };
@@ -6,10 +6,12 @@ const {
6
6
  WELLKNOWN_BLOCKLET_USER_PATH,
7
7
  } = require('@abtnode/constant');
8
8
 
9
- const { parseURL } = require('ufo');
9
+ const { parseURL, joinURL } = require('ufo');
10
10
 
11
11
  const isAdminPath = (pathname) => pathname.startsWith(WELLKNOWN_BLOCKLET_ADMIN_PATH);
12
12
  const isUserCenterPath = (pathname) => pathname.startsWith(WELLKNOWN_BLOCKLET_USER_PATH);
13
+ const isServerDashboardPath = (pathname, prefix, teamDid) =>
14
+ !teamDid ? false : pathname.startsWith(joinURL(prefix, 'blocklets', teamDid));
13
15
 
14
16
  const getEndpoint = (context) => {
15
17
  const { pathname } = parseURL(context.referrer);
@@ -54,4 +56,4 @@ const validateOperator = (context, operatorDid) => {
54
56
  }
55
57
  };
56
58
 
57
- module.exports = { validateOperator, getEndpoint, isUserCenterPath, isAdminPath };
59
+ module.exports = { validateOperator, getEndpoint, isUserCenterPath, isAdminPath, isServerDashboardPath };
@@ -0,0 +1,42 @@
1
+ const { ROLES, SERVER_ROLES, WELLKNOWN_BLOCKLET_USER_PATH } = require('@abtnode/constant');
2
+ const get = require('lodash/get');
3
+ const { joinURL } = require('ufo');
4
+ const logger = require('@abtnode/logger')('@abtnode/core:util:verify-user-private');
5
+
6
+ const { getEndpoint, isServerDashboardPath, isAdminPath } = require('./verify-access-key-user');
7
+
8
+ const USER_FOLLOWERS_PATH = joinURL(WELLKNOWN_BLOCKLET_USER_PATH, 'user-followers');
9
+
10
+ const isAdminUser = (context) => {
11
+ const { user = {} } = context || {};
12
+ const role = user.userInfo?.role || user.role;
13
+ return [ROLES.ADMIN, ROLES.OWNER, SERVER_ROLES.BLOCKLET_ADMIN, SERVER_ROLES.BLOCKLET_OWNER].includes(role);
14
+ };
15
+
16
+ const isUserPrivacyEnabled = (userInfo) => {
17
+ // 在 server 的dashboard 和 service 的 dashboard 中,不需要判断
18
+ const privacyInfo = get(userInfo, 'extra.privacy', {});
19
+ return get(privacyInfo, USER_FOLLOWERS_PATH, false);
20
+ };
21
+
22
+ const isInDashboard = (teamDid, prefix, context = {}) => {
23
+ const { user = {}, hostname, referrer } = context || {};
24
+ if (user.role === SERVER_ROLES.BLOCKLET_SDK || !teamDid) {
25
+ return false;
26
+ }
27
+
28
+ if (!hostname || !referrer) {
29
+ logger.warn('Missing hostname or referrer context');
30
+ return false;
31
+ }
32
+
33
+ try {
34
+ const pathname = getEndpoint(context);
35
+ return isAdminPath(pathname) || isServerDashboardPath(pathname, prefix, teamDid);
36
+ } catch (error) {
37
+ logger.warn('Failed to check if in dashboard', { error });
38
+ return false;
39
+ }
40
+ };
41
+
42
+ module.exports = { isInDashboard, isUserPrivacyEnabled, isAdminUser };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.50",
6
+ "version": "1.16.51-beta-20250905-023351-70af144b",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,21 +19,21 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "Apache-2.0",
21
21
  "dependencies": {
22
- "@abtnode/analytics": "1.16.50",
23
- "@abtnode/auth": "1.16.50",
24
- "@abtnode/certificate-manager": "1.16.50",
25
- "@abtnode/constant": "1.16.50",
26
- "@abtnode/cron": "1.16.50",
27
- "@abtnode/db-cache": "1.16.50",
28
- "@abtnode/docker-utils": "1.16.50",
29
- "@abtnode/logger": "1.16.50",
30
- "@abtnode/models": "1.16.50",
31
- "@abtnode/queue": "1.16.50",
32
- "@abtnode/rbac": "1.16.50",
33
- "@abtnode/router-provider": "1.16.50",
34
- "@abtnode/static-server": "1.16.50",
35
- "@abtnode/timemachine": "1.16.50",
36
- "@abtnode/util": "1.16.50",
22
+ "@abtnode/analytics": "1.16.51-beta-20250905-023351-70af144b",
23
+ "@abtnode/auth": "1.16.51-beta-20250905-023351-70af144b",
24
+ "@abtnode/certificate-manager": "1.16.51-beta-20250905-023351-70af144b",
25
+ "@abtnode/constant": "1.16.51-beta-20250905-023351-70af144b",
26
+ "@abtnode/cron": "1.16.51-beta-20250905-023351-70af144b",
27
+ "@abtnode/db-cache": "1.16.51-beta-20250905-023351-70af144b",
28
+ "@abtnode/docker-utils": "1.16.51-beta-20250905-023351-70af144b",
29
+ "@abtnode/logger": "1.16.51-beta-20250905-023351-70af144b",
30
+ "@abtnode/models": "1.16.51-beta-20250905-023351-70af144b",
31
+ "@abtnode/queue": "1.16.51-beta-20250905-023351-70af144b",
32
+ "@abtnode/rbac": "1.16.51-beta-20250905-023351-70af144b",
33
+ "@abtnode/router-provider": "1.16.51-beta-20250905-023351-70af144b",
34
+ "@abtnode/static-server": "1.16.51-beta-20250905-023351-70af144b",
35
+ "@abtnode/timemachine": "1.16.51-beta-20250905-023351-70af144b",
36
+ "@abtnode/util": "1.16.51-beta-20250905-023351-70af144b",
37
37
  "@aigne/aigne-hub": "^0.8.6",
38
38
  "@arcblock/did": "1.24.0",
39
39
  "@arcblock/did-connect-js": "1.24.0",
@@ -45,16 +45,16 @@
45
45
  "@arcblock/pm2-events": "^0.0.5",
46
46
  "@arcblock/validator": "1.24.0",
47
47
  "@arcblock/vc": "1.24.0",
48
- "@blocklet/constant": "1.16.50",
49
- "@blocklet/did-space-js": "^1.1.19",
50
- "@blocklet/env": "1.16.50",
48
+ "@blocklet/constant": "1.16.51-beta-20250905-023351-70af144b",
49
+ "@blocklet/did-space-js": "^1.1.22",
50
+ "@blocklet/env": "1.16.51-beta-20250905-023351-70af144b",
51
51
  "@blocklet/error": "^0.2.5",
52
- "@blocklet/meta": "1.16.50",
53
- "@blocklet/resolver": "1.16.50",
54
- "@blocklet/sdk": "1.16.50",
55
- "@blocklet/server-js": "1.16.50",
56
- "@blocklet/store": "1.16.50",
57
- "@blocklet/theme": "^3.1.33",
52
+ "@blocklet/meta": "1.16.51-beta-20250905-023351-70af144b",
53
+ "@blocklet/resolver": "1.16.51-beta-20250905-023351-70af144b",
54
+ "@blocklet/sdk": "1.16.51-beta-20250905-023351-70af144b",
55
+ "@blocklet/server-js": "1.16.51-beta-20250905-023351-70af144b",
56
+ "@blocklet/store": "1.16.51-beta-20250905-023351-70af144b",
57
+ "@blocklet/theme": "^3.1.34",
58
58
  "@fidm/x509": "^1.2.1",
59
59
  "@ocap/mcrypto": "1.24.0",
60
60
  "@ocap/util": "1.24.0",
@@ -118,5 +118,5 @@
118
118
  "jest": "^29.7.0",
119
119
  "unzipper": "^0.10.11"
120
120
  },
121
- "gitHead": "142ced081f92724f0abe11b093b6313b0a1cef2b"
121
+ "gitHead": "d19892df7ce4f08a4a7a3ce96dff3bc6b01925ad"
122
122
  }