@abtnode/core 1.7.25 → 1.8.0

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) {
@@ -684,7 +708,7 @@ class BlockletManager extends BaseBlockletManager {
684
708
 
685
709
  if (!attachRuntimeInfo) {
686
710
  try {
687
- const blocklet = await this.ensureBlocklet(did);
711
+ const blocklet = await this.ensureBlocklet(did, { throwOnNotExist: false });
688
712
  return blocklet;
689
713
  } catch (e) {
690
714
  logger.error('get blocklet detail error', { error: e.message });
@@ -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');
@@ -1202,14 +1230,17 @@ class BlockletManager extends BaseBlockletManager {
1202
1230
  return rootBlocklet;
1203
1231
  }
1204
1232
 
1205
- async ensureBlocklet(did, { e2eMode = false, validateEnv = true } = {}) {
1233
+ async ensureBlocklet(did, { e2eMode = false, validateEnv = true, throwOnNotExist = true } = {}) {
1206
1234
  if (!isValidDid(did)) {
1207
1235
  throw new Error(`Blocklet did is invalid: ${did}`);
1208
1236
  }
1209
1237
 
1210
1238
  const blocklet = await states.blocklet.getBlocklet(did);
1211
1239
  if (!blocklet) {
1212
- throw new Error(`Can not find blocklet in database by did ${did}`);
1240
+ if (throwOnNotExist) {
1241
+ throw new Error(`Can not find blocklet in database by did ${did}`);
1242
+ }
1243
+ return null;
1213
1244
  }
1214
1245
 
1215
1246
  // app settings
@@ -1311,7 +1342,12 @@ class BlockletManager extends BaseBlockletManager {
1311
1342
  }
1312
1343
 
1313
1344
  try {
1314
- let blocklet = await this.ensureBlocklet(did);
1345
+ let blocklet = await this.ensureBlocklet(did, { throwOnNotExist: false });
1346
+
1347
+ if (!blocklet) {
1348
+ return null;
1349
+ }
1350
+
1315
1351
  const fromCache = !!cachedBlocklet;
1316
1352
 
1317
1353
  // if from cached data, only use cache data of runtime info (engine, diskInfo, runtimeInfo...)
@@ -1408,7 +1444,21 @@ class BlockletManager extends BaseBlockletManager {
1408
1444
  }
1409
1445
  }
1410
1446
 
1411
- async onDownload({ blocklet, context, postAction, oldBlocklet, throwOnError }) {
1447
+ /**
1448
+ *
1449
+ *
1450
+ * @param {{
1451
+ * blocklet: {},
1452
+ * context: {},
1453
+ * postAction: 'install' | 'upgrade' | 'downgrade',
1454
+ * oldBlocklet: {},
1455
+ * throwOnError: Error
1456
+ * }} params
1457
+ * @return {*}
1458
+ * @memberof BlockletManager
1459
+ */
1460
+ async onDownload(params) {
1461
+ const { blocklet, context, postAction, oldBlocklet, throwOnError } = params;
1412
1462
  const { meta } = blocklet;
1413
1463
  const { name, did, version } = meta;
1414
1464
 
@@ -1433,7 +1483,7 @@ class BlockletManager extends BaseBlockletManager {
1433
1483
 
1434
1484
  preDownloadLock.release();
1435
1485
 
1436
- const { isCancelled } = await this._downloadBlocklet(blocklet, oldBlocklet);
1486
+ const { isCancelled } = await this._downloadBlocklet(blocklet, oldBlocklet, context);
1437
1487
 
1438
1488
  if (isCancelled) {
1439
1489
  logger.info('Download was canceled manually', { name, did, version });
@@ -1624,7 +1674,21 @@ class BlockletManager extends BaseBlockletManager {
1624
1674
  }
1625
1675
  }
1626
1676
 
1627
- async _installFromStore({ did, registry, sync }, context) {
1677
+ /**
1678
+ ***
1679
+ *
1680
+ * @param {{
1681
+ * did: string;
1682
+ * registry: string;
1683
+ * sync: boolean;
1684
+ * }} params
1685
+ * @param {*} context
1686
+ * @return {*}
1687
+ * @memberof BlockletManager
1688
+ */
1689
+ async _installFromStore(params, context) {
1690
+ const { did, registry, sync } = params;
1691
+
1628
1692
  logger.debug('start install blocklet', { did });
1629
1693
  if (!isValidDid(did)) {
1630
1694
  throw new Error('Blocklet did is invalid');
@@ -1642,8 +1706,6 @@ class BlockletManager extends BaseBlockletManager {
1642
1706
  throw new Error('Can not install an already installed blocklet');
1643
1707
  }
1644
1708
 
1645
- verifyPurchase(meta, context);
1646
-
1647
1709
  // install
1648
1710
  return this._install({
1649
1711
  meta,
@@ -1654,7 +1716,20 @@ class BlockletManager extends BaseBlockletManager {
1654
1716
  });
1655
1717
  }
1656
1718
 
1657
- async _installFromUrl({ url, sync }, context) {
1719
+ /**
1720
+ *
1721
+ *
1722
+ * @param {{
1723
+ * url: string;
1724
+ * sync: boolean;
1725
+ * }} params
1726
+ * @param {{}} context
1727
+ * @return {*}
1728
+ * @memberof BlockletManager
1729
+ */
1730
+ async _installFromUrl(params, context) {
1731
+ const { url, sync } = params;
1732
+
1658
1733
  logger.debug('start install blocklet', { url });
1659
1734
 
1660
1735
  const { inStore, registryUrl, blockletDid } = await parseSourceUrl(url);
@@ -1695,7 +1770,17 @@ class BlockletManager extends BaseBlockletManager {
1695
1770
  });
1696
1771
  }
1697
1772
 
1698
- async _installComponentFromUrl({ rootDid, mountPoint, url, context, title, name: inputName, did: inputDid }) {
1773
+ async _installComponentFromUrl({
1774
+ rootDid,
1775
+ mountPoint,
1776
+ url,
1777
+ context,
1778
+ title,
1779
+ name: inputName,
1780
+ did: inputDid,
1781
+ configs,
1782
+ downloadToken,
1783
+ }) {
1699
1784
  const blocklet = await this._getBlockletForInstallation(rootDid);
1700
1785
  if (!blocklet) {
1701
1786
  throw new Error('Root blocklet does not exist');
@@ -1703,12 +1788,28 @@ class BlockletManager extends BaseBlockletManager {
1703
1788
 
1704
1789
  const meta = await getBlockletMetaFromUrl(url);
1705
1790
 
1791
+ // 如果是一个付费的blocklet,需要携带token才能下载成功
1792
+ if (!isFreeBlocklet(meta)) {
1793
+ const info = await states.node.read();
1794
+
1795
+ // eslint-disable-next-line no-param-reassign
1796
+ context = {
1797
+ ...context,
1798
+ headers: {
1799
+ 'x-server-did': info.did,
1800
+ 'x-download-token': downloadToken,
1801
+ 'x-server-publick-key': info.pk,
1802
+ 'x-server-signature': sign(info.did, info.sk, {
1803
+ exp: (Date.now() + 5 * 60 * 1000) / 1000,
1804
+ }),
1805
+ },
1806
+ };
1807
+ }
1808
+
1706
1809
  if (!isComponentBlocklet(meta)) {
1707
1810
  throw new Error('The blocklet cannot be a component');
1708
1811
  }
1709
1812
 
1710
- verifyPurchase(meta, context);
1711
-
1712
1813
  if (title) {
1713
1814
  meta.title = await titleSchema.validateAsync(title);
1714
1815
  }
@@ -1724,26 +1825,41 @@ class BlockletManager extends BaseBlockletManager {
1724
1825
  did = found.did;
1725
1826
  }
1726
1827
 
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
- ];
1828
+ const newChild = {
1829
+ meta: ensureMeta(meta, { did, name }),
1830
+ mountPoint,
1831
+ bundleSource: { url },
1832
+ dynamic: true,
1833
+ children: await parseChildrenFromMeta(meta, { ...context, ancestors: [{ mountPoint }] }),
1834
+ };
1736
1835
 
1737
- checkDuplicateComponents([...blocklet.children, ...newChildren]);
1836
+ checkDuplicateComponents([...blocklet.children, newChild]);
1738
1837
 
1739
- // add component to db
1740
- await states.blocklet.addChildren(rootDid, newChildren);
1741
- await this._upsertDynamicNavigation(blocklet.meta.did, newChildren[0]);
1838
+ try {
1839
+ // add component to db
1840
+ await states.blocklet.addChildren(rootDid, [newChild]);
1841
+
1842
+ // update navigation
1843
+ await this._upsertDynamicNavigation(blocklet.meta.did, newChild);
1844
+
1845
+ // update configs
1846
+ if (Array.isArray(configs)) {
1847
+ if (hasReservedKey(configs)) {
1848
+ throw new Error('Component key of environments can not start with `ABT_NODE_` or `BLOCKLET_`');
1849
+ }
1850
+
1851
+ await states.blockletExtras.setConfigs([blocklet.meta.did, newChild.meta.did], configs);
1852
+ }
1853
+ } catch (err) {
1854
+ logger.error('Add component failed', err);
1855
+ await this._rollback('upgrade', rootDid, blocklet);
1856
+ throw err;
1857
+ }
1742
1858
 
1743
1859
  return this.updateChildren(
1744
1860
  {
1745
1861
  did: rootDid,
1746
- children: [...blocklet.children, ...newChildren],
1862
+ children: [...blocklet.children, newChild],
1747
1863
  oldBlocklet: blocklet,
1748
1864
  },
1749
1865
  context
@@ -2033,50 +2149,15 @@ class BlockletManager extends BaseBlockletManager {
2033
2149
  throw new Error('the blocklet is not installed');
2034
2150
  }
2035
2151
 
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
2152
  if (blocklet.source === BlockletSource.registry && blocklet.deployedFrom) {
2065
- const latestFromSameRegistry = versions.find((x) => x.registryUrl === blocklet.deployedFrom);
2066
- if (latestFromSameRegistry) {
2067
- return latestFromSameRegistry;
2068
- }
2153
+ return this._getLatestBlockletVersionFromStore({ blocklet, version });
2069
2154
  }
2070
2155
 
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
- });
2156
+ if (blocklet.source === BlockletSource.url && blocklet.deployedFrom) {
2157
+ return this._getLatestBlockletVersionFromUrl({ blocklet, version });
2158
+ }
2078
2159
 
2079
- return latestBlockletVersion;
2160
+ return null;
2080
2161
  }
2081
2162
 
2082
2163
  getCrons() {
@@ -2132,7 +2213,22 @@ class BlockletManager extends BaseBlockletManager {
2132
2213
  .filter((x) => (skipDeleted ? x.status !== BlockletStatus.deleted : true));
2133
2214
  }
2134
2215
 
2135
- async _install({ meta, source, deployedFrom, context, sync }) {
2216
+ /**
2217
+ *
2218
+ *
2219
+ * @param {{
2220
+ * meta: {}; // blocklet meta
2221
+ * source: number; // example: BlockletSource.registry,
2222
+ * deployedFrom: string;
2223
+ * context: {}
2224
+ * sync: boolean = false;
2225
+ * }} params
2226
+ * @return {*}
2227
+ * @memberof BlockletManager
2228
+ */
2229
+ async _install(params) {
2230
+ const { meta, source, deployedFrom, context, sync } = params;
2231
+
2136
2232
  validateBlockletMeta(meta, { ensureDist: true });
2137
2233
 
2138
2234
  const { name, did, version } = meta;
@@ -2141,6 +2237,7 @@ class BlockletManager extends BaseBlockletManager {
2141
2237
 
2142
2238
  const children = await this._getChildrenForInstallation(meta);
2143
2239
  try {
2240
+ // 创建一个blocklet到database
2144
2241
  const blocklet = await states.blocklet.addBlocklet({
2145
2242
  meta,
2146
2243
  source,
@@ -2157,7 +2254,15 @@ class BlockletManager extends BaseBlockletManager {
2157
2254
  const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
2158
2255
  this.emit(BlockletEvents.added, blocklet1);
2159
2256
 
2160
- // download
2257
+ /** @type {{
2258
+ * blocklet: any;
2259
+ * oldBlocklet: {
2260
+ * children: any[];
2261
+ * extraState: any;
2262
+ * };
2263
+ * context: {};
2264
+ * postAction: string
2265
+ * }} */
2161
2266
  const downloadParams = {
2162
2267
  blocklet: { ...blocklet1 },
2163
2268
  oldBlocklet: {
@@ -2220,6 +2325,13 @@ class BlockletManager extends BaseBlockletManager {
2220
2325
  }
2221
2326
  }
2222
2327
 
2328
+ /**
2329
+ *
2330
+ *
2331
+ * @param {{}} { meta, source, deployedFrom, context, sync }
2332
+ * @return {*}
2333
+ * @memberof BlockletManager
2334
+ */
2223
2335
  async _upgrade({ meta, source, deployedFrom, context, sync }) {
2224
2336
  validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
2225
2337
 
@@ -2242,6 +2354,26 @@ class BlockletManager extends BaseBlockletManager {
2242
2354
 
2243
2355
  this.emit(BlockletEvents.statusChange, newBlocklet);
2244
2356
 
2357
+ // 如果是一个付费的blocklet,需要携带token才能下载成功
2358
+ if (!isFreeBlocklet(meta)) {
2359
+ const blocklet = await states.blocklet.getBlocklet(did);
2360
+
2361
+ const info = await states.node.read();
2362
+
2363
+ // eslint-disable-next-line no-param-reassign
2364
+ context = {
2365
+ ...context,
2366
+ headers: {
2367
+ 'x-server-did': info.did,
2368
+ 'x-download-token': blocklet?.tokens?.paidBlockletDownloadToken,
2369
+ 'x-server-publick-key': info.pk,
2370
+ 'x-server-signature': sign(info.did, info.sk, {
2371
+ exp: (Date.now() + 5 * 60 * 1000) / 1000,
2372
+ }),
2373
+ },
2374
+ };
2375
+ }
2376
+
2245
2377
  // download
2246
2378
  const downloadParams = {
2247
2379
  oldBlocklet: { ...oldBlocklet },
@@ -2255,7 +2387,6 @@ class BlockletManager extends BaseBlockletManager {
2255
2387
  await this.onDownload({ ...downloadParams, throwOnError: true });
2256
2388
  return states.blocklet.getBlocklet(did);
2257
2389
  }
2258
-
2259
2390
  const ticket = this.installQueue.push(
2260
2391
  {
2261
2392
  entity: 'blocklet',
@@ -2469,6 +2600,14 @@ class BlockletManager extends BaseBlockletManager {
2469
2600
  // Update dynamic component meta in blocklet settings
2470
2601
  await this._ensureDynamicChildrenInSettings(blocklet);
2471
2602
 
2603
+ if (context?.headers?.['x-download-token']) {
2604
+ await states.blocklet.updateBlocklet(did, {
2605
+ tokens: {
2606
+ paidBlockletDownloadToken: context.headers['x-download-token'],
2607
+ },
2608
+ });
2609
+ }
2610
+
2472
2611
  states.notification.create({
2473
2612
  title: 'Blocklet Installed',
2474
2613
  description: `Blocklet ${meta.name}@${meta.version} is installed successfully. (Source: ${
@@ -2657,11 +2796,23 @@ class BlockletManager extends BaseBlockletManager {
2657
2796
  }
2658
2797
 
2659
2798
  /**
2660
- * for download: cwd, tarball, did
2661
- * for verify: verify, integrity
2662
- * for cancel control: ctrlStore, rootDid
2799
+ *
2800
+ *
2801
+ * @param {{
2802
+ * url: string,
2803
+ * cwd: string,
2804
+ * tarball: string,
2805
+ * did: string,
2806
+ * integrity: string,
2807
+ * verify: boolean = true,
2808
+ * ctrlStore: {},
2809
+ * rootDid: string,
2810
+ * context: {} = {},
2811
+ * }} { url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid, context = {} }
2812
+ * @return {*}
2813
+ * @memberof BlockletManager
2663
2814
  */
2664
- async _downloadTarball({ url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid }) {
2815
+ async _downloadTarball({ url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid, context = {} }) {
2665
2816
  fs.mkdirSync(cwd, { recursive: true });
2666
2817
 
2667
2818
  const tarballName = url.split('/').slice(-1)[0];
@@ -2673,7 +2824,7 @@ class BlockletManager extends BaseBlockletManager {
2673
2824
  const cachedTarFile = await this._getCachedTarFile(integrity);
2674
2825
  if (cachedTarFile) {
2675
2826
  logger.info('found cache tarFile', { did, tarballName, integrity });
2676
- await fs.move(cachedTarFile, tarballPath);
2827
+ await fs.move(cachedTarFile, tarballPath, { overwrite: true });
2677
2828
  } else if (protocol.startsWith('file')) {
2678
2829
  await fs.copy(decodeURIComponent(pathname), tarballPath);
2679
2830
  } else {
@@ -2684,7 +2835,7 @@ class BlockletManager extends BaseBlockletManager {
2684
2835
  }
2685
2836
  ctrlStore[rootDid].set(did, cancelCtrl);
2686
2837
 
2687
- await downloadFile(url, path.join(cwd, tarballName), { cancelCtrl });
2838
+ await downloadFile(url, path.join(cwd, tarballName), { cancelCtrl }, context);
2688
2839
 
2689
2840
  if (ctrlStore[rootDid]) {
2690
2841
  ctrlStore[rootDid].delete(did);
@@ -2773,12 +2924,16 @@ class BlockletManager extends BaseBlockletManager {
2773
2924
  }
2774
2925
 
2775
2926
  /**
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 }
2927
+ *
2928
+ *
2929
+ * @param {*} meta
2930
+ * @param {*} rootDid
2931
+ * @param {*} url
2932
+ * @param {{}} [context={}]
2933
+ * @return {*}
2934
+ * @memberof BlockletManager
2780
2935
  */
2781
- async _downloadBundle(meta, rootDid, url) {
2936
+ async _downloadBundle(meta, rootDid, url, context = {}) {
2782
2937
  const { bundleName: name, bundleDid: did, version, dist = {} } = meta;
2783
2938
  const { tarball, integrity } = dist;
2784
2939
 
@@ -2806,6 +2961,7 @@ class BlockletManager extends BaseBlockletManager {
2806
2961
  ctrlStore: this.downloadCtrls,
2807
2962
  rootDid,
2808
2963
  url,
2964
+ context,
2809
2965
  });
2810
2966
  logger.info('downloaded blocklet tar file', { name, version, tarballPath });
2811
2967
  if (tarballPath === downloadFile.CANCEL) {
@@ -2826,7 +2982,16 @@ class BlockletManager extends BaseBlockletManager {
2826
2982
  }
2827
2983
  }
2828
2984
 
2829
- async _downloadBlocklet(blocklet, oldBlocklet = {}) {
2985
+ /**
2986
+ *
2987
+ *
2988
+ * @param {{}} blocklet
2989
+ * @param {{}} [oldBlocklet={}]
2990
+ * @param {{}} [context={}]
2991
+ * @return {*}
2992
+ * @memberof BlockletManager
2993
+ */
2994
+ async _downloadBlocklet(blocklet, oldBlocklet = {}, context = {}) {
2830
2995
  const {
2831
2996
  meta: { name, did },
2832
2997
  } = blocklet;
@@ -2880,7 +3045,7 @@ class BlockletManager extends BaseBlockletManager {
2880
3045
  tarball: get(meta, 'dist.tarball'),
2881
3046
  registryUrl: blocklet.source === BlockletSource.registry ? blocklet.deployedFrom : undefined,
2882
3047
  });
2883
- tasks.push(this._downloadBundle(meta, did, url));
3048
+ tasks.push(this._downloadBundle(meta, did, url, context));
2884
3049
  }
2885
3050
  const results = await Promise.all(tasks);
2886
3051
  if (results.find((x) => x.isCancelled)) {
@@ -2976,6 +3141,9 @@ class BlockletManager extends BaseBlockletManager {
2976
3141
  const logsDir = path.join(this.dataDirs.logs, name);
2977
3142
  const cacheDir = path.join(this.dataDirs.cache, name);
2978
3143
 
3144
+ // Cleanup db
3145
+ await this.teamManager.deleteTeam(blocklet.meta.did);
3146
+
2979
3147
  // Cleanup disk storage
2980
3148
  fs.removeSync(cacheDir);
2981
3149
  if (keepData === false) {
@@ -3068,6 +3236,78 @@ class BlockletManager extends BaseBlockletManager {
3068
3236
 
3069
3237
  return blocklet;
3070
3238
  }
3239
+
3240
+ async _getLatestBlockletVersionFromStore({ blocklet, version }) {
3241
+ const { did, bundleDid } = blocklet.meta;
3242
+
3243
+ let versions = this.cachedBlockletVersions.get(did);
3244
+
3245
+ if (!versions) {
3246
+ const { blockletRegistryList } = await states.node.read();
3247
+ const tasks = blockletRegistryList.map((registry) =>
3248
+ this.registry
3249
+ .getBlockletMeta({ did: bundleDid, registryUrl: registry.url })
3250
+ .then((item) => ({ did, version: item.version, registryUrl: registry.url }))
3251
+ .catch((error) => {
3252
+ if (error.response && error.response.status === 404) {
3253
+ return;
3254
+ }
3255
+ logger.error('get blocklet meta from registry failed', { did, error });
3256
+ })); // prettier-ignore
3257
+
3258
+ versions = await Promise.all(tasks);
3259
+ this.cachedBlockletVersions.set(did, versions);
3260
+ }
3261
+
3262
+ versions = versions.filter((item) => item && semver.gt(item.version, version));
3263
+
3264
+ if (versions.length === 0) {
3265
+ return null;
3266
+ }
3267
+
3268
+ // When new version found from the store where the blocklet was installed from, we should use that store first
3269
+ if (blocklet.source === BlockletSource.registry && blocklet.deployedFrom) {
3270
+ const latestFromSameRegistry = versions.find((x) => x.registryUrl === blocklet.deployedFrom);
3271
+ if (latestFromSameRegistry) {
3272
+ return latestFromSameRegistry;
3273
+ }
3274
+ }
3275
+
3276
+ // Otherwise try upgrading from other store
3277
+ let latestBlockletVersion = versions[0];
3278
+ versions.forEach((item) => {
3279
+ if (semver.lt(latestBlockletVersion.version, item.version)) {
3280
+ latestBlockletVersion = item;
3281
+ }
3282
+ });
3283
+
3284
+ return latestBlockletVersion;
3285
+ }
3286
+
3287
+ async _getLatestBlockletVersionFromUrl({ blocklet, version }) {
3288
+ const { did } = blocklet.meta;
3289
+
3290
+ let versions = this.cachedBlockletVersions.get(did);
3291
+
3292
+ if (!versions) {
3293
+ try {
3294
+ const item = await getBlockletMetaFromUrl(blocklet.deployedFrom);
3295
+ versions = [{ did, version: item.version }];
3296
+ } catch (error) {
3297
+ logger.error('get blocklet meta from url failed when checking latest version', { did, error });
3298
+ versions = [];
3299
+ }
3300
+ }
3301
+
3302
+ this.cachedBlockletVersions.set(did, versions);
3303
+ versions = versions.filter((item) => item && semver.gt(item.version, version));
3304
+
3305
+ if (versions.length === 0) {
3306
+ return null;
3307
+ }
3308
+
3309
+ return versions[0];
3310
+ }
3071
3311
  }
3072
3312
 
3073
3313
  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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.7.25",
6
+ "version": "1.8.0",
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.25",
23
- "@abtnode/constant": "1.7.25",
24
- "@abtnode/cron": "1.7.25",
25
- "@abtnode/db": "1.7.25",
26
- "@abtnode/logger": "1.7.25",
27
- "@abtnode/queue": "1.7.25",
28
- "@abtnode/rbac": "1.7.25",
29
- "@abtnode/router-provider": "1.7.25",
30
- "@abtnode/static-server": "1.7.25",
31
- "@abtnode/timemachine": "1.7.25",
32
- "@abtnode/util": "1.7.25",
33
- "@arcblock/did": "^1.16.15",
22
+ "@abtnode/certificate-manager": "1.8.0",
23
+ "@abtnode/constant": "1.8.0",
24
+ "@abtnode/cron": "1.8.0",
25
+ "@abtnode/db": "1.8.0",
26
+ "@abtnode/logger": "1.8.0",
27
+ "@abtnode/queue": "1.8.0",
28
+ "@abtnode/rbac": "1.8.0",
29
+ "@abtnode/router-provider": "1.8.0",
30
+ "@abtnode/static-server": "1.8.0",
31
+ "@abtnode/timemachine": "1.8.0",
32
+ "@abtnode/util": "1.8.0",
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.25",
39
+ "@arcblock/vc": "1.17.0",
40
+ "@blocklet/meta": "1.8.0",
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": "a500728739278c8a9464cf631a811ddfc4838fad"
84
+ "gitHead": "6446a85fb33721abc24bb1045d59158e6b96c241"
84
85
  }