@abtnode/core 1.15.17 → 1.16.0-beta-b16cb035

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 (119) hide show
  1. package/lib/api/node.js +67 -69
  2. package/lib/api/team.js +386 -55
  3. package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
  4. package/lib/blocklet/downloader/bundle-downloader.js +272 -0
  5. package/lib/blocklet/downloader/constants.js +3 -0
  6. package/lib/blocklet/downloader/resolve-download.js +199 -0
  7. package/lib/blocklet/extras.js +83 -26
  8. package/lib/blocklet/hooks.js +18 -65
  9. package/lib/blocklet/manager/base.js +10 -16
  10. package/lib/blocklet/manager/disk.js +1679 -1566
  11. package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
  12. package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
  13. package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
  14. package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
  15. package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
  16. package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
  17. package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
  18. package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
  19. package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
  20. package/lib/blocklet/migration.js +30 -52
  21. package/lib/blocklet/storage/backup/audit-log.js +27 -0
  22. package/lib/blocklet/storage/backup/base.js +62 -0
  23. package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
  24. package/lib/blocklet/storage/backup/blocklet.js +70 -0
  25. package/lib/blocklet/storage/backup/blocklets.js +74 -0
  26. package/lib/blocklet/storage/backup/data.js +19 -0
  27. package/lib/blocklet/storage/backup/logs.js +24 -0
  28. package/lib/blocklet/storage/backup/routing-rule.js +19 -0
  29. package/lib/blocklet/storage/backup/spaces.js +240 -0
  30. package/lib/blocklet/storage/restore/base.js +67 -0
  31. package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
  32. package/lib/blocklet/storage/restore/blocklet.js +56 -0
  33. package/lib/blocklet/storage/restore/blocklets.js +43 -0
  34. package/lib/blocklet/storage/restore/logs.js +21 -0
  35. package/lib/blocklet/storage/restore/spaces.js +156 -0
  36. package/lib/blocklet/storage/utils/hash.js +51 -0
  37. package/lib/blocklet/storage/utils/zip.js +43 -0
  38. package/lib/cert.js +206 -0
  39. package/lib/event.js +237 -64
  40. package/lib/index.js +191 -83
  41. package/lib/migrations/1.0.21-update-config.js +1 -1
  42. package/lib/migrations/1.0.22-max-memory.js +1 -1
  43. package/lib/migrations/1.0.25.js +1 -1
  44. package/lib/migrations/1.0.32-update-config.js +1 -1
  45. package/lib/migrations/1.0.33-blocklets.js +1 -1
  46. package/lib/migrations/1.5.20-registry.js +15 -0
  47. package/lib/migrations/1.6.17-blocklet-children.js +48 -0
  48. package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
  49. package/lib/migrations/1.6.4-security.js +59 -0
  50. package/lib/migrations/1.6.5-security.js +60 -0
  51. package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
  52. package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
  53. package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
  54. package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
  55. package/lib/migrations/1.7.20-blocklet-component.js +41 -0
  56. package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
  57. package/lib/migrations/README.md +1 -1
  58. package/lib/migrations/index.js +6 -2
  59. package/lib/monitor/blocklet-runtime-monitor.js +200 -0
  60. package/lib/monitor/get-history-list.js +37 -0
  61. package/lib/monitor/node-runtime-monitor.js +228 -0
  62. package/lib/router/helper.js +572 -497
  63. package/lib/router/index.js +85 -21
  64. package/lib/router/manager.js +146 -187
  65. package/lib/states/README.md +36 -1
  66. package/lib/states/access-key.js +39 -17
  67. package/lib/states/audit-log.js +462 -0
  68. package/lib/states/base.js +4 -213
  69. package/lib/states/blocklet-extras.js +194 -138
  70. package/lib/states/blocklet.js +361 -104
  71. package/lib/states/cache.js +8 -6
  72. package/lib/states/challenge.js +5 -5
  73. package/lib/states/index.js +19 -36
  74. package/lib/states/migration.js +4 -4
  75. package/lib/states/node.js +135 -46
  76. package/lib/states/notification.js +22 -35
  77. package/lib/states/session.js +17 -9
  78. package/lib/states/site.js +50 -25
  79. package/lib/states/user.js +74 -20
  80. package/lib/states/webhook.js +10 -6
  81. package/lib/team/manager.js +124 -7
  82. package/lib/util/blocklet.js +1223 -246
  83. package/lib/util/chain.js +1 -1
  84. package/lib/util/default-node-config.js +5 -23
  85. package/lib/util/disk-monitor.js +13 -10
  86. package/lib/util/domain-status.js +84 -15
  87. package/lib/util/get-accessible-external-node-ip.js +2 -2
  88. package/lib/util/get-domain-for-blocklet.js +13 -0
  89. package/lib/util/get-meta-from-url.js +33 -0
  90. package/lib/util/index.js +207 -272
  91. package/lib/util/ip.js +6 -0
  92. package/lib/util/maintain.js +233 -0
  93. package/lib/util/public-to-store.js +85 -0
  94. package/lib/util/ready.js +1 -1
  95. package/lib/util/requirement.js +28 -9
  96. package/lib/util/reset-node.js +22 -7
  97. package/lib/util/router.js +13 -0
  98. package/lib/util/rpc.js +16 -0
  99. package/lib/util/store.js +179 -0
  100. package/lib/util/sysinfo.js +44 -0
  101. package/lib/util/ua.js +54 -0
  102. package/lib/validators/blocklet-extra.js +24 -0
  103. package/lib/validators/node.js +25 -12
  104. package/lib/validators/permission.js +16 -1
  105. package/lib/validators/role.js +17 -3
  106. package/lib/validators/router.js +40 -20
  107. package/lib/validators/trusted-passport.js +1 -0
  108. package/lib/validators/util.js +22 -5
  109. package/lib/webhook/index.js +45 -35
  110. package/lib/webhook/sender/index.js +5 -0
  111. package/lib/webhook/sender/slack/index.js +1 -1
  112. package/lib/webhook/sender/wallet/index.js +48 -0
  113. package/package.json +54 -36
  114. package/lib/blocklet/registry.js +0 -205
  115. package/lib/states/https-cert.js +0 -67
  116. package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
  117. package/lib/util/service.js +0 -66
  118. package/lib/util/upgrade.js +0 -178
  119. /package/lib/{queue.js → util/queue.js} +0 -0
package/lib/api/team.js CHANGED
@@ -1,13 +1,46 @@
1
1
  const { EventEmitter } = require('events');
2
2
  const pick = require('lodash/pick');
3
+ const joinUrl = require('url-join');
4
+
3
5
  const logger = require('@abtnode/logger')('@abtnode/core:api:team');
4
- const { ROLES, genPermissionName } = require('@abtnode/constant');
6
+ const {
7
+ ROLES,
8
+ genPermissionName,
9
+ EVENTS,
10
+ WHO_CAN_ACCESS,
11
+ PASSPORT_STATUS,
12
+ WELLKNOWN_SERVICE_PATH_PREFIX,
13
+ MAX_USER_PAGE_SIZE,
14
+ STORE_DETAIL_PAGE_PATH_PREFIX,
15
+ } = require('@abtnode/constant');
5
16
  const { isValid: isValidDid } = require('@arcblock/did');
6
- const { BlockletEvents } = require('@blocklet/meta/lib/constants');
17
+ const { BlockletEvents } = require('@blocklet/constant');
18
+ const { sendToUser } = require('@blocklet/sdk/lib/util/send-notification');
19
+ const {
20
+ createPassportVC,
21
+ createPassport,
22
+ upsertToPassports,
23
+ createUserPassport,
24
+ } = require('@abtnode/auth/lib/passport');
25
+ const { getPassportStatusEndpoint } = require('@abtnode/auth/lib/auth');
26
+ const getBlockletInfo = require('@blocklet/meta/lib/info');
27
+ const { parseUserAvatar } = require('@abtnode/util/lib/user-avatar');
28
+
7
29
  const { validateTrustedPassportIssuers } = require('../validators/trusted-passport');
8
30
  const { validateCreateRole, validateUpdateRole } = require('../validators/role');
9
31
  const { validateCreatePermission, validateUpdatePermission } = require('../validators/permission');
10
32
 
33
+ const { getBlocklet } = require('../util/blocklet');
34
+ const StoreUtil = require('../util/store');
35
+
36
+ const sanitizeUrl = (url) => {
37
+ if (!url) {
38
+ throw new Error('Registry URL should not be empty');
39
+ }
40
+
41
+ return url.trim();
42
+ };
43
+
11
44
  const validateReservedRole = (role) => {
12
45
  if (Object.values(ROLES).includes(role)) {
13
46
  throw new Error(`The role ${role} is reserved`);
@@ -15,18 +48,53 @@ const validateReservedRole = (role) => {
15
48
  return true;
16
49
  };
17
50
 
51
+ const sendPassportVcNotification = ({ userDid, appWallet: wallet, locale, vc }) => {
52
+ try {
53
+ const receiver = userDid;
54
+ const sender = { appDid: wallet.address, appSk: wallet.secretKey };
55
+ const message = {
56
+ title: {
57
+ en: 'Receive a Passport',
58
+ zh: '获得通行证',
59
+ }[locale],
60
+ body: {
61
+ en: 'You got a passport',
62
+ zh: '你获得了一张通行证',
63
+ }[locale],
64
+ attachments: [
65
+ {
66
+ type: 'vc',
67
+ data: {
68
+ credential: vc,
69
+ tag: vc.credentialSubject.passport.name,
70
+ },
71
+ },
72
+ ],
73
+ };
74
+
75
+ sendToUser(receiver, message, sender, process.env.ABT_NODE_SERVICE_PORT).catch((error) => {
76
+ logger.error('Failed send passport vc to wallet', { error });
77
+ });
78
+ } catch (error) {
79
+ logger.error('Failed send passport vc to wallet', { error });
80
+ }
81
+ };
82
+
18
83
  class TeamAPI extends EventEmitter {
19
84
  /**
20
85
  *
21
86
  * @param {object} states abtnode core StateDB
22
87
  */
23
- constructor({ teamManager, states }) {
88
+ constructor({ teamManager, states, dataDirs }) {
24
89
  super();
25
90
 
26
91
  this.notification = states.notification;
27
92
  this.node = states.node;
28
- this.inviteExpireTime = 1000 * 3600 * 24 * 30; // 30 days
93
+ this.states = states;
94
+ this.memberInviteExpireTime = 1000 * 3600 * 24 * 30; // 30 days
95
+ this.serverInviteExpireTime = 1000 * 3600; // 1 hour
29
96
  this.teamManager = teamManager;
97
+ this.dataDirs = dataDirs;
30
98
  }
31
99
 
32
100
  // User && Invitation
@@ -54,41 +122,71 @@ class TeamAPI extends EventEmitter {
54
122
  if (teamDid === nodeInfo.did && nodeInfo.nodeOwner && user.did !== nodeInfo.nodeOwner.did) {
55
123
  await this.notification.create({
56
124
  title: 'New member join',
57
- description: `User with Name (${user.fullName}) and DID (${user.did}) has joined this Node`,
125
+ description: `User with Name (${user.fullName}) and DID (${user.did}) has joined this server`,
58
126
  entityType: 'node',
59
- action: '/settings/team',
127
+ action: '/team/members',
60
128
  entityId: user.did,
61
129
  severity: 'success',
62
130
  });
63
131
  }
64
132
 
65
- this.emit('user.added', { teamDid, user: doc });
133
+ this.emit(EVENTS.USER_ADDED, { teamDid, user: doc });
66
134
 
67
135
  return doc;
68
136
  }
69
137
 
70
- async getUsers({ teamDid }) {
138
+ async getUsers({ teamDid, query, paging: inputPaging, sort, dids }) {
71
139
  const state = await this.getUserState(teamDid);
72
140
 
73
- const list = await state.getUsers();
74
-
75
- return list.map(
76
- (d) =>
77
- pick(d, [
78
- 'did',
79
- 'pk',
80
- 'role',
81
- 'email',
82
- 'fullName',
83
- 'approved',
84
- 'createdAt',
85
- 'updatedAt',
86
- 'passports',
87
- 'firstLoginAt',
88
- 'lastLoginAt',
89
- ])
90
- // eslint-disable-next-line function-paren-newline
91
- );
141
+ if (inputPaging?.pageSize > MAX_USER_PAGE_SIZE) {
142
+ throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
143
+ }
144
+
145
+ if (dids && dids.length > MAX_USER_PAGE_SIZE) {
146
+ throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE}`);
147
+ }
148
+
149
+ let list;
150
+ let paging;
151
+
152
+ if (dids) {
153
+ list = await state.getUsersByDids({ query, dids });
154
+ paging = {
155
+ total: list.length,
156
+ pageSize: dids.length,
157
+ pageCount: 1,
158
+ page: 1,
159
+ };
160
+ } else {
161
+ const doc = await state.getUsers({ query, sort, paging: { pageSize: 20, ...inputPaging } });
162
+ list = doc.list;
163
+ paging = doc.paging;
164
+ }
165
+
166
+ return {
167
+ users: list.map(
168
+ (d) =>
169
+ pick(d, [
170
+ 'did',
171
+ 'pk',
172
+ 'role',
173
+ 'email',
174
+ 'fullName',
175
+ 'approved',
176
+ 'createdAt',
177
+ 'updatedAt',
178
+ 'passports',
179
+ 'firstLoginAt',
180
+ 'lastLoginAt',
181
+ 'lastLoginIp',
182
+ 'remark',
183
+ 'avatar',
184
+ 'locale',
185
+ ])
186
+ // eslint-disable-next-line function-paren-newline
187
+ ),
188
+ paging,
189
+ };
92
190
  }
93
191
 
94
192
  async getUsersCount({ teamDid }) {
@@ -97,19 +195,54 @@ class TeamAPI extends EventEmitter {
97
195
  return state.count();
98
196
  }
99
197
 
198
+ async getUsersCountPerRole({ teamDid }) {
199
+ const roles = await this.getRoles({ teamDid });
200
+
201
+ const state = await this.getUserState(teamDid);
202
+
203
+ const res = [];
204
+
205
+ const all = await state.count();
206
+ res.push({ key: '$all', value: all });
207
+
208
+ for (const { name } of roles) {
209
+ // eslint-disable-next-line no-await-in-loop
210
+ const count = await state.count({ passports: { $elemMatch: { name, status: PASSPORT_STATUS.VALID } } });
211
+ res.push({ key: name, value: count });
212
+ }
213
+
214
+ const none = await state.count({ passports: { $size: 0 } });
215
+ res.push({ key: '$none', value: none });
216
+
217
+ return res;
218
+ }
219
+
100
220
  async getUser({ teamDid, user }) {
101
221
  const state = await this.getUserState(teamDid);
102
222
 
103
223
  return state.getUser(user.did);
104
224
  }
105
225
 
226
+ async getOwner({ teamDid }) {
227
+ const owner = await this.teamManager.getOwner(teamDid);
228
+
229
+ if (!owner) {
230
+ return null;
231
+ }
232
+
233
+ const state = await this.getUserState(teamDid);
234
+
235
+ const full = await state.getUser(owner.did);
236
+ return full || owner;
237
+ }
238
+
106
239
  async updateUser({ teamDid, user }) {
107
240
  const state = await this.getUserState(teamDid);
108
241
 
109
242
  const doc = await state.update(user);
110
243
 
111
244
  logger.info('user updated successfully', { teamDid, userDid: user.did });
112
- this.emit('user.updated', { teamDid, user: doc });
245
+ this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
113
246
 
114
247
  return doc;
115
248
  }
@@ -131,7 +264,7 @@ class TeamAPI extends EventEmitter {
131
264
 
132
265
  logger.info('user removed successfully', { teamDid, userDid: did });
133
266
 
134
- this.emit('user.removed', { teamDid, user: { did } });
267
+ this.emit(EVENTS.USER_REMOVED, { teamDid, user: { did } });
135
268
 
136
269
  return { did };
137
270
  }
@@ -152,7 +285,7 @@ class TeamAPI extends EventEmitter {
152
285
 
153
286
  logger.info('user approval updated successfully', { teamDid, userDid: user.did, approved: user.approved });
154
287
 
155
- this.emit('user.updated', { teamDid, user: doc2 });
288
+ this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc2 });
156
289
 
157
290
  return doc2;
158
291
  }
@@ -171,11 +304,77 @@ class TeamAPI extends EventEmitter {
171
304
 
172
305
  logger.info('user role updated successfully', { teamDid, userDid: user.did, role: user.role });
173
306
 
174
- this.emit('user.updated', { teamDid, user: doc });
307
+ this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
175
308
 
176
309
  return doc;
177
310
  }
178
311
 
312
+ async issuePassportToUser({ teamDid, userDid, role: roleName, notify = true }, context = {}) {
313
+ const { locale = 'en' } = context;
314
+
315
+ const userState = await this.getUserState(teamDid);
316
+ const user = await userState.getUser(userDid);
317
+
318
+ if (!user) {
319
+ throw new Error(`user does not exist: ${userDid}`);
320
+ }
321
+
322
+ if (!user.approved) {
323
+ throw new Error(`the user is revoked: ${userDid}`);
324
+ }
325
+
326
+ const rbac = await this.getRBAC(teamDid);
327
+ const role = await rbac.getRole(roleName);
328
+
329
+ if (!role) {
330
+ throw new Error(`passport does not exist: ${roleName}`);
331
+ }
332
+
333
+ // create vc
334
+ const blocklet = await getBlocklet({ did: teamDid, states: this.states, dataDirs: this.dataDirs });
335
+ const nodeInfo = await this.node.read();
336
+ const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
337
+ const { wallet, passportColor, appUrl, name: issuerName } = blockletInfo;
338
+
339
+ const vc = createPassportVC({
340
+ issuerName,
341
+ issuerWallet: wallet,
342
+ ownerDid: userDid,
343
+ passport: await createPassport({
344
+ role,
345
+ }),
346
+ endpoint: getPassportStatusEndpoint({
347
+ baseUrl: joinUrl(appUrl, WELLKNOWN_SERVICE_PATH_PREFIX),
348
+ userDid,
349
+ teamDid,
350
+ }),
351
+ ownerProfile: {
352
+ ...user,
353
+ avatar: await parseUserAvatar(user.avatar, { dataDir: blocklet.env.dataDir }),
354
+ },
355
+ preferredColor: passportColor,
356
+ });
357
+
358
+ // write passport to db
359
+ const passport = createUserPassport(vc, { role: role.name });
360
+ user.passports = upsertToPassports(user.passports || [], passport);
361
+ await this.updateUser({
362
+ teamDid,
363
+ user: {
364
+ did: user.did,
365
+ pk: user.pk,
366
+ passports: user.passports,
367
+ },
368
+ });
369
+
370
+ // send vc to wallet
371
+ if (notify) {
372
+ sendPassportVcNotification({ userDid, appWallet: wallet, locale, vc });
373
+ }
374
+
375
+ return user;
376
+ }
377
+
179
378
  async revokeUserPassport({ teamDid, userDid, passportId } = {}) {
180
379
  if (!passportId) {
181
380
  throw new Error('Revoked passport should not be empty');
@@ -187,7 +386,7 @@ class TeamAPI extends EventEmitter {
187
386
 
188
387
  logger.info('user passport revoked successfully', { teamDid, userDid, passportId });
189
388
 
190
- this.emit('user.updated', { teamDid, user: doc });
389
+ this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
191
390
 
192
391
  return doc;
193
392
  }
@@ -203,16 +402,40 @@ class TeamAPI extends EventEmitter {
203
402
 
204
403
  logger.info('user passport enabled successfully', { teamDid, userDid, passportId });
205
404
 
206
- this.emit('user.updated', { teamDid, user: doc });
405
+ this.emit(EVENTS.USER_UPDATED, { teamDid, user: doc });
207
406
 
208
407
  return doc;
209
408
  }
210
409
 
211
410
  // Invite member
212
411
 
213
- async createInvitation({ teamDid, role, remark, interfaceName }, context) {
412
+ /**
413
+ * @type InvitationSession {
414
+ * type: 'invite';
415
+ * role: string;
416
+ * remark: string;
417
+ * expireDate: number;
418
+ * inviter: {
419
+ * did: string;
420
+ * email?: string;
421
+ * fullName?: string;
422
+ * role?: string;
423
+ * };
424
+ * teamDid: string;
425
+ * status: '' | 'success';
426
+ * receiver: {
427
+ * did: string;
428
+ * };
429
+ * }
430
+ * @returns
431
+ */
432
+ async createMemberInvitation({ teamDid, role, expireTime, remark }, context) {
214
433
  await this.teamManager.checkEnablePassportIssuance(teamDid);
215
434
 
435
+ if (expireTime && expireTime <= 0) {
436
+ throw new Error('Expire time must be greater than 0');
437
+ }
438
+
216
439
  if (!role) {
217
440
  throw new Error('Role cannot be empty');
218
441
  }
@@ -232,7 +455,7 @@ class TeamAPI extends EventEmitter {
232
455
  throw new Error('Inviter does not exist');
233
456
  }
234
457
 
235
- const expireDate = Date.now() + this.inviteExpireTime;
458
+ const expireDate = Date.now() + (expireTime || this.memberInviteExpireTime);
236
459
  const state = await this.getSessionState(teamDid);
237
460
  const { id: inviteId } = await state.start({
238
461
  type: 'invite',
@@ -241,7 +464,6 @@ class TeamAPI extends EventEmitter {
241
464
  expireDate,
242
465
  inviter: user,
243
466
  teamDid,
244
- interfaceName,
245
467
  });
246
468
 
247
469
  logger.info('Create invite member', { role, user, inviteId });
@@ -253,16 +475,47 @@ class TeamAPI extends EventEmitter {
253
475
  expireDate: new Date(expireDate).toString(),
254
476
  inviter: user,
255
477
  teamDid,
256
- interfaceName,
257
478
  };
258
479
  }
259
480
 
260
- async getInvitations({ teamDid }) {
481
+ async createTransferInvitation({ teamDid, remark }, context) {
482
+ return this.createMemberInvitation(
483
+ { teamDid, expireTime: this.serverInviteExpireTime, remark, role: 'owner' },
484
+ context
485
+ );
486
+ }
487
+
488
+ async getInvitation({ teamDid, inviteId }) {
489
+ if (!teamDid || !inviteId) {
490
+ return null;
491
+ }
492
+
493
+ const state = await this.getSessionState(teamDid);
494
+
495
+ const invitation = await state.findOne({ _id: inviteId, type: 'invite' });
496
+ if (!invitation) {
497
+ return null;
498
+ }
499
+
500
+ return {
501
+ // eslint-disable-next-line no-underscore-dangle
502
+ inviteId: invitation._id,
503
+ role: invitation.role,
504
+ remark: invitation.remark,
505
+ expireDate: new Date(invitation.expireDate).toString(),
506
+ inviter: invitation.inviter,
507
+ teamDid: invitation.teamDid,
508
+ status: invitation.status,
509
+ receiver: invitation.receiver,
510
+ };
511
+ }
512
+
513
+ async getInvitations({ teamDid, filter }) {
261
514
  const state = await this.getSessionState(teamDid);
262
515
 
263
516
  const invitations = await state.find({ type: 'invite' });
264
517
 
265
- return invitations.map((d) => ({
518
+ return invitations.filter(filter || ((x) => x.status !== 'success')).map((d) => ({
266
519
  // eslint-disable-next-line no-underscore-dangle
267
520
  inviteId: d._id,
268
521
  role: d.role,
@@ -270,7 +523,8 @@ class TeamAPI extends EventEmitter {
270
523
  expireDate: new Date(d.expireDate).toString(),
271
524
  inviter: d.inviter,
272
525
  teamDid: d.teamDid,
273
- interfaceName: d.interfaceName,
526
+ status: d.status,
527
+ receiver: d.receiver,
274
528
  }));
275
529
  }
276
530
 
@@ -287,7 +541,7 @@ class TeamAPI extends EventEmitter {
287
541
  return true;
288
542
  }
289
543
 
290
- async processInvitation({ teamDid, inviteId }) {
544
+ async checkInvitation({ teamDid, inviteId }) {
291
545
  const state = await this.getSessionState(teamDid);
292
546
 
293
547
  const invitation = await state.read(inviteId);
@@ -296,7 +550,7 @@ class TeamAPI extends EventEmitter {
296
550
  throw new Error(`The invitation does not exist: ${inviteId}`);
297
551
  }
298
552
 
299
- const { role, expireDate } = await state.read(inviteId);
553
+ const { role, expireDate, remark } = await state.read(inviteId);
300
554
 
301
555
  if (Date.now() > expireDate) {
302
556
  logger.error('Invite id has expired', { inviteId, expireAt: new Date(expireDate) });
@@ -309,15 +563,33 @@ class TeamAPI extends EventEmitter {
309
563
  throw new Error(`Role does not exist: ${role}`);
310
564
  }
311
565
 
312
- await state.end(inviteId);
313
-
314
- logger.info('Invitation session completed successfully', { inviteId, role });
315
-
316
566
  return {
317
567
  role,
568
+ remark,
318
569
  };
319
570
  }
320
571
 
572
+ async closeInvitation({ teamDid, inviteId, status, receiver, timeout = 30 * 1000 }) {
573
+ const state = await this.getSessionState(teamDid);
574
+
575
+ const invitation = await state.read(inviteId);
576
+
577
+ if (!invitation) {
578
+ throw new Error(`The invitation does not exist: ${inviteId}`);
579
+ }
580
+
581
+ await state.update(inviteId, { status, receiver });
582
+
583
+ setTimeout(async () => {
584
+ try {
585
+ logger.info('Invitation session closed', { inviteId });
586
+ await state.end(inviteId);
587
+ } catch (error) {
588
+ logger.error('close invitation failed', { error });
589
+ }
590
+ }, timeout);
591
+ }
592
+
321
593
  // Issue Passport
322
594
 
323
595
  async createPassportIssuance({ teamDid, ownerDid, name }) {
@@ -338,7 +610,7 @@ class TeamAPI extends EventEmitter {
338
610
  throw new Error(`Passport does not exist: ${name}`);
339
611
  }
340
612
 
341
- const expireDate = Date.now() + this.inviteExpireTime;
613
+ const expireDate = Date.now() + this.memberInviteExpireTime;
342
614
 
343
615
  const state = await this.getSessionState(teamDid);
344
616
  const { id } = await state.start({
@@ -365,7 +637,13 @@ class TeamAPI extends EventEmitter {
365
637
  async getPassportIssuances({ teamDid, ownerDid }) {
366
638
  const state = await this.getSessionState(teamDid);
367
639
 
368
- const list = await state.find({ type: 'passport-issuance', ownerDid });
640
+ const query = { type: 'passport-issuance' };
641
+
642
+ if (ownerDid) {
643
+ query.ownerDid = ownerDid;
644
+ }
645
+
646
+ const list = await state.find(query);
369
647
 
370
648
  return list.map((d) => ({
371
649
  // eslint-disable-next-line no-underscore-dangle
@@ -452,14 +730,18 @@ class TeamAPI extends EventEmitter {
452
730
  }
453
731
  }
454
732
 
455
- // Access Control
733
+ async configWhoCanAccess({ teamDid, value }) {
734
+ if (!Object.values(WHO_CAN_ACCESS).includes(value)) {
735
+ throw new Error('value is invalid');
736
+ }
456
737
 
457
- async getRoles({ teamDid }) {
458
- const rbac = await this.getRBAC(teamDid);
738
+ await this.teamManager.configWhoCanAccess(teamDid, value);
739
+ }
459
740
 
460
- const roles = await rbac.getRoles();
741
+ // Access Control
461
742
 
462
- return roles.map((d) => pick(d, ['name', 'grants', 'title', 'description']));
743
+ async getRoles({ teamDid }) {
744
+ return this.teamManager.getRoles(teamDid);
463
745
  }
464
746
 
465
747
  async createRole({ teamDid, name, description, title, childName, permissions = [] }) {
@@ -486,7 +768,7 @@ class TeamAPI extends EventEmitter {
486
768
  async updateRole({ teamDid, role: { name, title, description } = {} }) {
487
769
  logger.info('update role', { teamDid, name, title, description });
488
770
 
489
- validateUpdateRole({ name, title, description });
771
+ await validateUpdateRole({ name, title, description });
490
772
 
491
773
  const rbac = await this.getRBAC(teamDid);
492
774
 
@@ -628,6 +910,55 @@ class TeamAPI extends EventEmitter {
628
910
  );
629
911
  }
630
912
 
913
+ // eslint-disable-next-line no-unused-vars
914
+ async addStore({ teamDid, url }, context) {
915
+ logger.info('add registry', { url });
916
+
917
+ const urlObj = new URL(url);
918
+ let newUrl = urlObj.origin;
919
+
920
+ // if the pathname is store blocklet list or blocklet detail
921
+ if (urlObj.pathname?.includes(STORE_DETAIL_PAGE_PATH_PREFIX)) {
922
+ const lastIndex = urlObj.pathname.lastIndexOf(STORE_DETAIL_PAGE_PATH_PREFIX);
923
+ const pathnamePrefix = urlObj.pathname.substring(0, lastIndex);
924
+ newUrl = `${newUrl}${pathnamePrefix || ''}`;
925
+ }
926
+
927
+ const sanitized = sanitizeUrl(newUrl);
928
+
929
+ const storeList = await this.teamManager.getStoreList(teamDid);
930
+
931
+ const exist = storeList.find((x) => x.url === sanitized);
932
+ if (exist) {
933
+ throw new Error(`Blocklet registry already exist: ${sanitized}`);
934
+ }
935
+
936
+ await StoreUtil.validateStoreUrl(sanitized);
937
+
938
+ const store = await StoreUtil.getStoreMeta(sanitized);
939
+
940
+ storeList.push({ ...store, url: sanitized, protected: false });
941
+
942
+ return this.teamManager.updateStoreList(teamDid, storeList);
943
+ }
944
+
945
+ // eslint-disable-next-line no-unused-vars
946
+ async deleteStore({ teamDid, url }, context) {
947
+ logger.info('delete registry', { url });
948
+ const sanitized = sanitizeUrl(url);
949
+
950
+ const storeList = await this.teamManager.getStoreList(teamDid);
951
+ const exist = storeList.find((x) => x.url === sanitized);
952
+ if (!exist) {
953
+ throw new Error(`Blocklet registry does not exist: ${sanitized}`);
954
+ }
955
+
956
+ return this.teamManager.updateStoreList(
957
+ teamDid,
958
+ storeList.filter((x) => x.url !== sanitized)
959
+ );
960
+ }
961
+
631
962
  // =======
632
963
  // Private
633
964
  // =======
@@ -648,7 +979,7 @@ class TeamAPI extends EventEmitter {
648
979
  // Just for test
649
980
  // =============
650
981
  setInviteExpireTime(ms) {
651
- this.inviteExpireTime = ms;
982
+ this.memberInviteExpireTime = ms;
652
983
  }
653
984
  }
654
985