@abtnode/core 1.7.24 → 1.7.27

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/lib/api/team.js CHANGED
@@ -1,13 +1,15 @@
1
1
  const { EventEmitter } = require('events');
2
2
  const pick = require('lodash/pick');
3
3
  const logger = require('@abtnode/logger')('@abtnode/core:api:team');
4
- const { ROLES, genPermissionName, EVENTS, WHO_CAN_ACCESS } = require('@abtnode/constant');
4
+ const { ROLES, genPermissionName, EVENTS, WHO_CAN_ACCESS, PASSPORT_STATUS } = require('@abtnode/constant');
5
5
  const { isValid: isValidDid } = require('@arcblock/did');
6
6
  const { BlockletEvents } = require('@blocklet/meta/lib/constants');
7
7
  const { validateTrustedPassportIssuers } = require('../validators/trusted-passport');
8
8
  const { validateCreateRole, validateUpdateRole } = require('../validators/role');
9
9
  const { validateCreatePermission, validateUpdatePermission } = require('../validators/permission');
10
10
 
11
+ const MAX_USER_PAGE_SIZE = 100;
12
+
11
13
  const validateReservedRole = (role) => {
12
14
  if (Object.values(ROLES).includes(role)) {
13
15
  throw new Error(`The role ${role} is reserved`);
@@ -68,29 +70,37 @@ class TeamAPI extends EventEmitter {
68
70
  return doc;
69
71
  }
70
72
 
71
- async getUsers({ teamDid }) {
73
+ async getUsers({ teamDid, query, paging: inputPaging, sort }) {
72
74
  const state = await this.getUserState(teamDid);
73
75
 
74
- const list = await state.getUsers();
75
-
76
- return list.map(
77
- (d) =>
78
- pick(d, [
79
- 'did',
80
- 'pk',
81
- 'role',
82
- 'email',
83
- 'fullName',
84
- 'approved',
85
- 'createdAt',
86
- 'updatedAt',
87
- 'passports',
88
- 'firstLoginAt',
89
- 'lastLoginAt',
90
- 'remark',
91
- ])
92
- // eslint-disable-next-line function-paren-newline
93
- );
76
+ if (inputPaging?.pageSize > MAX_USER_PAGE_SIZE) {
77
+ throw new Error(`Length of users should not exceed ${MAX_USER_PAGE_SIZE} per page`);
78
+ }
79
+
80
+ const { list, paging } = await state.getUsers({ query, sort, paging: { pageSize: 20, ...inputPaging } });
81
+
82
+ return {
83
+ users: list.map(
84
+ (d) =>
85
+ pick(d, [
86
+ 'did',
87
+ 'pk',
88
+ 'role',
89
+ 'email',
90
+ 'fullName',
91
+ 'approved',
92
+ 'createdAt',
93
+ 'updatedAt',
94
+ 'passports',
95
+ 'firstLoginAt',
96
+ 'lastLoginAt',
97
+ 'remark',
98
+ 'avatar',
99
+ ])
100
+ // eslint-disable-next-line function-paren-newline
101
+ ),
102
+ paging,
103
+ };
94
104
  }
95
105
 
96
106
  async getUsersCount({ teamDid }) {
@@ -99,6 +109,28 @@ class TeamAPI extends EventEmitter {
99
109
  return state.count();
100
110
  }
101
111
 
112
+ async getUsersCountPerRole({ teamDid }) {
113
+ const roles = await this.getRoles({ teamDid });
114
+
115
+ const state = await this.getUserState(teamDid);
116
+
117
+ const res = [];
118
+
119
+ const all = await state.count();
120
+ res.push({ key: '$all', value: all });
121
+
122
+ for (const { name } of roles) {
123
+ // eslint-disable-next-line no-await-in-loop
124
+ const count = await state.count({ passports: { $elemMatch: { name, status: PASSPORT_STATUS.VALID } } });
125
+ res.push({ key: name, value: count });
126
+ }
127
+
128
+ const none = await state.count({ passports: { $size: 0 } });
129
+ res.push({ key: '$none', value: none });
130
+
131
+ return res;
132
+ }
133
+
102
134
  async getUser({ teamDid, user }) {
103
135
  const state = await this.getUserState(teamDid);
104
136
 
@@ -10,7 +10,7 @@ const capitalize = require('lodash/capitalize');
10
10
  const { Throttle } = require('stream-throttle');
11
11
  const LRU = require('lru-cache');
12
12
  const joi = require('joi');
13
-
13
+ const { sign } = require('@arcblock/jwt');
14
14
  const { isValid: isValidDid } = require('@arcblock/did');
15
15
  const { verifyPresentation } = require('@arcblock/vc');
16
16
  const { toBase58, isHex } = require('@ocap/util');
@@ -45,6 +45,7 @@ const toBlockletDid = require('@blocklet/meta/lib/did');
45
45
  const { validateMeta } = require('@blocklet/meta/lib/validate');
46
46
  const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
47
47
  const { titleSchema, descriptionSchema } = require('@blocklet/meta/lib/schema');
48
+ const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
48
49
 
49
50
  const {
50
51
  BlockletStatus,
@@ -101,7 +102,6 @@ const {
101
102
  getDiffFiles,
102
103
  getBundleDir,
103
104
  needBlockletDownload,
104
- verifyPurchase,
105
105
  findAvailableDid,
106
106
  ensureMeta,
107
107
  } = require('../../util/blocklet');
@@ -172,13 +172,15 @@ class BlockletManager extends BaseBlockletManager {
172
172
  /**
173
173
  * @param {*} dataDirs generate by ../../util:getDataDirs
174
174
  */
175
- constructor({ dataDirs, registry, startQueue, installQueue, daemon = false }) {
175
+ constructor({ dataDirs, registry, startQueue, installQueue, daemon = false, teamManager }) {
176
176
  super();
177
177
 
178
178
  this.dataDirs = dataDirs;
179
179
  this.installDir = dataDirs.blocklets;
180
180
  this.startQueue = startQueue;
181
181
  this.installQueue = installQueue;
182
+ this.teamManager = teamManager;
183
+
182
184
  /**
183
185
  * { did: Map({ <childDid>: <downloadFile.cancelCtrl> }) }
184
186
  */
@@ -209,9 +211,17 @@ class BlockletManager extends BaseBlockletManager {
209
211
  // ============================================================================================
210
212
 
211
213
  /**
212
- * @param {Object} params
213
- * @param {string} params.installId
214
- * @param {string} params.sync default: false
214
+ *
215
+ *
216
+ * @param {{
217
+ * url: string;
218
+ * sync: boolean = false;
219
+ * }} params
220
+ * @param {{
221
+ * [key: string]: any
222
+ * }} context
223
+ * @return {*}
224
+ * @memberof BlockletManager
215
225
  */
216
226
  async install(params, context) {
217
227
  logger.debug('install blocklet', { params, context });
@@ -245,7 +255,6 @@ class BlockletManager extends BaseBlockletManager {
245
255
  /**
246
256
  * @param {String} rootDid
247
257
  * @param {String} mountPoint
248
- * @param {Boolean} context.blockletPurchaseVerified
249
258
  *
250
259
  * installFromUrl
251
260
  * @param {String} url
@@ -259,8 +268,13 @@ class BlockletManager extends BaseBlockletManager {
259
268
  * Custom info
260
269
  * @param {String} title custom component title
261
270
  * @param {String} name custom component name
271
+ *
272
+ * @param {ConfigEntry} configs pre configs
262
273
  */
263
- async installComponent({ rootDid, mountPoint, url, file, did, diffVersion, deleteSet, title, name }, context = {}) {
274
+ async installComponent(
275
+ { rootDid, mountPoint, url, file, did, diffVersion, deleteSet, title, name, configs, downloadToken },
276
+ context = {}
277
+ ) {
264
278
  logger.debug('start install component', { rootDid, mountPoint, url });
265
279
 
266
280
  if (file) {
@@ -268,7 +282,17 @@ class BlockletManager extends BaseBlockletManager {
268
282
  }
269
283
 
270
284
  if (url) {
271
- return this._installComponentFromUrl({ rootDid, mountPoint, url, context, title, did, name });
285
+ return this._installComponentFromUrl({
286
+ rootDid,
287
+ mountPoint,
288
+ url,
289
+ context,
290
+ title,
291
+ did,
292
+ name,
293
+ configs,
294
+ downloadToken,
295
+ });
272
296
  }
273
297
 
274
298
  // should not be here
@@ -317,7 +341,7 @@ class BlockletManager extends BaseBlockletManager {
317
341
  const did = get(vc, 'credentialSubject.purchased.blocklet.id');
318
342
  const registry = urlObject.origin;
319
343
 
320
- return this._installFromStore({ did, registry }, { ...context, blockletPurchaseVerified: true });
344
+ return this._installFromStore({ did, registry }, context);
321
345
  }
322
346
 
323
347
  async start({ did, throwOnError, checkHealthImmediately = false, e2eMode = false }, context) {
@@ -833,6 +857,10 @@ class BlockletManager extends BaseBlockletManager {
833
857
  async upgrade({ did, registryUrl, sync }, context) {
834
858
  const blocklet = await states.blocklet.getBlocklet(did);
835
859
 
860
+ if (!registryUrl && blocklet.source === BlockletSource.url) {
861
+ return this._installFromUrl({ url: blocklet.deployedFrom }, context);
862
+ }
863
+
836
864
  // TODO: 查看了下目前页面中的升级按钮,都是会传 registryUrl 过来的,这个函数里的逻辑感觉需要在以后做一个简化
837
865
  if (!registryUrl && blocklet.source !== BlockletSource.registry) {
838
866
  throw new Error('Wrong upgrade source, empty registryUrl or not installed from blocklet registry');
@@ -1408,7 +1436,21 @@ class BlockletManager extends BaseBlockletManager {
1408
1436
  }
1409
1437
  }
1410
1438
 
1411
- async onDownload({ blocklet, context, postAction, oldBlocklet, throwOnError }) {
1439
+ /**
1440
+ *
1441
+ *
1442
+ * @param {{
1443
+ * blocklet: {},
1444
+ * context: {},
1445
+ * postAction: 'install' | 'upgrade' | 'downgrade',
1446
+ * oldBlocklet: {},
1447
+ * throwOnError: Error
1448
+ * }} params
1449
+ * @return {*}
1450
+ * @memberof BlockletManager
1451
+ */
1452
+ async onDownload(params) {
1453
+ const { blocklet, context, postAction, oldBlocklet, throwOnError } = params;
1412
1454
  const { meta } = blocklet;
1413
1455
  const { name, did, version } = meta;
1414
1456
 
@@ -1433,7 +1475,7 @@ class BlockletManager extends BaseBlockletManager {
1433
1475
 
1434
1476
  preDownloadLock.release();
1435
1477
 
1436
- const { isCancelled } = await this._downloadBlocklet(blocklet, oldBlocklet);
1478
+ const { isCancelled } = await this._downloadBlocklet(blocklet, oldBlocklet, context);
1437
1479
 
1438
1480
  if (isCancelled) {
1439
1481
  logger.info('Download was canceled manually', { name, did, version });
@@ -1624,7 +1666,21 @@ class BlockletManager extends BaseBlockletManager {
1624
1666
  }
1625
1667
  }
1626
1668
 
1627
- async _installFromStore({ did, registry, sync }, context) {
1669
+ /**
1670
+ ***
1671
+ *
1672
+ * @param {{
1673
+ * did: string;
1674
+ * registry: string;
1675
+ * sync: boolean;
1676
+ * }} params
1677
+ * @param {*} context
1678
+ * @return {*}
1679
+ * @memberof BlockletManager
1680
+ */
1681
+ async _installFromStore(params, context) {
1682
+ const { did, registry, sync } = params;
1683
+
1628
1684
  logger.debug('start install blocklet', { did });
1629
1685
  if (!isValidDid(did)) {
1630
1686
  throw new Error('Blocklet did is invalid');
@@ -1642,8 +1698,6 @@ class BlockletManager extends BaseBlockletManager {
1642
1698
  throw new Error('Can not install an already installed blocklet');
1643
1699
  }
1644
1700
 
1645
- verifyPurchase(meta, context);
1646
-
1647
1701
  // install
1648
1702
  return this._install({
1649
1703
  meta,
@@ -1654,7 +1708,20 @@ class BlockletManager extends BaseBlockletManager {
1654
1708
  });
1655
1709
  }
1656
1710
 
1657
- async _installFromUrl({ url, sync }, context) {
1711
+ /**
1712
+ *
1713
+ *
1714
+ * @param {{
1715
+ * url: string;
1716
+ * sync: boolean;
1717
+ * }} params
1718
+ * @param {{}} context
1719
+ * @return {*}
1720
+ * @memberof BlockletManager
1721
+ */
1722
+ async _installFromUrl(params, context) {
1723
+ const { url, sync } = params;
1724
+
1658
1725
  logger.debug('start install blocklet', { url });
1659
1726
 
1660
1727
  const { inStore, registryUrl, blockletDid } = await parseSourceUrl(url);
@@ -1695,7 +1762,17 @@ class BlockletManager extends BaseBlockletManager {
1695
1762
  });
1696
1763
  }
1697
1764
 
1698
- async _installComponentFromUrl({ rootDid, mountPoint, url, context, title, name: inputName, did: inputDid }) {
1765
+ async _installComponentFromUrl({
1766
+ rootDid,
1767
+ mountPoint,
1768
+ url,
1769
+ context,
1770
+ title,
1771
+ name: inputName,
1772
+ did: inputDid,
1773
+ configs,
1774
+ downloadToken,
1775
+ }) {
1699
1776
  const blocklet = await this._getBlockletForInstallation(rootDid);
1700
1777
  if (!blocklet) {
1701
1778
  throw new Error('Root blocklet does not exist');
@@ -1703,12 +1780,28 @@ class BlockletManager extends BaseBlockletManager {
1703
1780
 
1704
1781
  const meta = await getBlockletMetaFromUrl(url);
1705
1782
 
1783
+ // 如果是一个付费的blocklet,需要携带token才能下载成功
1784
+ if (!isFreeBlocklet(meta)) {
1785
+ const info = await states.node.read();
1786
+
1787
+ // eslint-disable-next-line no-param-reassign
1788
+ context = {
1789
+ ...context,
1790
+ headers: {
1791
+ 'x-server-did': info.did,
1792
+ 'x-download-token': downloadToken,
1793
+ 'x-server-publick-key': info.pk,
1794
+ 'x-server-signature': sign(info.did, info.sk, {
1795
+ exp: (Date.now() + 5 * 60 * 1000) / 1000,
1796
+ }),
1797
+ },
1798
+ };
1799
+ }
1800
+
1706
1801
  if (!isComponentBlocklet(meta)) {
1707
1802
  throw new Error('The blocklet cannot be a component');
1708
1803
  }
1709
1804
 
1710
- verifyPurchase(meta, context);
1711
-
1712
1805
  if (title) {
1713
1806
  meta.title = await titleSchema.validateAsync(title);
1714
1807
  }
@@ -1724,26 +1817,41 @@ class BlockletManager extends BaseBlockletManager {
1724
1817
  did = found.did;
1725
1818
  }
1726
1819
 
1727
- const newChildren = [
1728
- {
1729
- meta: ensureMeta(meta, { did, name }),
1730
- mountPoint,
1731
- bundleSource: { url },
1732
- dynamic: true,
1733
- children: await parseChildrenFromMeta(meta, { ...context, ancestors: [{ mountPoint }] }),
1734
- },
1735
- ];
1820
+ const newChild = {
1821
+ meta: ensureMeta(meta, { did, name }),
1822
+ mountPoint,
1823
+ bundleSource: { url },
1824
+ dynamic: true,
1825
+ children: await parseChildrenFromMeta(meta, { ...context, ancestors: [{ mountPoint }] }),
1826
+ };
1827
+
1828
+ checkDuplicateComponents([...blocklet.children, newChild]);
1829
+
1830
+ try {
1831
+ // add component to db
1832
+ await states.blocklet.addChildren(rootDid, [newChild]);
1736
1833
 
1737
- checkDuplicateComponents([...blocklet.children, ...newChildren]);
1834
+ // update navigation
1835
+ await this._upsertDynamicNavigation(blocklet.meta.did, newChild);
1738
1836
 
1739
- // add component to db
1740
- await states.blocklet.addChildren(rootDid, newChildren);
1741
- await this._upsertDynamicNavigation(blocklet.meta.did, newChildren[0]);
1837
+ // update configs
1838
+ if (Array.isArray(configs)) {
1839
+ if (hasReservedKey(configs)) {
1840
+ throw new Error('Component key of environments can not start with `ABT_NODE_` or `BLOCKLET_`');
1841
+ }
1842
+
1843
+ await states.blockletExtras.setConfigs([blocklet.meta.did, newChild.meta.did], configs);
1844
+ }
1845
+ } catch (err) {
1846
+ logger.error('Add component failed', err);
1847
+ await this._rollback('upgrade', rootDid, blocklet);
1848
+ throw err;
1849
+ }
1742
1850
 
1743
1851
  return this.updateChildren(
1744
1852
  {
1745
1853
  did: rootDid,
1746
- children: [...blocklet.children, ...newChildren],
1854
+ children: [...blocklet.children, newChild],
1747
1855
  oldBlocklet: blocklet,
1748
1856
  },
1749
1857
  context
@@ -2033,50 +2141,15 @@ class BlockletManager extends BaseBlockletManager {
2033
2141
  throw new Error('the blocklet is not installed');
2034
2142
  }
2035
2143
 
2036
- const { bundleDid } = blocklet.meta;
2037
-
2038
- let versions = this.cachedBlockletVersions.get(did);
2039
-
2040
- if (!versions) {
2041
- const { blockletRegistryList } = await states.node.read();
2042
- const tasks = blockletRegistryList.map((registry) =>
2043
- this.registry
2044
- .getBlockletMeta({ did: bundleDid, registryUrl: registry.url })
2045
- .then((item) => ({ did, version: item.version, registryUrl: registry.url }))
2046
- .catch((error) => {
2047
- if (error.response && error.response.status === 404) {
2048
- return;
2049
- }
2050
- logger.error('get blocklet meta from registry failed', { did, error });
2051
- })); // prettier-ignore
2052
-
2053
- versions = await Promise.all(tasks);
2054
- this.cachedBlockletVersions.set(did, versions);
2055
- }
2056
-
2057
- versions = versions.filter((item) => item && semver.gt(item.version, version));
2058
-
2059
- if (versions.length === 0) {
2060
- return null;
2061
- }
2062
-
2063
- // When new version found from the store where the blocklet was installed from, we should use that store first
2064
2144
  if (blocklet.source === BlockletSource.registry && blocklet.deployedFrom) {
2065
- const latestFromSameRegistry = versions.find((x) => x.registryUrl === blocklet.deployedFrom);
2066
- if (latestFromSameRegistry) {
2067
- return latestFromSameRegistry;
2068
- }
2145
+ return this._getLatestBlockletVersionFromStore({ blocklet, version });
2069
2146
  }
2070
2147
 
2071
- // Otherwise try upgrading from other store
2072
- let latestBlockletVersion = versions[0];
2073
- versions.forEach((item) => {
2074
- if (semver.lt(latestBlockletVersion.version, item.version)) {
2075
- latestBlockletVersion = item;
2076
- }
2077
- });
2148
+ if (blocklet.source === BlockletSource.url && blocklet.deployedFrom) {
2149
+ return this._getLatestBlockletVersionFromUrl({ blocklet, version });
2150
+ }
2078
2151
 
2079
- return latestBlockletVersion;
2152
+ return null;
2080
2153
  }
2081
2154
 
2082
2155
  getCrons() {
@@ -2132,7 +2205,22 @@ class BlockletManager extends BaseBlockletManager {
2132
2205
  .filter((x) => (skipDeleted ? x.status !== BlockletStatus.deleted : true));
2133
2206
  }
2134
2207
 
2135
- async _install({ meta, source, deployedFrom, context, sync }) {
2208
+ /**
2209
+ *
2210
+ *
2211
+ * @param {{
2212
+ * meta: {}; // blocklet meta
2213
+ * source: number; // example: BlockletSource.registry,
2214
+ * deployedFrom: string;
2215
+ * context: {}
2216
+ * sync: boolean = false;
2217
+ * }} params
2218
+ * @return {*}
2219
+ * @memberof BlockletManager
2220
+ */
2221
+ async _install(params) {
2222
+ const { meta, source, deployedFrom, context, sync } = params;
2223
+
2136
2224
  validateBlockletMeta(meta, { ensureDist: true });
2137
2225
 
2138
2226
  const { name, did, version } = meta;
@@ -2141,6 +2229,7 @@ class BlockletManager extends BaseBlockletManager {
2141
2229
 
2142
2230
  const children = await this._getChildrenForInstallation(meta);
2143
2231
  try {
2232
+ // 创建一个blocklet到database
2144
2233
  const blocklet = await states.blocklet.addBlocklet({
2145
2234
  meta,
2146
2235
  source,
@@ -2157,7 +2246,15 @@ class BlockletManager extends BaseBlockletManager {
2157
2246
  const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
2158
2247
  this.emit(BlockletEvents.added, blocklet1);
2159
2248
 
2160
- // download
2249
+ /** @type {{
2250
+ * blocklet: any;
2251
+ * oldBlocklet: {
2252
+ * children: any[];
2253
+ * extraState: any;
2254
+ * };
2255
+ * context: {};
2256
+ * postAction: string
2257
+ * }} */
2161
2258
  const downloadParams = {
2162
2259
  blocklet: { ...blocklet1 },
2163
2260
  oldBlocklet: {
@@ -2220,6 +2317,13 @@ class BlockletManager extends BaseBlockletManager {
2220
2317
  }
2221
2318
  }
2222
2319
 
2320
+ /**
2321
+ *
2322
+ *
2323
+ * @param {{}} { meta, source, deployedFrom, context, sync }
2324
+ * @return {*}
2325
+ * @memberof BlockletManager
2326
+ */
2223
2327
  async _upgrade({ meta, source, deployedFrom, context, sync }) {
2224
2328
  validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
2225
2329
 
@@ -2242,6 +2346,26 @@ class BlockletManager extends BaseBlockletManager {
2242
2346
 
2243
2347
  this.emit(BlockletEvents.statusChange, newBlocklet);
2244
2348
 
2349
+ // 如果是一个付费的blocklet,需要携带token才能下载成功
2350
+ if (!isFreeBlocklet(meta)) {
2351
+ const blocklet = await states.blocklet.getBlocklet(did);
2352
+
2353
+ const info = await states.node.read();
2354
+
2355
+ // eslint-disable-next-line no-param-reassign
2356
+ context = {
2357
+ ...context,
2358
+ headers: {
2359
+ 'x-server-did': info.did,
2360
+ 'x-download-token': blocklet?.tokens?.paidBlockletDownloadToken,
2361
+ 'x-server-publick-key': info.pk,
2362
+ 'x-server-signature': sign(info.did, info.sk, {
2363
+ exp: (Date.now() + 5 * 60 * 1000) / 1000,
2364
+ }),
2365
+ },
2366
+ };
2367
+ }
2368
+
2245
2369
  // download
2246
2370
  const downloadParams = {
2247
2371
  oldBlocklet: { ...oldBlocklet },
@@ -2255,7 +2379,6 @@ class BlockletManager extends BaseBlockletManager {
2255
2379
  await this.onDownload({ ...downloadParams, throwOnError: true });
2256
2380
  return states.blocklet.getBlocklet(did);
2257
2381
  }
2258
-
2259
2382
  const ticket = this.installQueue.push(
2260
2383
  {
2261
2384
  entity: 'blocklet',
@@ -2469,6 +2592,14 @@ class BlockletManager extends BaseBlockletManager {
2469
2592
  // Update dynamic component meta in blocklet settings
2470
2593
  await this._ensureDynamicChildrenInSettings(blocklet);
2471
2594
 
2595
+ if (context?.headers?.['x-download-token']) {
2596
+ await states.blocklet.updateBlocklet(did, {
2597
+ tokens: {
2598
+ paidBlockletDownloadToken: context.headers['x-download-token'],
2599
+ },
2600
+ });
2601
+ }
2602
+
2472
2603
  states.notification.create({
2473
2604
  title: 'Blocklet Installed',
2474
2605
  description: `Blocklet ${meta.name}@${meta.version} is installed successfully. (Source: ${
@@ -2657,11 +2788,23 @@ class BlockletManager extends BaseBlockletManager {
2657
2788
  }
2658
2789
 
2659
2790
  /**
2660
- * for download: cwd, tarball, did
2661
- * for verify: verify, integrity
2662
- * for cancel control: ctrlStore, rootDid
2791
+ *
2792
+ *
2793
+ * @param {{
2794
+ * url: string,
2795
+ * cwd: string,
2796
+ * tarball: string,
2797
+ * did: string,
2798
+ * integrity: string,
2799
+ * verify: boolean = true,
2800
+ * ctrlStore: {},
2801
+ * rootDid: string,
2802
+ * context: {} = {},
2803
+ * }} { url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid, context = {} }
2804
+ * @return {*}
2805
+ * @memberof BlockletManager
2663
2806
  */
2664
- async _downloadTarball({ url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid }) {
2807
+ async _downloadTarball({ url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid, context = {} }) {
2665
2808
  fs.mkdirSync(cwd, { recursive: true });
2666
2809
 
2667
2810
  const tarballName = url.split('/').slice(-1)[0];
@@ -2673,7 +2816,7 @@ class BlockletManager extends BaseBlockletManager {
2673
2816
  const cachedTarFile = await this._getCachedTarFile(integrity);
2674
2817
  if (cachedTarFile) {
2675
2818
  logger.info('found cache tarFile', { did, tarballName, integrity });
2676
- await fs.move(cachedTarFile, tarballPath);
2819
+ await fs.move(cachedTarFile, tarballPath, { overwrite: true });
2677
2820
  } else if (protocol.startsWith('file')) {
2678
2821
  await fs.copy(decodeURIComponent(pathname), tarballPath);
2679
2822
  } else {
@@ -2684,7 +2827,7 @@ class BlockletManager extends BaseBlockletManager {
2684
2827
  }
2685
2828
  ctrlStore[rootDid].set(did, cancelCtrl);
2686
2829
 
2687
- await downloadFile(url, path.join(cwd, tarballName), { cancelCtrl });
2830
+ await downloadFile(url, path.join(cwd, tarballName), { cancelCtrl }, context);
2688
2831
 
2689
2832
  if (ctrlStore[rootDid]) {
2690
2833
  ctrlStore[rootDid].delete(did);
@@ -2773,12 +2916,16 @@ class BlockletManager extends BaseBlockletManager {
2773
2916
  }
2774
2917
 
2775
2918
  /**
2776
- * download bundle, verify bundle, resolve bundle to installDir
2777
- * @param {object} meta
2778
- * @param {string} rootDid root blocklet did of the blocklet to be downloaded
2779
- * @return {object} { isCancelled: Boolean }
2919
+ *
2920
+ *
2921
+ * @param {*} meta
2922
+ * @param {*} rootDid
2923
+ * @param {*} url
2924
+ * @param {{}} [context={}]
2925
+ * @return {*}
2926
+ * @memberof BlockletManager
2780
2927
  */
2781
- async _downloadBundle(meta, rootDid, url) {
2928
+ async _downloadBundle(meta, rootDid, url, context = {}) {
2782
2929
  const { bundleName: name, bundleDid: did, version, dist = {} } = meta;
2783
2930
  const { tarball, integrity } = dist;
2784
2931
 
@@ -2806,6 +2953,7 @@ class BlockletManager extends BaseBlockletManager {
2806
2953
  ctrlStore: this.downloadCtrls,
2807
2954
  rootDid,
2808
2955
  url,
2956
+ context,
2809
2957
  });
2810
2958
  logger.info('downloaded blocklet tar file', { name, version, tarballPath });
2811
2959
  if (tarballPath === downloadFile.CANCEL) {
@@ -2826,7 +2974,16 @@ class BlockletManager extends BaseBlockletManager {
2826
2974
  }
2827
2975
  }
2828
2976
 
2829
- async _downloadBlocklet(blocklet, oldBlocklet = {}) {
2977
+ /**
2978
+ *
2979
+ *
2980
+ * @param {{}} blocklet
2981
+ * @param {{}} [oldBlocklet={}]
2982
+ * @param {{}} [context={}]
2983
+ * @return {*}
2984
+ * @memberof BlockletManager
2985
+ */
2986
+ async _downloadBlocklet(blocklet, oldBlocklet = {}, context = {}) {
2830
2987
  const {
2831
2988
  meta: { name, did },
2832
2989
  } = blocklet;
@@ -2880,7 +3037,7 @@ class BlockletManager extends BaseBlockletManager {
2880
3037
  tarball: get(meta, 'dist.tarball'),
2881
3038
  registryUrl: blocklet.source === BlockletSource.registry ? blocklet.deployedFrom : undefined,
2882
3039
  });
2883
- tasks.push(this._downloadBundle(meta, did, url));
3040
+ tasks.push(this._downloadBundle(meta, did, url, context));
2884
3041
  }
2885
3042
  const results = await Promise.all(tasks);
2886
3043
  if (results.find((x) => x.isCancelled)) {
@@ -2976,6 +3133,9 @@ class BlockletManager extends BaseBlockletManager {
2976
3133
  const logsDir = path.join(this.dataDirs.logs, name);
2977
3134
  const cacheDir = path.join(this.dataDirs.cache, name);
2978
3135
 
3136
+ // Cleanup db
3137
+ await this.teamManager.deleteTeam(blocklet.meta.did);
3138
+
2979
3139
  // Cleanup disk storage
2980
3140
  fs.removeSync(cacheDir);
2981
3141
  if (keepData === false) {
@@ -3068,6 +3228,78 @@ class BlockletManager extends BaseBlockletManager {
3068
3228
 
3069
3229
  return blocklet;
3070
3230
  }
3231
+
3232
+ async _getLatestBlockletVersionFromStore({ blocklet, version }) {
3233
+ const { did, bundleDid } = blocklet.meta;
3234
+
3235
+ let versions = this.cachedBlockletVersions.get(did);
3236
+
3237
+ if (!versions) {
3238
+ const { blockletRegistryList } = await states.node.read();
3239
+ const tasks = blockletRegistryList.map((registry) =>
3240
+ this.registry
3241
+ .getBlockletMeta({ did: bundleDid, registryUrl: registry.url })
3242
+ .then((item) => ({ did, version: item.version, registryUrl: registry.url }))
3243
+ .catch((error) => {
3244
+ if (error.response && error.response.status === 404) {
3245
+ return;
3246
+ }
3247
+ logger.error('get blocklet meta from registry failed', { did, error });
3248
+ })); // prettier-ignore
3249
+
3250
+ versions = await Promise.all(tasks);
3251
+ this.cachedBlockletVersions.set(did, versions);
3252
+ }
3253
+
3254
+ versions = versions.filter((item) => item && semver.gt(item.version, version));
3255
+
3256
+ if (versions.length === 0) {
3257
+ return null;
3258
+ }
3259
+
3260
+ // When new version found from the store where the blocklet was installed from, we should use that store first
3261
+ if (blocklet.source === BlockletSource.registry && blocklet.deployedFrom) {
3262
+ const latestFromSameRegistry = versions.find((x) => x.registryUrl === blocklet.deployedFrom);
3263
+ if (latestFromSameRegistry) {
3264
+ return latestFromSameRegistry;
3265
+ }
3266
+ }
3267
+
3268
+ // Otherwise try upgrading from other store
3269
+ let latestBlockletVersion = versions[0];
3270
+ versions.forEach((item) => {
3271
+ if (semver.lt(latestBlockletVersion.version, item.version)) {
3272
+ latestBlockletVersion = item;
3273
+ }
3274
+ });
3275
+
3276
+ return latestBlockletVersion;
3277
+ }
3278
+
3279
+ async _getLatestBlockletVersionFromUrl({ blocklet, version }) {
3280
+ const { did } = blocklet.meta;
3281
+
3282
+ let versions = this.cachedBlockletVersions.get(did);
3283
+
3284
+ if (!versions) {
3285
+ try {
3286
+ const item = await getBlockletMetaFromUrl(blocklet.deployedFrom);
3287
+ versions = [{ did, version: item.version }];
3288
+ } catch (error) {
3289
+ logger.error('get blocklet meta from url failed when checking latest version', { did, error });
3290
+ versions = [];
3291
+ }
3292
+ }
3293
+
3294
+ this.cachedBlockletVersions.set(did, versions);
3295
+ versions = versions.filter((item) => item && semver.gt(item.version, version));
3296
+
3297
+ if (versions.length === 0) {
3298
+ return null;
3299
+ }
3300
+
3301
+ return versions[0];
3302
+ }
3071
3303
  }
3072
3304
 
3073
3305
  module.exports = BlockletManager;
package/lib/index.js CHANGED
@@ -117,6 +117,7 @@ function ABTNode(options) {
117
117
  maintainerEmail: DEFAULT_CERTIFICATE_EMAIL,
118
118
  dataDir: dataDirs.certManagerModule,
119
119
  });
120
+
120
121
  const routerManager = new RouterManager({ certManager });
121
122
  const routingSnapshot = new RoutingSnapshot({
122
123
  baseDir: dataDirs.core,
@@ -126,13 +127,18 @@ function ABTNode(options) {
126
127
  return { sites };
127
128
  },
128
129
  });
130
+
129
131
  const blockletRegistry = new BlockletRegistry();
132
+
133
+ const teamManager = new TeamManager({ nodeDid: options.nodeDid, dataDirs, states });
134
+
130
135
  const blockletManager = new BlockletManager({
131
136
  dataDirs,
132
137
  startQueue,
133
138
  installQueue,
134
139
  registry: blockletRegistry,
135
140
  daemon: options.daemon,
141
+ teamManager,
136
142
  });
137
143
  blockletManager.setMaxListeners(0);
138
144
 
@@ -156,7 +162,6 @@ function ABTNode(options) {
156
162
  getRouterProvider,
157
163
  } = getRouterHelpers({ dataDirs, routingSnapshot, routerManager, blockletManager, certManager });
158
164
 
159
- const teamManager = new TeamManager({ nodeDid: options.nodeDid, dataDirs, states });
160
165
  const nodeAPI = new NodeAPI(states.node, blockletRegistry);
161
166
  const teamAPI = new TeamAPI({ states, teamManager });
162
167
 
@@ -272,6 +277,7 @@ function ABTNode(options) {
272
277
  // Account
273
278
  getUsers: teamAPI.getUsers.bind(teamAPI),
274
279
  getUsersCount: teamAPI.getUsersCount.bind(teamAPI),
280
+ getUsersCountPerRole: teamAPI.getUsersCountPerRole.bind(teamAPI),
275
281
  getUser: teamAPI.getUser.bind(teamAPI),
276
282
  getOwner: teamAPI.getOwner.bind(teamAPI),
277
283
  getNodeUsers: () => teamAPI.getUsers({ teamDid: options.nodeDid }),
@@ -95,19 +95,16 @@ class Router {
95
95
  }
96
96
 
97
97
  async updateRoutingTable() {
98
+ logger.info('update routing table');
99
+
98
100
  const { sites, certificates, headers = {}, services = [], nodeInfo = {} } = (await this.getRoutingParams()) || {};
99
101
  if (!Array.isArray(sites)) {
100
102
  logger.error('sites is not an array', { sites });
101
103
  return;
102
104
  }
103
105
 
104
- logger.info('updateRoutingTable sites:', { sites });
105
-
106
106
  this.routingTable = getRoutingTable({ sites, nodeInfo });
107
107
 
108
- logger.info('updateRoutingTable routingTable:', { routingTable: this.routingTable });
109
- logger.info('updateRoutingTable certificates:', { certificates: certificates.map((item) => item.domain) });
110
-
111
108
  const requestLimit = nodeInfo.routing.requestLimit || { enable: false, rate: GATEWAY_REQ_LIMIT.min };
112
109
  if (requestLimit.enabled) {
113
110
  requestLimit.maxInstantRate = requestLimit.rate >= 20 ? 20 : requestLimit.rate + 20;
@@ -1,3 +1,5 @@
1
+ const pickBy = require('lodash/pickBy');
2
+
1
3
  const logger = require('@abtnode/logger')('@abtnode/core:states:user');
2
4
  const { PASSPORT_STATUS } = require('@abtnode/constant');
3
5
  const BaseState = require('./base');
@@ -7,6 +9,8 @@ const fixPassports = (doc) => {
7
9
  doc.passports = (doc.passports || []).filter((x) => x.id);
8
10
  };
9
11
 
12
+ const isNullOrUndefined = (x) => x === undefined || x === null;
13
+
10
14
  class User extends BaseState {
11
15
  constructor(baseDir, options = {}) {
12
16
  super(baseDir, { filename: 'user.db', ...options });
@@ -85,13 +89,45 @@ class User extends BaseState {
85
89
  return doc;
86
90
  }
87
91
 
88
- async getUsers() {
89
- const docs = await super.find();
90
- if (docs) {
91
- docs.forEach(fixPassports); // backward compatible
92
+ async getUsers({ query, sort, paging: inputPaging } = {}) {
93
+ const { approved, role, search } = query || {};
94
+
95
+ // make query param
96
+ const queryParam = {};
97
+
98
+ if (!isNullOrUndefined(approved)) {
99
+ queryParam.approved = !!approved;
100
+ }
101
+
102
+ if (search) {
103
+ queryParam.$or = [{ fullName: search }, { did: search }];
104
+ }
105
+
106
+ if (role && role !== '$all') {
107
+ if (role === '$none') {
108
+ queryParam.passports = { $size: 0 };
109
+ } else {
110
+ queryParam.passports = { $elemMatch: { name: role, status: PASSPORT_STATUS.VALID } };
111
+ }
112
+ }
113
+
114
+ const sortParam = pickBy(sort, (x) => !isNullOrUndefined(x));
115
+
116
+ if (!Object.keys(sortParam).length) {
117
+ sortParam.createdAt = -1;
118
+ }
119
+
120
+ // get data
121
+ const { list, paging } = await this.paginate(queryParam, sortParam, inputPaging);
122
+
123
+ if (list) {
124
+ list.forEach(fixPassports); // backward compatible
92
125
  }
93
126
 
94
- return docs;
127
+ return {
128
+ list,
129
+ paging,
130
+ };
95
131
  }
96
132
 
97
133
  async getUser(did) {
@@ -17,6 +17,18 @@ const { isCLI } = require('../util');
17
17
 
18
18
  const rbacCreationLock = new Lock('rbac-creation-lock');
19
19
 
20
+ const closeDatabase = async (db) =>
21
+ new Promise((resolve, reject) => {
22
+ db.closeDatabase((err) => {
23
+ if (err) {
24
+ reject(err);
25
+ return;
26
+ }
27
+
28
+ resolve();
29
+ });
30
+ });
31
+
20
32
  class TeamManager extends EventEmitter {
21
33
  constructor({ nodeDid, dataDirs, states }) {
22
34
  super();
@@ -48,10 +60,6 @@ class TeamManager extends EventEmitter {
48
60
  });
49
61
  });
50
62
 
51
- this.states.blocklet.on('remove', ({ meta: { did } }) => {
52
- this.cache[did] = null;
53
- });
54
-
55
63
  // init blocklet
56
64
  this.states.blocklet
57
65
  .getBlocklets()
@@ -280,6 +288,28 @@ class TeamManager extends EventEmitter {
280
288
  return owner;
281
289
  }
282
290
 
291
+ async deleteTeam(did) {
292
+ if (this.cache[did]) {
293
+ try {
294
+ if (this.cache[did].rbac) {
295
+ await closeDatabase(this.cache[did].rbac.storage.db);
296
+ }
297
+
298
+ if (this.cache[did].user) {
299
+ await closeDatabase(this.cache[did].user.db);
300
+ }
301
+
302
+ if (this.cache[did].session) {
303
+ await closeDatabase(this.cache[did].session.db);
304
+ }
305
+ } catch (err) {
306
+ logger.error('Failed to close database', { did, err });
307
+ }
308
+ }
309
+
310
+ this.cache[did] = null;
311
+ }
312
+
283
313
  // =======
284
314
  // Private
285
315
  // =======
@@ -44,7 +44,6 @@ const getBlockletInfo = require('@blocklet/meta/lib/info');
44
44
  const { validateMeta, fixAndValidateService } = require('@blocklet/meta/lib/validate');
45
45
  const {
46
46
  forEachBlocklet,
47
- isFreeBlocklet,
48
47
  getDisplayName,
49
48
  findWebInterface,
50
49
  forEachBlockletSync,
@@ -338,7 +337,8 @@ const getRuntimeEnvironments = (blocklet, nodeEnvironments, ancestors) => {
338
337
  };
339
338
  };
340
339
 
341
- const isUsefulError = (err) => err && err.message !== 'process or namespace not found';
340
+ const isUsefulError = (err) =>
341
+ err && err.message !== 'process or namespace not found' && !/^Process \d+ not found$/.test(err.message);
342
342
 
343
343
  const getHealthyCheckTimeout = (blocklet, { checkHealthImmediately } = {}) => {
344
344
  let minConsecutiveTime = 5000;
@@ -724,8 +724,6 @@ const parseChildrenFromMeta = async (src, context = {}) => {
724
724
 
725
725
  validateBlockletMeta(m, { ensureDist: true });
726
726
 
727
- verifyPurchase(m, context);
728
-
729
727
  if (!isComponentBlocklet(m)) {
730
728
  throw new Error(`The blocklet cannot be a component: ${m.title}`);
731
729
  }
@@ -1227,18 +1225,6 @@ const needBlockletDownload = (blocklet, oldBlocklet) => {
1227
1225
  return !(get(oldBlocklet, 'meta.dist.integrity') === get(blocklet, 'meta.dist.integrity'));
1228
1226
  };
1229
1227
 
1230
- const verifyPurchase = (meta, opts = {}) => {
1231
- if (opts.blockletPurchaseVerified) {
1232
- return true;
1233
- }
1234
-
1235
- if (isFreeBlocklet(meta)) {
1236
- return true;
1237
- }
1238
-
1239
- throw new Error('Can not install a non-free blocklet directly');
1240
- };
1241
-
1242
1228
  const findAvailableDid = (meta, siblings) => {
1243
1229
  const reg = /-(\d+)$/;
1244
1230
  const match = reg.exec(meta.name);
@@ -1316,7 +1302,6 @@ module.exports = {
1316
1302
  getDiffFiles,
1317
1303
  getBundleDir,
1318
1304
  needBlockletDownload,
1319
- verifyPurchase,
1320
1305
  findAvailableDid,
1321
1306
  ensureMeta,
1322
1307
  getSourceUrlsFromConfig,
package/lib/util/index.js CHANGED
@@ -68,7 +68,7 @@ const getInterfaceUrl = ({ baseUrl, url }) => {
68
68
 
69
69
  const trimSlash = (str = '') => str.replace(/^\/+/, '').replace(/\/+$/, '');
70
70
 
71
- const replaceDomainSlot = ({ domain, context, nodeIp }) => {
71
+ const replaceDomainSlot = ({ domain, context = {}, nodeIp }) => {
72
72
  let processed = domain;
73
73
  if (processed.includes(SLOT_FOR_IP_DNS_SITE)) {
74
74
  const ipRegex = /\d+[-.]\d+[-.]\d+[-.]\d+/;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.7.24",
6
+ "version": "1.7.27",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,30 +19,31 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/certificate-manager": "1.7.24",
23
- "@abtnode/constant": "1.7.24",
24
- "@abtnode/cron": "1.7.24",
25
- "@abtnode/db": "1.7.24",
26
- "@abtnode/logger": "1.7.24",
27
- "@abtnode/queue": "1.7.24",
28
- "@abtnode/rbac": "1.7.24",
29
- "@abtnode/router-provider": "1.7.24",
30
- "@abtnode/static-server": "1.7.24",
31
- "@abtnode/timemachine": "1.7.24",
32
- "@abtnode/util": "1.7.24",
33
- "@arcblock/did": "^1.16.15",
22
+ "@abtnode/certificate-manager": "1.7.27",
23
+ "@abtnode/constant": "1.7.27",
24
+ "@abtnode/cron": "1.7.27",
25
+ "@abtnode/db": "1.7.27",
26
+ "@abtnode/logger": "1.7.27",
27
+ "@abtnode/queue": "1.7.27",
28
+ "@abtnode/rbac": "1.7.27",
29
+ "@abtnode/router-provider": "1.7.27",
30
+ "@abtnode/static-server": "1.7.27",
31
+ "@abtnode/timemachine": "1.7.27",
32
+ "@abtnode/util": "1.7.27",
33
+ "@arcblock/did": "1.17.0",
34
34
  "@arcblock/did-motif": "^1.1.10",
35
- "@arcblock/did-util": "^1.16.15",
36
- "@arcblock/event-hub": "1.16.15",
35
+ "@arcblock/did-util": "1.17.0",
36
+ "@arcblock/event-hub": "1.17.0",
37
+ "@arcblock/jwt": "^1.17.0",
37
38
  "@arcblock/pm2-events": "^0.0.5",
38
- "@arcblock/vc": "^1.16.15",
39
- "@blocklet/meta": "1.7.24",
39
+ "@arcblock/vc": "1.17.0",
40
+ "@blocklet/meta": "1.7.27",
40
41
  "@fidm/x509": "^1.2.1",
41
42
  "@nedb/core": "^1.2.2",
42
43
  "@nedb/multi": "^1.2.2",
43
- "@ocap/mcrypto": "^1.16.15",
44
- "@ocap/util": "^1.16.15",
45
- "@ocap/wallet": "^1.16.15",
44
+ "@ocap/mcrypto": "1.17.0",
45
+ "@ocap/util": "1.17.0",
46
+ "@ocap/wallet": "1.17.0",
46
47
  "@slack/webhook": "^5.0.3",
47
48
  "axios": "^0.27.2",
48
49
  "axon": "^2.0.3",
@@ -80,5 +81,5 @@
80
81
  "express": "^4.17.1",
81
82
  "jest": "^27.4.5"
82
83
  },
83
- "gitHead": "904178e4dd0cd14c2a0a8b9131708c7ec68351e1"
84
+ "gitHead": "81a5492df66389b0aede13f033d1e493450833bc"
84
85
  }