@abtnode/core 1.8.61 → 1.8.63-beta-c51e554d

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
@@ -595,7 +595,13 @@ class TeamAPI extends EventEmitter {
595
595
  async getPassportIssuances({ teamDid, ownerDid }) {
596
596
  const state = await this.getSessionState(teamDid);
597
597
 
598
- const list = await state.find({ type: 'passport-issuance', ownerDid });
598
+ const query = { type: 'passport-issuance' };
599
+
600
+ if (ownerDid) {
601
+ query.ownerDid = ownerDid;
602
+ }
603
+
604
+ const list = await state.find(query);
599
605
 
600
606
  return list.map((d) => ({
601
607
  // eslint-disable-next-line no-underscore-dangle
@@ -146,7 +146,22 @@ const parseConfigs = ({ data, did, dek }) => {
146
146
  return data;
147
147
  };
148
148
 
149
+ const encryptConfigs = ({ data, did, dek }) => {
150
+ const enableSecurity = dek && did;
151
+
152
+ if (enableSecurity && Array.isArray(data)) {
153
+ data.forEach((x) => {
154
+ if (x.secure) {
155
+ x.value = security.encrypt(x.value, did, dek);
156
+ }
157
+ });
158
+ }
159
+
160
+ return data;
161
+ };
162
+
149
163
  module.exports = {
150
164
  mergeConfigs,
151
165
  parseConfigs,
166
+ encryptConfigs,
152
167
  };
@@ -31,6 +31,7 @@ const {
31
31
  WHO_CAN_ACCESS,
32
32
  SERVER_ROLES,
33
33
  WHO_CAN_ACCESS_PREFIX_ROLES,
34
+ BLOCKLET_INSTALL_TYPE,
34
35
  } = require('@abtnode/constant');
35
36
 
36
37
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
@@ -104,7 +105,7 @@ const {
104
105
  getDiskInfo,
105
106
  getUpdateMetaList,
106
107
  getRuntimeEnvironments,
107
- getSourceFromInstallParams,
108
+ getTypeFromInstallParams,
108
109
  parseChildrenFromMeta,
109
110
  checkDuplicateComponents,
110
111
  getDiffFiles,
@@ -118,6 +119,7 @@ const {
118
119
  consumeServerlessNFT,
119
120
  validateAppConfig,
120
121
  checkDuplicateAppSk,
122
+ checkDuplicateMountPoint,
121
123
  } = require('../../util/blocklet');
122
124
  const StoreUtil = require('../../util/store');
123
125
  const states = require('../../states');
@@ -132,6 +134,7 @@ const handleInstanceInStore = require('../../util/public-to-store');
132
134
  const { getNFTState, getServerDidDomain } = require('../../util');
133
135
  const { BlockletRuntimeMonitor } = require('../../monitor/blocklet-runtime-monitor');
134
136
  const getHistoryList = require('../../monitor/get-history-list');
137
+ const installFromBackup = require('./helper/install-from-backup');
135
138
 
136
139
  const {
137
140
  isInProgress,
@@ -252,6 +255,7 @@ class BlockletManager extends BaseBlockletManager {
252
255
  * downloadTokenList: Array<{did: string, token: string}>;
253
256
  * startImmediately: boolean;
254
257
  * controller: Controller
258
+ * type: BLOCKLET_INSTALL_TYPE
255
259
  * }} params
256
260
  * @param {{
257
261
  * [key: string]: any
@@ -273,32 +277,37 @@ class BlockletManager extends BaseBlockletManager {
273
277
  });
274
278
  context.downloadTokenList = params.downloadTokenList || [];
275
279
 
276
- const source = getSourceFromInstallParams(params);
280
+ const type = getTypeFromInstallParams(params);
277
281
  if (typeof context.startImmediately === 'undefined') {
278
282
  context.startImmediately = !!params.startImmediately;
279
283
  }
280
284
 
281
- if (source === BlockletSource.url) {
285
+ if (type === BLOCKLET_INSTALL_TYPE.URL) {
282
286
  const { url, controller, sync, delay } = params;
283
287
  return this._installFromUrl({ url, controller, sync, delay }, context);
284
288
  }
285
289
 
286
- if (source === BlockletSource.upload) {
290
+ if (type === BLOCKLET_INSTALL_TYPE.UPLOAD) {
287
291
  const { file, did, diffVersion, deleteSet } = params;
288
292
  return this._installFromUpload({ file, did, diffVersion, deleteSet, context });
289
293
  }
290
294
 
291
- if (source === BlockletSource.registry) {
295
+ if (type === BLOCKLET_INSTALL_TYPE.STORE) {
292
296
  const { did, controller, sync, delay, storeUrl } = params;
293
297
  return this._installFromStore({ did, controller, sync, delay, storeUrl }, context);
294
298
  }
295
299
 
296
- if (source === BlockletSource.custom) {
300
+ if (type === BLOCKLET_INSTALL_TYPE.CREATE) {
297
301
  return this._installFromCreate({ title: params.title, description: params.description }, context);
298
302
  }
299
303
 
304
+ if (type === BLOCKLET_INSTALL_TYPE.RESTORE) {
305
+ const { url, blockletSecretKey } = params;
306
+ return this._installFromBackup({ url, blockletSecretKey }, context);
307
+ }
308
+
300
309
  // should not be here
301
- throw new Error('Unknown source');
310
+ throw new Error('Unknown type');
302
311
  }
303
312
 
304
313
  /**
@@ -1144,20 +1153,26 @@ class BlockletManager extends BaseBlockletManager {
1144
1153
  }
1145
1154
 
1146
1155
  const rootDid = blocklet.meta.did;
1156
+ const isRootComponent = !did;
1147
1157
 
1148
- const { children } = blocklet;
1149
- const component = children.find((x) => x.meta.did === did);
1150
-
1158
+ const component = isRootComponent ? blocklet : blocklet.children.find((x) => x.meta.did === did);
1151
1159
  if (!component) {
1152
1160
  throw new Error('component does not exist');
1153
1161
  }
1154
1162
 
1155
- if (!component.dynamic) {
1163
+ if (!isRootComponent && !component.dynamic) {
1156
1164
  throw new Error('cannot update mountPoint of non-dynamic component');
1157
1165
  }
1158
1166
 
1167
+ if (isRootComponent && component.group === BlockletGroup.gateway) {
1168
+ throw new Error('cannot update mountPoint of gateway blocklet');
1169
+ }
1170
+
1171
+ checkDuplicateMountPoint(blocklet, mountPoint);
1172
+
1159
1173
  component.mountPoint = mountPoint;
1160
- await states.blocklet.updateBlocklet(rootDid, { children });
1174
+
1175
+ await states.blocklet.updateBlocklet(rootDid, { mountPoint: blocklet.mountPoint, children: blocklet.children });
1161
1176
 
1162
1177
  this.emit(BlockletEvents.upgraded, { blocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
1163
1178
 
@@ -1594,6 +1609,19 @@ class BlockletManager extends BaseBlockletManager {
1594
1609
  return blocklet;
1595
1610
  }
1596
1611
 
1612
+ /**
1613
+ * backup 目录结构
1614
+ * /blocklets/<name1>version>
1615
+ * /blocklets/<name2>version>
1616
+ * /blocklets/<name3>version>
1617
+ * /data
1618
+ * /blocklet.json
1619
+ * /blocklet_extras.json
1620
+ */
1621
+ async _installFromBackup({ url, blockletSecretKey, moveDir } = {}, context = {}) {
1622
+ return installFromBackup({ url, blockletSecretKey, moveDir, context, manager: this, states });
1623
+ }
1624
+
1597
1625
  async ensureBlocklet(did, opts = {}) {
1598
1626
  return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did });
1599
1627
  }
@@ -1698,10 +1726,10 @@ class BlockletManager extends BaseBlockletManager {
1698
1726
  }));
1699
1727
  }
1700
1728
 
1701
- if (!fromCache) {
1702
- // app runtime info, app status
1703
- blocklet.appRuntimeInfo = this.runtimeMonitor.getRuntimeInfo(blocklet.meta.did);
1729
+ // app runtime info, app status
1730
+ blocklet.appRuntimeInfo = this.runtimeMonitor.getRuntimeInfo(blocklet.meta.did);
1704
1731
 
1732
+ if (!fromCache) {
1705
1733
  // app disk info, component runtime info, component status, component engine
1706
1734
  await forEachBlocklet(blocklet, async (component, { level }) => {
1707
1735
  component.engine = getEngine(getBlockletEngineNameByPlatform(component.meta)).describe();
@@ -0,0 +1,160 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const omit = require('lodash/omit');
4
+
5
+ const { forEachBlockletSync } = require('@blocklet/meta/lib/util');
6
+ const getBlockletInfo = require('@blocklet/meta/lib/info');
7
+
8
+ const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
9
+
10
+ const logger = require('@abtnode/logger')('@abtnode/core:install-from-backup');
11
+
12
+ const { validateBlocklet, checkDuplicateAppSk, getAppDirs } = require('../../../util/blocklet');
13
+
14
+ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states, manager } = {}) => {
15
+ // TODO: support more url schema feature (http, did-spaces)
16
+ if (!url.startsWith('file://')) {
17
+ throw new Error('url must starts with file://');
18
+ }
19
+
20
+ const dir = url.replace('file://', '');
21
+
22
+ if (!dir || !fs.existsSync(dir)) {
23
+ throw new Error(`dir(${dir}) does not exist`);
24
+ }
25
+
26
+ // parse data from source dir
27
+
28
+ const srcBundleDirs = await getAppDirs(path.join(dir, 'blocklets'));
29
+ if (!srcBundleDirs.length) {
30
+ throw new Error(`bundle dirs does not found in ${dir}`);
31
+ }
32
+
33
+ const srcDataDir = path.join(dir, 'data');
34
+
35
+ /** @type {import('@abtnode/client').BlockletState} */
36
+ const state = omit(fs.readJSONSync(path.join(dir, 'blocklet.json')), [
37
+ '_id',
38
+ 'createdAt',
39
+ 'updatedAt',
40
+ 'installedAt',
41
+ 'startedAt',
42
+ ]);
43
+
44
+ const extra = omit(fs.readJSONSync(path.join(dir, 'blocklet-extras.json')), ['_id', 'createdAt', 'updatedAt']);
45
+
46
+ if (state.meta.did !== extra.did) {
47
+ throw new Error('did does not match in blocklet.json and blocklet_extra.json');
48
+ }
49
+
50
+ forEachBlockletSync(state, (component) => {
51
+ delete component.status;
52
+ delete component.ports;
53
+ delete component.environments;
54
+ });
55
+
56
+ const { meta } = state;
57
+
58
+ await validateBlocklet({ meta });
59
+
60
+ const { did, name: appName } = meta;
61
+
62
+ // FIXME: meta.did and meta.name should be dynamic created when installing multiple blocklet is supported
63
+ const existState = await states.blocklet.hasBlocklet(did);
64
+ if (existState) {
65
+ logger.error('blocklet is already exist', { did });
66
+ throw new Error('blocklet is already exist');
67
+ }
68
+
69
+ if (blockletSecretKey) {
70
+ extra.configs = extra.configs || [];
71
+ const skConfig = extra.configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
72
+ if (skConfig) {
73
+ skConfig.value = blockletSecretKey;
74
+ } else {
75
+ extra.configs.push({
76
+ key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK,
77
+ value: blockletSecretKey,
78
+ secure: true,
79
+ shared: false,
80
+ });
81
+ }
82
+ }
83
+
84
+ // validate appDid
85
+ const nodeInfo = await states.node.read();
86
+ const { wallet } = getBlockletInfo({ meta: state.meta, configs: extra.configs, environments: [] }, nodeInfo.sk);
87
+ if (state.appDid !== wallet.address) {
88
+ throw new Error('blocklet appDid is different from the previous one');
89
+ }
90
+ await checkDuplicateAppSk({ sk: wallet.secretKey, states });
91
+
92
+ states.blockletExtras.encryptSecurityData({ data: extra, rootDid: extra.did });
93
+
94
+ logger.info('installFromBackup', { srcBundleDirs, srcDataDir });
95
+
96
+ try {
97
+ // copy extra
98
+ const existExtra = await states.blockletExtras.find({ did });
99
+ if (existExtra) {
100
+ // 如果数据存在, 当前视为脏数据, 直接删除
101
+ // FIXME 另一个人的数据可能被删除. 修复方式: 动态生成 meta.did 或通过 blocklet sk 生成 meta.did
102
+ // FIXME 简单粗暴的删掉数据可能不是理想的方式,需要考虑旧数据存在时, 如何与新数据 merge
103
+ logger.error('old extra state exists and will be force removed', { existExtra });
104
+ await states.blockletExtras.remove({ did });
105
+ }
106
+ await states.blockletExtras.insert(extra);
107
+ logger.info('blocklet extra is copied successfully');
108
+
109
+ // add blocklet
110
+ await states.blocklet.addBlocklet(state);
111
+ logger.info('blocklet state is added successfully');
112
+
113
+ // copy bundle
114
+ // 假设相同名称的应用,肯定是同一个应用
115
+ // 假设版本号相同时, 应用不会变更
116
+ // FIXME: blocklet bundle name/did 不是唯一的. 修改 blocklet bundle name/did 生成方式 使 name/bundle 唯一
117
+ await Promise.all(
118
+ srcBundleDirs.map(async ({ key: bundleName, dir: srcDir }) => {
119
+ const installDir = path.join(manager.dataDirs.blocklets, bundleName);
120
+ if (fs.existsSync(installDir)) {
121
+ logger.info(`${bundleName} is already exist`);
122
+ return;
123
+ }
124
+
125
+ fs.mkdirSync(installDir, { recursive: true });
126
+ if (moveDir) {
127
+ await fs.move(srcDir, installDir, { overwrite: true });
128
+ } else {
129
+ await fs.copy(srcDir, installDir);
130
+ }
131
+ logger.info(`bundle is ${moveDir ? 'moved' : 'copied'} successfully`, { installDir });
132
+ })
133
+ );
134
+
135
+ // FIXME same as copy extra
136
+ const dataDir = path.join(manager.dataDirs.data, appName);
137
+ if (fs.existsSync(dataDir)) {
138
+ logger.error('old data exists and will be force removed', { dataDir });
139
+ await fs.remove(dataDir);
140
+ }
141
+ fs.mkdirSync(dataDir, { recursive: true });
142
+ if (fs.existsSync(srcDataDir)) {
143
+ if (moveDir) {
144
+ await fs.move(srcDataDir, dataDir, { overwrite: true });
145
+ } else {
146
+ await fs.copy(srcDataDir, dataDir);
147
+ }
148
+ logger.info(`data is ${moveDir ? 'moved' : 'copied'} successfully`);
149
+ }
150
+ } catch (error) {
151
+ logger.error('installFromBackup failed', { error });
152
+
153
+ await manager._rollback('install', did);
154
+
155
+ throw error;
156
+ }
157
+
158
+ logger.info('start install blocklet', { did });
159
+ return manager._installBlocklet({ did, context });
160
+ };
@@ -258,13 +258,19 @@ const ensureWellknownRule = async (sites) => {
258
258
  if (blockletRules.length) {
259
259
  // get pathPrefix for blocklet-service
260
260
  const rootBlockletRule = blockletRules.find((x) => x.to.did === x.to.componentId);
261
- const pathPrefix = joinUrl(rootBlockletRule?.from?.pathPrefix || '/', WELLKNOWN_SERVICE_PATH_PREFIX);
261
+
262
+ const servicePathPrefix = joinUrl(
263
+ // rootBlockletRule?.from?.pathPrefix is for backwards compatibility
264
+ // rootBlockletRule?.from?.groupPathPrefix 在旧的场景中可能不为 '/' (blocklet 只能挂载在 relative prefix 下)
265
+ rootBlockletRule?.from?.groupPathPrefix || rootBlockletRule?.from?.pathPrefix || '/',
266
+ WELLKNOWN_SERVICE_PATH_PREFIX
267
+ );
262
268
 
263
269
  // requests for /.well-known/service will stay in blocklet-service and never proxy back to blocklet
264
270
  // so any rule is ok to be cloned
265
- if (!site.rules.some((x) => x.from.pathPrefix === pathPrefix)) {
271
+ if (!site.rules.some((x) => x.from.pathPrefix === servicePathPrefix)) {
266
272
  const rule = cloneDeep(rootBlockletRule || blockletRules[0]);
267
- rule.from.pathPrefix = pathPrefix;
273
+ rule.from.pathPrefix = servicePathPrefix;
268
274
  rule.isProtected = true;
269
275
  site.rules.push(rule);
270
276
  }
@@ -275,6 +281,25 @@ const ensureWellknownRule = async (sites) => {
275
281
  return tempSites;
276
282
  };
277
283
 
284
+ const ensureBlockletDid = async (sites) => {
285
+ const info = await states.node.read();
286
+
287
+ return (sites || []).map((site) => {
288
+ if (site.domain === DOMAIN_FOR_INTERNAL_SITE) {
289
+ return site;
290
+ }
291
+
292
+ if ([DOMAIN_FOR_IP_SITE, DOMAIN_FOR_DEFAULT_SITE, DOMAIN_FOR_IP_SITE_REGEXP].includes(site.domain)) {
293
+ site.blockletDid = info.did;
294
+ return site;
295
+ }
296
+
297
+ site.blockletDid = site.domain.replace(BLOCKLET_SITE_GROUP_SUFFIX, '');
298
+
299
+ return site;
300
+ });
301
+ };
302
+
278
303
  const ensureCorsForWebWallet = async (sites) => {
279
304
  const info = await states.node.read();
280
305
  for (const site of sites) {
@@ -300,8 +325,10 @@ const filterSitesForRemovedBlocklets = async (sites = []) => {
300
325
 
301
326
  const ensureLatestInfo = async (sites = [], { withDefaultCors = true } = {}) => {
302
327
  let result = await ensureLatestNodeInfo(sites, { withDefaultCors });
328
+ result = await ensureBlockletDid(result);
303
329
  result = await ensureWellknownRule(result);
304
330
  result = await ensureCorsForWebWallet(result);
331
+
305
332
  return ensureLatestInterfaceInfo(result);
306
333
  };
307
334
 
@@ -11,6 +11,7 @@ const get = require('lodash/get');
11
11
  const { EventEmitter } = require('events');
12
12
  const uuid = require('uuid');
13
13
  const isUrl = require('is-url');
14
+ const joinUrl = require('url-join');
14
15
  const cloneDeep = require('lodash/cloneDeep');
15
16
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
16
17
  const logger = require('@abtnode/logger')('@abtnode/core:router:manager');
@@ -570,7 +571,8 @@ class RouterManager extends EventEmitter {
570
571
  rule.groupId = rule.id;
571
572
  rule.from.pathPrefix = normalizePathPrefix(rule.from.pathPrefix);
572
573
  if (rule.to.type === ROUTING_RULE_TYPES.BLOCKLET) {
573
- rule.from.groupPathPrefix = rule.from.pathPrefix;
574
+ // pathPrefix of root blocklet maybe changed to another prefix than '/', so use old groupPathPrefix first
575
+ rule.from.groupPathPrefix = rule.from.groupPathPrefix || rule.from.pathPrefix;
574
576
  rule.to.componentId = rule.to.did;
575
577
  }
576
578
  if (rule.to.url) {
@@ -580,18 +582,32 @@ class RouterManager extends EventEmitter {
580
582
 
581
583
  /**
582
584
  * get all rules to be add or update to site from root rule
583
- * @param {*} rule
585
+ * @param {*} rootRule
584
586
  */
585
- async getRulesForMutation(rule) {
586
- if (rule.to.type !== ROUTING_RULE_TYPES.BLOCKLET) {
587
- return [rule];
587
+ async getRulesForMutation(rootRule) {
588
+ if (rootRule.to.type !== ROUTING_RULE_TYPES.BLOCKLET) {
589
+ return [rootRule];
588
590
  }
589
591
 
590
592
  const rules = [];
591
- let occupied = false;
592
593
 
593
594
  // get child rules
594
- const blocklet = await states.blocklet.getBlocklet(rule.to.did);
595
+ const blocklet = await states.blocklet.getBlocklet(rootRule.to.did);
596
+
597
+ // blocklet may be mounted in relative prefix (for old usage), so blockletPrefix may not be '/'
598
+ // blocklet prefix is the origin pathPrefix in rootRule
599
+ const blockletPrefix = normalizePathPrefix(rootRule.from.pathPrefix);
600
+
601
+ // root component's mountPoint may not be '/'
602
+ const rootComponentPrefix = joinUrl(blockletPrefix, blocklet.mountPoint || '/');
603
+ rootRule.from.pathPrefix = normalizePathPrefix(rootComponentPrefix);
604
+
605
+ const isOccupiable = blocklet.meta.group === BlockletGroup.gateway;
606
+
607
+ if (!isOccupiable) {
608
+ rules.push(rootRule);
609
+ }
610
+
595
611
  forEachChildSync(blocklet, (component, { id, ancestors }) => {
596
612
  if (component.meta.group === BlockletGroup.gateway) {
597
613
  return;
@@ -611,38 +627,40 @@ class RouterManager extends EventEmitter {
611
627
  return;
612
628
  }
613
629
 
614
- const pathPrefix = path.join(rule.from.pathPrefix, ...ancestors.map((x) => x.mountPoint || ''), mountPoint);
615
- const isRootPath = normalizePathPrefix(pathPrefix) === normalizePathPrefix(rule.from.pathPrefix);
616
- if (isRootPath) {
617
- occupied = true;
630
+ const pathPrefix = path.join(
631
+ blockletPrefix,
632
+ // level 1 component should be independent with root component
633
+ ...ancestors.slice(1).map((x) => x.mountPoint || ''),
634
+ mountPoint
635
+ );
636
+
637
+ const occupied = normalizePathPrefix(pathPrefix) === normalizePathPrefix(rootRule.from.pathPrefix);
638
+
639
+ if (occupied && !isOccupiable) {
640
+ return;
618
641
  }
619
642
 
620
643
  // if is root path, child rule become root rule
621
644
  const childRule = {
622
- id: isRootPath ? rule.id : uuid.v4(),
623
- groupId: rule.id,
645
+ id: occupied ? rootRule.id : uuid.v4(),
646
+ groupId: rootRule.id,
624
647
  from: {
625
648
  pathPrefix: normalizePathPrefix(pathPrefix),
626
- groupPathPrefix: rule.from.pathPrefix,
649
+ groupPathPrefix: blockletPrefix,
627
650
  },
628
651
  to: {
629
652
  type: ROUTING_RULE_TYPES.BLOCKLET,
630
653
  port: findInterfacePortByName(component, childWebInterface.name),
631
- did: rule.to.did, // root component did
632
- interfaceName: rule.to.interfaceName, // root component interface
654
+ did: rootRule.to.did, // root component did
655
+ interfaceName: rootRule.to.interfaceName, // root component interface
633
656
  componentId: id,
634
657
  },
635
- isProtected: isRootPath ? rule.isProtected : true,
658
+ isProtected: occupied ? rootRule.isProtected : true,
636
659
  };
637
660
 
638
661
  rules.push(childRule);
639
662
  });
640
663
 
641
- // get root rule
642
- if (!occupied && blocklet.meta.group !== BlockletGroup.gateway) {
643
- rules.push(rule);
644
- }
645
-
646
664
  return rules;
647
665
  }
648
666
  }
@@ -9,7 +9,7 @@ const dayjs = require('dayjs');
9
9
 
10
10
  const BaseState = require('./base');
11
11
 
12
- const { mergeConfigs, parseConfigs } = require('../blocklet/extras');
12
+ const { mergeConfigs, parseConfigs, encryptConfigs } = require('../blocklet/extras');
13
13
  const { validateAddMeta, validateExpiredInfo } = require('../validators/blocklet-extra');
14
14
 
15
15
  const noop = (k) => (v) => v[k];
@@ -214,6 +214,34 @@ class BlockletExtrasState extends BaseState {
214
214
  expiredAt: { $exists: true, $lte: now.subtract(EXPIRED_BLOCKLET_DATA_RETENTION_DAYS, 'days').toISOString() },
215
215
  });
216
216
  }
217
+
218
+ encryptSecurityData({ data, _rootDid } = {}) {
219
+ if (!data) {
220
+ return data;
221
+ }
222
+
223
+ const { dek } = this.config;
224
+
225
+ if (!dek) {
226
+ return data;
227
+ }
228
+
229
+ const did = _rootDid || data.did;
230
+
231
+ if (!did) {
232
+ throw new Error('data.did does not exist');
233
+ }
234
+
235
+ encryptConfigs({ data: data.configs, did, dek });
236
+
237
+ if (Array.isArray(data.children)) {
238
+ for (const child of data.children) {
239
+ this.encryptSecurityData({ data: child, _rootDid: did });
240
+ }
241
+ }
242
+
243
+ return data;
244
+ }
217
245
  }
218
246
 
219
247
  module.exports = BlockletExtrasState;
@@ -31,8 +31,7 @@ const getFolderSize = require('@abtnode/util/lib/get-folder-size');
31
31
  const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
32
32
  const hashFiles = require('@abtnode/util/lib/hash-files');
33
33
  const isPathPrefixEqual = require('@abtnode/util/lib/is-path-prefix-equal');
34
- const { BLOCKLET_MAX_MEM_LIMIT_IN_MB, BLOCKLET_STORE } = require('@abtnode/constant');
35
- const { BLOCKLET_PREFERENCE_FILE, BLOCKLET_PREFERENCE_PREFIX } = require('@blocklet/constant');
34
+ const { BLOCKLET_MAX_MEM_LIMIT_IN_MB, BLOCKLET_STORE, BLOCKLET_INSTALL_TYPE } = require('@abtnode/constant');
36
35
  const formatBackSlash = require('@abtnode/util/lib/format-back-slash');
37
36
 
38
37
  const SCRIPT_ENGINES_WHITE_LIST = ['npm', 'npx', 'pnpm', 'yarn'];
@@ -50,6 +49,8 @@ const {
50
49
  BLOCKLET_CONFIGURABLE_KEY,
51
50
  BLOCKLET_DYNAMIC_PATH_PREFIX,
52
51
  fromBlockletStatus,
52
+ BLOCKLET_PREFERENCE_FILE,
53
+ BLOCKLET_PREFERENCE_PREFIX,
53
54
  } = require('@blocklet/constant');
54
55
  const verifyMultiSig = require('@blocklet/meta/lib/verify-multi-sig');
55
56
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
@@ -926,6 +927,59 @@ const verifyIntegrity = async ({ file, integrity: expected }) => {
926
927
  return true;
927
928
  };
928
929
 
930
+ /**
931
+ * @param {string} installDir
932
+ * @returns {Array<{ key: <[scope/]name/version>, dir: appDir }>}
933
+ */
934
+ const getAppDirs = async (installDir) => {
935
+ const appDirs = [];
936
+
937
+ const getNextLevel = (level, name) => {
938
+ if (level === 'root') {
939
+ if (name.startsWith('@')) {
940
+ return 'scope';
941
+ }
942
+ return 'name';
943
+ }
944
+ if (level === 'scope') {
945
+ return 'name';
946
+ }
947
+ if (level === 'name') {
948
+ return 'version';
949
+ }
950
+ throw new Error(`Invalid level ${level}`);
951
+ };
952
+
953
+ const fillAppDirs = async (dir, level = 'root') => {
954
+ if (level === 'version') {
955
+ appDirs.push({
956
+ key: formatBackSlash(path.relative(installDir, dir)),
957
+ dir,
958
+ });
959
+
960
+ return;
961
+ }
962
+
963
+ const nextDirs = [];
964
+ for (const x of await fs.promises.readdir(dir)) {
965
+ if (!fs.lstatSync(path.join(dir, x)).isDirectory()) {
966
+ logger.error('pruneBlockletBundle: invalid file in bundle storage', { dir, file: x });
967
+ // eslint-disable-next-line no-continue
968
+ continue;
969
+ }
970
+ nextDirs.push(x);
971
+ }
972
+
973
+ for (const x of nextDirs) {
974
+ await fillAppDirs(path.join(dir, x), getNextLevel(level, x));
975
+ }
976
+ };
977
+
978
+ await fillAppDirs(installDir, 'root');
979
+
980
+ return appDirs;
981
+ };
982
+
929
983
  const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings }) => {
930
984
  for (const blocklet of blocklets) {
931
985
  if (
@@ -961,53 +1015,10 @@ const pruneBlockletBundle = async ({ blocklets, installDir, blockletSettings })
961
1015
  }
962
1016
  }
963
1017
 
964
- // appDirs: [{ key: <[scope/]name/version>, dir: appDir }]
965
- const appDirs = [];
966
-
967
1018
  // fill appDirs
1019
+ let appDirs = [];
968
1020
  try {
969
- // @return root/scope/bundle/version
970
- const getNextLevel = (level, name) => {
971
- if (level === 'root') {
972
- if (name.startsWith('@')) {
973
- return 'scope';
974
- }
975
- return 'bundle';
976
- }
977
- if (level === 'scope') {
978
- return 'bundle';
979
- }
980
- if (level === 'bundle') {
981
- return 'version';
982
- }
983
- throw new Error(`Invalid level ${level}`);
984
- };
985
-
986
- const fillAppDirs = async (dir, level = 'root') => {
987
- if (level === 'version') {
988
- appDirs.push({
989
- key: formatBackSlash(path.relative(installDir, dir)),
990
- dir,
991
- });
992
-
993
- return;
994
- }
995
-
996
- const nextDirs = [];
997
- for (const x of await fs.promises.readdir(dir)) {
998
- if (!fs.lstatSync(path.join(dir, x)).isDirectory()) {
999
- logger.error('pruneBlockletBundle: invalid file in bundle storage', { dir, file: x });
1000
- // eslint-disable-next-line no-continue
1001
- continue;
1002
- }
1003
- nextDirs.push(x);
1004
- }
1005
-
1006
- for (const x of nextDirs) {
1007
- await fillAppDirs(path.join(dir, x), getNextLevel(level, x));
1008
- }
1009
- };
1010
- await fillAppDirs(installDir, 'root');
1021
+ appDirs = await getAppDirs(installDir);
1011
1022
  } catch (error) {
1012
1023
  logger.error('fill app dirs failed', { error });
1013
1024
  }
@@ -1197,24 +1208,34 @@ const getUpdateMetaList = (oldBlocklet = {}, newBlocklet = {}) => {
1197
1208
  return res;
1198
1209
  };
1199
1210
 
1200
- const getSourceFromInstallParams = (params) => {
1211
+ /**
1212
+ * @returns BLOCKLET_INSTALL_TYPE
1213
+ */
1214
+ const getTypeFromInstallParams = (params) => {
1215
+ if (params.type) {
1216
+ if (!Object.values(BLOCKLET_INSTALL_TYPE).includes(params.type)) {
1217
+ throw new Error(`Can only install blocklet from ${Object.values(BLOCKLET_INSTALL_TYPE).join('/')}`);
1218
+ }
1219
+ return params.type;
1220
+ }
1221
+
1201
1222
  if (params.url) {
1202
- return BlockletSource.url;
1223
+ return BLOCKLET_INSTALL_TYPE.URL;
1203
1224
  }
1204
1225
 
1205
1226
  if (params.file) {
1206
- return BlockletSource.upload;
1227
+ return BLOCKLET_INSTALL_TYPE.UPLOAD;
1207
1228
  }
1208
1229
 
1209
1230
  if (params.did) {
1210
- return BlockletSource.registry;
1231
+ return BLOCKLET_INSTALL_TYPE.STORE;
1211
1232
  }
1212
1233
 
1213
1234
  if (params.title && params.description) {
1214
- return BlockletSource.custom;
1235
+ return BLOCKLET_INSTALL_TYPE.CREATE;
1215
1236
  }
1216
1237
 
1217
- throw new Error('Can only install blocklet from store/url/upload/custom');
1238
+ throw new Error(`Can only install blocklet from ${Object.values(BLOCKLET_INSTALL_TYPE).join('/')}`);
1218
1239
  };
1219
1240
 
1220
1241
  const checkDuplicateComponents = (components = []) => {
@@ -1599,25 +1620,32 @@ const validateAppConfig = async (config, blockletDid, states) => {
1599
1620
  }
1600
1621
  };
1601
1622
 
1602
- const checkDuplicateAppSk = async ({ did, states }) => {
1603
- const nodeInfo = await states.node.read();
1604
- const blocklets = await states.blocklet.getBlocklets({});
1605
- const blocklet = await states.blocklet.getBlocklet(did);
1606
- const configs = await states.blockletExtras.getConfigs([did]);
1623
+ const checkDuplicateAppSk = async ({ sk, did, states }) => {
1624
+ if (!sk && !did) {
1625
+ throw new Error('sk and did is empty');
1626
+ }
1607
1627
 
1608
- const { wallet } = getBlockletInfo(
1609
- {
1610
- meta: blocklet.meta,
1611
- environments: (configs || []).filter((x) => x.value),
1612
- },
1613
- nodeInfo.sk
1614
- );
1628
+ let appSk = sk;
1629
+ if (!sk) {
1630
+ const nodeInfo = await states.node.read();
1631
+ const blocklet = await states.blocklet.getBlocklet(did);
1632
+ const configs = await states.blockletExtras.getConfigs([did]);
1633
+ const { wallet } = getBlockletInfo(
1634
+ {
1635
+ meta: blocklet.meta,
1636
+ environments: (configs || []).filter((x) => x.value),
1637
+ },
1638
+ nodeInfo.sk
1639
+ );
1640
+ appSk = wallet.secretKey;
1641
+ }
1615
1642
 
1616
- const others = blocklets.filter((b) => b.meta.did !== did);
1643
+ const blocklets = await states.blocklet.getBlocklets({});
1644
+ const others = did ? blocklets.filter((b) => b.meta.did !== did) : blocklets;
1617
1645
 
1618
1646
  const exist = others.find((b) => {
1619
1647
  const item = (b.environments || []).find((e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
1620
- return item?.value === toHex(wallet.secretKey);
1648
+ return item?.value === toHex(appSk);
1621
1649
  });
1622
1650
 
1623
1651
  if (exist) {
@@ -1625,6 +1653,19 @@ const checkDuplicateAppSk = async ({ did, states }) => {
1625
1653
  }
1626
1654
  };
1627
1655
 
1656
+ const checkDuplicateMountPoint = (blocklet, mountPoint) => {
1657
+ const err = new Error(`cannot add duplicate mount point, ${mountPoint || '/'} already exist`);
1658
+ if (normalizePathPrefix(blocklet.mountPoint) === normalizePathPrefix(mountPoint)) {
1659
+ throw err;
1660
+ }
1661
+
1662
+ for (const component of blocklet.children || []) {
1663
+ if (normalizePathPrefix(component.mountPoint) === normalizePathPrefix(mountPoint)) {
1664
+ throw err;
1665
+ }
1666
+ }
1667
+ };
1668
+
1628
1669
  module.exports = {
1629
1670
  consumeServerlessNFT,
1630
1671
  forEachBlocklet,
@@ -1650,13 +1691,14 @@ module.exports = {
1650
1691
  expandTarball,
1651
1692
  verifyIntegrity,
1652
1693
  statusMap,
1694
+ getAppDirs,
1653
1695
  pruneBlockletBundle,
1654
1696
  getDiskInfo,
1655
1697
  getRuntimeInfo,
1656
1698
  mergeMeta,
1657
1699
  fixAndVerifyMetaFromStore,
1658
1700
  getUpdateMetaList,
1659
- getSourceFromInstallParams,
1701
+ getTypeFromInstallParams,
1660
1702
  findWebInterface,
1661
1703
  checkDuplicateComponents,
1662
1704
  getDiffFiles,
@@ -1670,4 +1712,5 @@ module.exports = {
1670
1712
  createDataArchive,
1671
1713
  validateAppConfig,
1672
1714
  checkDuplicateAppSk,
1715
+ checkDuplicateMountPoint,
1673
1716
  };
@@ -47,7 +47,7 @@ const ruleSchema = {
47
47
  port: Joi.number().label('port').port().when('type', { is: ROUTING_RULE_TYPES.BLOCKLET, then: Joi.required() }),
48
48
  url: Joi.string().label('url').when('type', { is: ROUTING_RULE_TYPES.REDIRECT, then: Joi.required() }),
49
49
  redirectCode: Joi.alternatives()
50
- .try(301, 302)
50
+ .try(301, 302, 307, 308)
51
51
  .label('redirect code')
52
52
  .when('type', { is: ROUTING_RULE_TYPES.REDIRECT, then: Joi.required() }),
53
53
  interfaceName: Joi.string() // root interface
@@ -81,7 +81,7 @@ const ruleSchema = {
81
81
  const corsSchema = Joi.array()
82
82
  .items(
83
83
  Joi.string().domain({ minDomainSegments: 1, tlds: false }),
84
- Joi.string().valid(DOMAIN_FOR_DEFAULT_SITE),
84
+ Joi.string().valid(DOMAIN_FOR_DEFAULT_SITE, '__none__'),
85
85
  Joi.string().ip()
86
86
  )
87
87
  .min(1)
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.8.61",
6
+ "version": "1.8.63-beta-c51e554d",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,18 +19,18 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/auth": "1.8.61",
23
- "@abtnode/certificate-manager": "1.8.61",
24
- "@abtnode/constant": "1.8.61",
25
- "@abtnode/cron": "1.8.61",
26
- "@abtnode/db": "1.8.61",
27
- "@abtnode/logger": "1.8.61",
28
- "@abtnode/queue": "1.8.61",
29
- "@abtnode/rbac": "1.8.61",
30
- "@abtnode/router-provider": "1.8.61",
31
- "@abtnode/static-server": "1.8.61",
32
- "@abtnode/timemachine": "1.8.61",
33
- "@abtnode/util": "1.8.61",
22
+ "@abtnode/auth": "1.8.63-beta-c51e554d",
23
+ "@abtnode/certificate-manager": "1.8.63-beta-c51e554d",
24
+ "@abtnode/constant": "1.8.63-beta-c51e554d",
25
+ "@abtnode/cron": "1.8.63-beta-c51e554d",
26
+ "@abtnode/db": "1.8.63-beta-c51e554d",
27
+ "@abtnode/logger": "1.8.63-beta-c51e554d",
28
+ "@abtnode/queue": "1.8.63-beta-c51e554d",
29
+ "@abtnode/rbac": "1.8.63-beta-c51e554d",
30
+ "@abtnode/router-provider": "1.8.63-beta-c51e554d",
31
+ "@abtnode/static-server": "1.8.63-beta-c51e554d",
32
+ "@abtnode/timemachine": "1.8.63-beta-c51e554d",
33
+ "@abtnode/util": "1.8.63-beta-c51e554d",
34
34
  "@arcblock/did": "1.18.36",
35
35
  "@arcblock/did-motif": "^1.1.10",
36
36
  "@arcblock/did-util": "1.18.36",
@@ -38,9 +38,9 @@
38
38
  "@arcblock/jwt": "^1.18.36",
39
39
  "@arcblock/pm2-events": "^0.0.5",
40
40
  "@arcblock/vc": "1.18.36",
41
- "@blocklet/constant": "1.8.61",
42
- "@blocklet/meta": "1.8.61",
43
- "@blocklet/sdk": "1.8.61",
41
+ "@blocklet/constant": "1.8.63-beta-c51e554d",
42
+ "@blocklet/meta": "1.8.63-beta-c51e554d",
43
+ "@blocklet/sdk": "1.8.63-beta-c51e554d",
44
44
  "@fidm/x509": "^1.2.1",
45
45
  "@ocap/mcrypto": "1.18.36",
46
46
  "@ocap/util": "1.18.36",
@@ -85,5 +85,5 @@
85
85
  "express": "^4.18.2",
86
86
  "jest": "^27.5.1"
87
87
  },
88
- "gitHead": "f68ac14c2074400d392e2171d328f12dbd2e64cb"
88
+ "gitHead": "9913ed7968dfab63d6549201a5a98f9819fcfac6"
89
89
  }