@abtnode/core 1.8.68 → 1.8.69-beta-54faead3

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.
@@ -2,6 +2,7 @@ const { EventEmitter } = require('events');
2
2
  const fs = require('fs-extra');
3
3
  const { fileURLToPath } = require('url');
4
4
  const path = require('path');
5
+ const pick = require('lodash/pick');
5
6
  const cloneDeep = require('lodash/cloneDeep');
6
7
  const { toBase58 } = require('@ocap/util');
7
8
 
@@ -161,7 +162,11 @@ class BundleDownloader extends EventEmitter {
161
162
  }
162
163
  ctrlStore[rootDid].set(did, cancelCtrl);
163
164
 
164
- const headers = context.headers ? cloneDeep(context.headers) : {};
165
+ const headers = pick(context.headers ? cloneDeep(context.headers) : {}, [
166
+ 'x-server-did',
167
+ 'x-server-public-key',
168
+ 'x-server-signature',
169
+ ]);
165
170
  const exist = (context.downloadTokenList || []).find((x) => x.did === did);
166
171
  if (exist) {
167
172
  headers['x-download-token'] = exist.token;
@@ -4,6 +4,7 @@ const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const flat = require('flat');
6
6
  const get = require('lodash/get');
7
+ const merge = require('lodash/merge');
7
8
  const pick = require('lodash/pick');
8
9
  const cloneDeep = require('lodash/cloneDeep');
9
10
  const semver = require('semver');
@@ -15,20 +16,18 @@ const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
15
16
  const didDocument = require('@abtnode/util/lib/did-document');
16
17
  const { sign } = require('@arcblock/jwt');
17
18
  const { isValid: isValidDid } = require('@arcblock/did');
18
- const { verifyPresentation } = require('@arcblock/vc');
19
19
  const { toSvg: createDidLogo } =
20
20
  process.env.NODE_ENV !== 'test' ? require('@arcblock/did-motif') : require('@arcblock/did-motif/dist/did-motif.cjs');
21
21
  const getBlockletInfo = require('@blocklet/meta/lib/info');
22
22
  const sleep = require('@abtnode/util/lib/sleep');
23
23
 
24
24
  const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
25
- const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
26
25
  const {
27
- VC_TYPE_BLOCKLET_PURCHASE,
28
26
  WHO_CAN_ACCESS,
29
27
  SERVER_ROLES,
30
28
  WHO_CAN_ACCESS_PREFIX_ROLES,
31
29
  BLOCKLET_INSTALL_TYPE,
30
+ NODE_MODES,
32
31
  } = require('@abtnode/constant');
33
32
 
34
33
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
@@ -49,7 +48,7 @@ const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-
49
48
  const toBlockletDid = require('@blocklet/meta/lib/did');
50
49
  const { validateMeta } = require('@blocklet/meta/lib/validate');
51
50
  const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
52
- const { titleSchema, mountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
51
+ const { titleSchema, updateMountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
53
52
  const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
54
53
  const Lock = require('@abtnode/util/lib/lock');
55
54
 
@@ -111,6 +110,10 @@ const {
111
110
  validateAppConfig,
112
111
  checkDuplicateAppSk,
113
112
  checkDuplicateMountPoint,
113
+ validateStore,
114
+ validateInServerless,
115
+ isRotatingAppSk,
116
+ isRotatingAppDid,
114
117
  } = require('../../util/blocklet');
115
118
  const StoreUtil = require('../../util/store');
116
119
  const states = require('../../states');
@@ -193,13 +196,14 @@ class BlockletManager extends BaseBlockletManager {
193
196
  /**
194
197
  * @param {*} dataDirs generate by ../../util:getDataDirs
195
198
  */
196
- constructor({ dataDirs, startQueue, installQueue, daemon = false, teamManager }) {
199
+ constructor({ dataDirs, startQueue, installQueue, backupQueue, daemon = false, teamManager }) {
197
200
  super();
198
201
 
199
202
  this.dataDirs = dataDirs;
200
203
  this.installDir = dataDirs.blocklets;
201
204
  this.startQueue = startQueue;
202
205
  this.installQueue = installQueue;
206
+ this.backupQueue = backupQueue;
203
207
  this.teamManager = teamManager;
204
208
 
205
209
  // cached installed blocklets for performance
@@ -242,6 +246,8 @@ class BlockletManager extends BaseBlockletManager {
242
246
  * did: string;
243
247
  * title: string;
244
248
  * description: string;
249
+ * storeUrl: string;
250
+ * appSk: string;
245
251
  * sync: boolean = false; // download synchronously, not use queue
246
252
  * delay: number; // push download task to queue after a delay
247
253
  * downloadTokenList: Array<{did: string, token: string}>;
@@ -264,6 +270,7 @@ class BlockletManager extends BaseBlockletManager {
264
270
 
265
271
  const info = await states.node.read();
266
272
 
273
+ // Note: if you added new header here, please change core/state/lib/blocklet/downloader/bundle-downloader.js to use that header
267
274
  context.headers = Object.assign(context?.headers || {}, {
268
275
  'x-server-did': info.did,
269
276
  'x-server-public-key': info.pk,
@@ -278,28 +285,31 @@ class BlockletManager extends BaseBlockletManager {
278
285
  context.startImmediately = !!params.startImmediately;
279
286
  }
280
287
 
288
+ const { appSk } = params;
289
+
281
290
  if (type === BLOCKLET_INSTALL_TYPE.URL) {
282
291
  const { url, controller, sync, delay } = params;
283
- return this._installFromUrl({ url, controller, sync, delay }, context);
292
+ return this._installFromUrl({ url, controller, sync, delay, appSk }, context);
284
293
  }
285
294
 
286
295
  if (type === BLOCKLET_INSTALL_TYPE.UPLOAD) {
287
296
  const { file, did, diffVersion, deleteSet } = params;
288
- return this._installFromUpload({ file, did, diffVersion, deleteSet, context });
297
+ return this._installFromUpload({ file, did, diffVersion, deleteSet, appSk }, context);
289
298
  }
290
299
 
291
300
  if (type === BLOCKLET_INSTALL_TYPE.STORE) {
292
301
  const { did, controller, sync, delay, storeUrl } = params;
293
- return this._installFromStore({ did, controller, sync, delay, storeUrl }, context);
302
+ return this._installFromStore({ did, controller, sync, delay, storeUrl, appSk }, context);
294
303
  }
295
304
 
296
305
  if (type === BLOCKLET_INSTALL_TYPE.CREATE) {
297
- return this._installFromCreate({ title: params.title, description: params.description }, context);
306
+ const { title, description } = params;
307
+ return this._installFromCreate({ title, description, appSk }, context);
298
308
  }
299
309
 
300
310
  if (type === BLOCKLET_INSTALL_TYPE.RESTORE) {
301
- const { url, blockletSecretKey } = params;
302
- return this._installFromBackup({ url, blockletSecretKey }, context);
311
+ const { url } = params;
312
+ return this._installFromBackup({ url, appSk }, context);
303
313
  }
304
314
 
305
315
  // should not be here
@@ -343,10 +353,16 @@ class BlockletManager extends BaseBlockletManager {
343
353
  },
344
354
  context = {}
345
355
  ) {
346
- const mountPoint = await mountPointSchema.validateAsync(tmpMountPoint);
356
+ const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
347
357
  logger.debug('start install component', { rootDid, mountPoint, url });
348
358
 
349
359
  if (file) {
360
+ // TODO: 如何触发这种场景?
361
+ const info = await states.node.read();
362
+ if (info.mode === NODE_MODES.SERVERLESS) {
363
+ throw new Error("Can't install component in serverless-mode server via upload");
364
+ }
365
+
350
366
  return this._installComponentFromUpload({
351
367
  rootDid,
352
368
  mountPoint,
@@ -360,6 +376,11 @@ class BlockletManager extends BaseBlockletManager {
360
376
  }
361
377
 
362
378
  if (url) {
379
+ const info = await states.node.read();
380
+ if (info.mode === NODE_MODES.SERVERLESS) {
381
+ validateStore(info, url);
382
+ }
383
+
363
384
  return this._installComponentFromUrl({
364
385
  rootDid,
365
386
  mountPoint,
@@ -431,25 +452,6 @@ class BlockletManager extends BaseBlockletManager {
431
452
  return { isInstalled: !!blocklet, isRunning, blockletDid, isExternal };
432
453
  }
433
454
 
434
- async installBlockletFromVc({ vcPresentation, challenge }, context) {
435
- logger.info('Install from vc');
436
- const vc = getVcFromPresentation(vcPresentation);
437
-
438
- // FIXME: 这里的 trustedIssuers 相当于相信任何 VC,需要想更安全的方法
439
- verifyPresentation({ presentation: vcPresentation, trustedIssuers: [get(vc, 'issuer.id')], challenge });
440
-
441
- if (!vc.type.includes(VC_TYPE_BLOCKLET_PURCHASE)) {
442
- throw new Error(`Expect ${VC_TYPE_BLOCKLET_PURCHASE} VC type`);
443
- }
444
-
445
- const blockletUrl = get(vc, 'credentialSubject.purchased.blocklet.url');
446
- const urlObject = new URL(blockletUrl);
447
- const did = get(vc, 'credentialSubject.purchased.blocklet.id');
448
- const registry = urlObject.origin;
449
-
450
- return this._installFromStore({ did, registry }, context);
451
- }
452
-
453
455
  async start({ did, throwOnError, checkHealthImmediately = false, e2eMode = false }, context) {
454
456
  logger.info('start blocklet', { did });
455
457
  // should check blocklet integrity
@@ -608,32 +610,65 @@ class BlockletManager extends BaseBlockletManager {
608
610
  }
609
611
 
610
612
  /**
611
- *
612
- *
613
- * @param {import('@abtnode/client').BackupToSpacesParams} input
613
+ * FIXME: @wangshijun create audit log for this
614
+ * @param {import('@abtnode/client').RequestBackupToSpacesInput} input
614
615
  * @memberof BlockletManager
615
616
  */
616
- async backupToSpaces(input) {
617
- const spacesBackup = new SpacesBackup(input);
617
+ // eslint-disable-next-line no-unused-vars
618
+ async backupToSpaces({ appPid }, context) {
619
+ // add to queue
620
+ // const ticket = this.backupQueue.push({
621
+ // entity: 'blocklet',
622
+ // action: 'backup-to-space',
623
+ // appPid,
624
+ // context,
625
+ // });
626
+
627
+ // ticket.on('failed', async (err) => {
628
+ // logger.error('backup failed', { entity: 'blocklet', appPid, error: err });
629
+ // this.emit('blocklet.backup.failed', { appPid, err });
630
+ // this._createNotification(appPid, {
631
+ // title: 'Blocklet Backup Failed',
632
+ // description: `Blocklet backup failed with error: ${err.message || 'queue exception'}`,
633
+ // entityType: 'blocklet',
634
+ // entityId: appPid,
635
+ // severity: 'error',
636
+ // });
637
+ // });
638
+
639
+ const userDid = context.user.did;
640
+ const { referrer } = context;
641
+
642
+ const spacesBackup = new SpacesBackup({ appPid, event: this, userDid, referrer });
643
+ this.emit(BlockletEvents.backupProgress, { appPid, message: 'Start backup...', progress: 10, completed: false });
618
644
  await spacesBackup.backup();
645
+ this.emit(BlockletEvents.backupProgress, { appPid, completed: true, progress: 100 });
619
646
  }
620
647
 
621
648
  /**
622
- *
623
- *
649
+ * FIXME: @linchen support cancel
650
+ * FIXME: @wangshijun create audit log for this
624
651
  * @param {import('@abtnode/client').RequestRestoreFromSpacesInput} input
625
652
  * @memberof BlockletManager
626
653
  */
627
- async restoreFromSpaces(input) {
628
- const spacesRestore = new SpacesRestore(input);
629
- await spacesRestore.restore();
654
+ // eslint-disable-next-line no-unused-vars
655
+ async restoreFromSpaces(input, context) {
656
+ this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, message: 'Start restore...', completed: false });
657
+
658
+ const userDid = context.user.did;
659
+ const { referrer } = context;
660
+
661
+ const spacesRestore = new SpacesRestore({ ...input, event: this, userDid, referrer });
662
+ const params = await spacesRestore.restore();
630
663
 
631
- // FIXME: 需要改成队列执行,本次失败,下次还需要重试,页面刷新后也能知道最新的状态
664
+ this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, message: 'Installing blocklet...' });
632
665
  await this._installFromBackup({
633
- url: `file://${spacesRestore.blockletRestoreDir}`,
634
- blockletSecretKey: spacesRestore.blockletWallet.secretKey,
666
+ url: `file://${spacesRestore.restoreDir}`,
635
667
  moveDir: true,
668
+ ...merge(...params),
636
669
  });
670
+
671
+ this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, completed: true });
637
672
  }
638
673
 
639
674
  async restart({ did }, context) {
@@ -1003,8 +1038,9 @@ class BlockletManager extends BaseBlockletManager {
1003
1038
  return blocklets;
1004
1039
  }
1005
1040
 
1041
+ // CAUTION: this method currently only support config by blocklet.meta.did
1006
1042
  // eslint-disable-next-line no-unused-vars
1007
- async config({ did, configs: newConfigs, skipHook }, context) {
1043
+ async config({ did, configs: newConfigs, skipHook, skipDidDocument }, context) {
1008
1044
  if (!Array.isArray(newConfigs)) {
1009
1045
  throw new Error('configs list is not an array');
1010
1046
  }
@@ -1034,7 +1070,7 @@ class BlockletManager extends BaseBlockletManager {
1034
1070
  throw new Error(`Cannot set ${x.key} to child blocklet`);
1035
1071
  }
1036
1072
 
1037
- await validateAppConfig(x, rootDid, states);
1073
+ await validateAppConfig(x, states);
1038
1074
  } else if (!BLOCKLET_CONFIGURABLE_KEY[x.key] && !isPreferenceKey(x)) {
1039
1075
  if (!(blocklet.meta.environments || []).some((y) => y.name === x.key)) {
1040
1076
  // forbid unknown format key
@@ -1057,14 +1093,31 @@ class BlockletManager extends BaseBlockletManager {
1057
1093
  });
1058
1094
  }
1059
1095
 
1096
+ const willAppSkChange = isRotatingAppSk(newConfigs, blocklet.configs, blocklet.externalSk);
1097
+ const willAppDidChange = isRotatingAppDid(newConfigs, blocklet.configs, blocklet.externalSk);
1098
+
1060
1099
  // update db
1061
1100
  await states.blockletExtras.setConfigs(dids, newConfigs);
1062
1101
 
1063
- const isSkOrWalletTypeChanged = newConfigs.find((item) =>
1064
- [BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK, BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_WALLET_TYPE].includes(item.key)
1065
- );
1102
+ if (willAppSkChange) {
1103
+ const info = await states.node.read();
1104
+ const { wallet } = getBlockletInfo(blocklet, info.sk);
1105
+ const migratedFrom = Array.isArray(blocklet.migratedFrom) ? blocklet.migratedFrom : [];
1106
+ await states.blocklet.updateBlocklet(rootDid, {
1107
+ migratedFrom: [
1108
+ ...migratedFrom,
1109
+ { appSk: wallet.secretKey, appDid: wallet.address, at: new Date().toISOString() },
1110
+ ],
1111
+ });
1112
+ }
1066
1113
 
1067
- if (isSkOrWalletTypeChanged) {
1114
+ // Reload nginx to make sure did-space can embed content from this app
1115
+ if (newConfigs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT)?.value) {
1116
+ this.emit(BlockletEvents.spaceConnected, blocklet);
1117
+ }
1118
+
1119
+ // FIXME: @zhenqiang best way to handle the did document: allow all appDids to resolve
1120
+ if (willAppDidChange && !skipDidDocument) {
1068
1121
  await this._updateDidDocument(blocklet);
1069
1122
  }
1070
1123
 
@@ -1198,7 +1251,7 @@ class BlockletManager extends BaseBlockletManager {
1198
1251
  }
1199
1252
 
1200
1253
  async updateComponentMountPoint({ did, rootDid: inputRootDid, mountPoint: tmpMountPoint }, context) {
1201
- const mountPoint = await mountPointSchema.validateAsync(tmpMountPoint);
1254
+ const mountPoint = await updateMountPointSchema.validateAsync(tmpMountPoint);
1202
1255
 
1203
1256
  const blocklet = await states.blocklet.getBlocklet(inputRootDid);
1204
1257
 
@@ -1679,13 +1732,13 @@ class BlockletManager extends BaseBlockletManager {
1679
1732
  * /blocklet.json
1680
1733
  * /blocklet_extras.json
1681
1734
  * @see Blocklet 端到端备份方案的设计与实现(一期) https://team.arcblock.io/comment/discussions/e62084d5-fafa-489e-91d5-defcd6e93223
1682
- * @param {{ url: string, blockletSecretKey: string, moveDir: boolean}} [{ url }={}]
1735
+ * @param {{ url: string, appSk: string, moveDir: boolean}} [{ url }={}]
1683
1736
  * @param {Record<string, string>} [context={}]
1684
1737
  * @return {Promise<any>}
1685
1738
  * @memberof BlockletManager
1686
1739
  */
1687
- async _installFromBackup({ url, blockletSecretKey, moveDir } = {}, context = {}) {
1688
- return installFromBackup({ url, blockletSecretKey, moveDir, context, manager: this, states });
1740
+ async _installFromBackup({ url, appSk, moveDir } = {}, context = {}) {
1741
+ return installFromBackup({ url, appSk, moveDir, context, manager: this, states });
1689
1742
  }
1690
1743
 
1691
1744
  async ensureBlocklet(did, opts = {}) {
@@ -2067,6 +2120,14 @@ class BlockletManager extends BaseBlockletManager {
2067
2120
  // put BLOCKLET_APP_ID at root level for indexing
2068
2121
  blocklet.appDid = appSystemEnvironments.BLOCKLET_APP_ID;
2069
2122
 
2123
+ if (!Array.isArray(blocklet.migratedFrom)) {
2124
+ blocklet.migratedFrom = [];
2125
+ }
2126
+ // This can only be set once, can be used for indexing, will not change ever
2127
+ if (!blocklet.appPid) {
2128
+ blocklet.appPid = appSystemEnvironments.BLOCKLET_APP_PID;
2129
+ }
2130
+
2070
2131
  // update state to db
2071
2132
  return states.blocklet.updateBlocklet(did, blocklet);
2072
2133
  }
@@ -2084,17 +2145,18 @@ class BlockletManager extends BaseBlockletManager {
2084
2145
  *
2085
2146
  * @param {{
2086
2147
  * did: string;
2087
- * registry: string;
2088
2148
  * sync: boolean;
2089
2149
  * delay: number;
2090
2150
  * controller: Controller;
2151
+ * appSk: string;
2152
+ * storeUrl: string;
2091
2153
  * }} params
2092
2154
  * @param {*} context
2093
2155
  * @return {*}
2094
2156
  * @memberof BlockletManager
2095
2157
  */
2096
2158
  async _installFromStore(params, context) {
2097
- const { did, storeUrl, sync, delay, controller } = params;
2159
+ const { did, storeUrl, sync, delay, controller, appSk } = params;
2098
2160
 
2099
2161
  logger.debug('start install blocklet', { did });
2100
2162
  if (!isValidDid(did)) {
@@ -2138,6 +2200,7 @@ class BlockletManager extends BaseBlockletManager {
2138
2200
  sync,
2139
2201
  delay,
2140
2202
  controller,
2203
+ appSk,
2141
2204
  context,
2142
2205
  });
2143
2206
  }
@@ -2154,13 +2217,14 @@ class BlockletManager extends BaseBlockletManager {
2154
2217
  * sync: boolean;
2155
2218
  * delay: number;
2156
2219
  * controller: Controller;
2220
+ * appSk: string;
2157
2221
  * }} params
2158
2222
  * @param {{}} context
2159
2223
  * @return {*}
2160
2224
  * @memberof BlockletManager
2161
2225
  */
2162
2226
  async _installFromUrl(params, context) {
2163
- const { url, sync, delay, controller } = params;
2227
+ const { url, sync, delay, controller, appSk } = params;
2164
2228
 
2165
2229
  logger.debug('start install blocklet', { url });
2166
2230
 
@@ -2174,13 +2238,18 @@ class BlockletManager extends BaseBlockletManager {
2174
2238
 
2175
2239
  // install from store if url is a store url
2176
2240
  const { inStore, registryUrl, blockletDid: bundleDid } = await StoreUtil.parseSourceUrl(url, controller);
2241
+
2242
+ const nodeInfo = await states.node.read();
2243
+
2244
+ await validateStore(nodeInfo, registryUrl);
2245
+
2177
2246
  if (inStore) {
2178
2247
  const exist = await states.blocklet.getBlocklet(blockletDid);
2179
2248
  if (exist) {
2180
2249
  return this.upgrade({ did: blockletDid, storeUrl: registryUrl, sync, delay }, context);
2181
2250
  }
2182
2251
 
2183
- return this._installFromStore({ did: bundleDid, storeUrl: registryUrl, controller, sync, delay }, context);
2252
+ return this._installFromStore({ did: bundleDid, storeUrl: registryUrl, controller, sync, delay, appSk }, context);
2184
2253
  }
2185
2254
 
2186
2255
  const meta = ensureMeta(bundleMeta, { name: blockletName, did: blockletDid });
@@ -2206,6 +2275,7 @@ class BlockletManager extends BaseBlockletManager {
2206
2275
  sync,
2207
2276
  delay,
2208
2277
  controller,
2278
+ appSk,
2209
2279
  context,
2210
2280
  });
2211
2281
  }
@@ -2310,7 +2380,7 @@ class BlockletManager extends BaseBlockletManager {
2310
2380
  );
2311
2381
  }
2312
2382
 
2313
- async _installFromCreate({ title, description }, context = {}) {
2383
+ async _installFromCreate({ title, description, appSk }, context = {}) {
2314
2384
  logger.debug('create blocklet', { title, description });
2315
2385
 
2316
2386
  await joi.string().label('title').max(20).required().validateAsync(title);
@@ -2338,11 +2408,9 @@ class BlockletManager extends BaseBlockletManager {
2338
2408
  };
2339
2409
  const meta = validateMeta(rawMeta);
2340
2410
 
2341
- await states.blocklet.addBlocklet({
2342
- meta,
2343
- source: BlockletSource.custom,
2344
- });
2411
+ await states.blocklet.addBlocklet({ meta, source: BlockletSource.custom, externalSk: !!appSk });
2345
2412
  await this._setConfigsFromMeta(did);
2413
+ await this._setAppSk(did, appSk);
2346
2414
 
2347
2415
  // check duplicate appSk
2348
2416
  await checkDuplicateAppSk({ did, states });
@@ -2382,7 +2450,7 @@ class BlockletManager extends BaseBlockletManager {
2382
2450
  return { cwd, tarFile };
2383
2451
  }
2384
2452
 
2385
- async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
2453
+ async _installFromUpload({ file, did, diffVersion, deleteSet, appSk }, context) {
2386
2454
  logger.info('install blocklet', { from: 'upload file' });
2387
2455
  const { tarFile } = await this._downloadFromUpload(file);
2388
2456
 
@@ -2474,12 +2542,14 @@ class BlockletManager extends BaseBlockletManager {
2474
2542
  source: BlockletSource.upload,
2475
2543
  deployedFrom: `Upload by ${context.user.fullName}`,
2476
2544
  children,
2545
+ externalSk: !!appSk,
2477
2546
  });
2478
2547
 
2479
2548
  const action = 'install';
2480
2549
  const oldState = { extraState: oldExtraState };
2481
2550
  try {
2482
2551
  await this._setConfigsFromMeta(meta.did);
2552
+ await this._setAppSk(appSk);
2483
2553
  await validateBlocklet(blocklet);
2484
2554
 
2485
2555
  // check duplicate appSk
@@ -2776,6 +2846,7 @@ class BlockletManager extends BaseBlockletManager {
2776
2846
  * meta: {}; // blocklet meta
2777
2847
  * source: number; // example: BlockletSource.registry,
2778
2848
  * deployedFrom: string;
2849
+ * appSk: string;
2779
2850
  * context: {}
2780
2851
  * sync: boolean = false;
2781
2852
  * delay: number;
@@ -2785,10 +2856,15 @@ class BlockletManager extends BaseBlockletManager {
2785
2856
  * @memberof BlockletManager
2786
2857
  */
2787
2858
  async _install(params) {
2788
- const { meta, source, deployedFrom, context, sync, delay, controller } = params;
2859
+ const { meta, source, deployedFrom, context, sync, delay, controller, appSk } = params;
2789
2860
 
2790
2861
  validateBlockletMeta(meta, { ensureDist: true });
2791
2862
 
2863
+ const info = await states.node.read();
2864
+ if (info.mode === NODE_MODES.SERVERLESS) {
2865
+ validateInServerless({ blockletMeta: meta });
2866
+ }
2867
+
2792
2868
  const { name, did, version } = meta;
2793
2869
 
2794
2870
  const oldExtraState = await states.blockletExtras.findOne({ did: meta.did });
@@ -2801,6 +2877,7 @@ class BlockletManager extends BaseBlockletManager {
2801
2877
  source,
2802
2878
  deployedFrom,
2803
2879
  children,
2880
+ externalSk: !!appSk,
2804
2881
  });
2805
2882
 
2806
2883
  await validateBlocklet(blocklet);
@@ -2808,6 +2885,7 @@ class BlockletManager extends BaseBlockletManager {
2808
2885
  await states.blockletExtras.addMeta({ did, meta: { did, name }, controller });
2809
2886
 
2810
2887
  await this._setConfigsFromMeta(did);
2888
+ await this._setAppSk(did, appSk);
2811
2889
 
2812
2890
  // check duplicate appSk
2813
2891
  await checkDuplicateAppSk({ did, states });
@@ -3405,12 +3483,31 @@ class BlockletManager extends BaseBlockletManager {
3405
3483
  }
3406
3484
  }
3407
3485
 
3486
+ async _setAppSk(did, appSk, context) {
3487
+ if (process.env.NODE_ENV === 'production' && !appSk) {
3488
+ throw new Error(`appSk for blocklet ${did} is required`);
3489
+ }
3490
+
3491
+ if (appSk) {
3492
+ await this.config(
3493
+ {
3494
+ did,
3495
+ configs: [{ key: 'BLOCKLET_APP_SK', value: appSk, secure: true }],
3496
+ skipHook: true,
3497
+ skipDidDocument: true,
3498
+ },
3499
+ context
3500
+ );
3501
+ }
3502
+ }
3503
+
3408
3504
  async _findNextCustomBlockletName(leftTimes = 10) {
3409
3505
  if (leftTimes <= 0) {
3410
3506
  throw new Error('Generate custom blocklet did too many times');
3411
3507
  }
3412
3508
  const number = await states.node.increaseCustomBlockletNumber();
3413
3509
  const name = `custom-${number}`;
3510
+ // MEMO: 空壳 APP可以保留原有的 did 生成逻辑
3414
3511
  const did = toBlockletDid(name);
3415
3512
  const blocklet = await states.blocklet.getBlocklet(did);
3416
3513
  if (blocklet) {
@@ -2,7 +2,7 @@ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const omit = require('lodash/omit');
4
4
 
5
- const { forEachBlockletSync } = require('@blocklet/meta/lib/util');
5
+ const { forEachBlockletSync, getBlockletAppIdList } = require('@blocklet/meta/lib/util');
6
6
  const getBlockletInfo = require('@blocklet/meta/lib/info');
7
7
 
8
8
  const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
@@ -19,7 +19,7 @@ const { validateBlocklet, checkDuplicateAppSk, getAppDirs } = require('../../../
19
19
  * }} param0
20
20
  * @returns
21
21
  */
22
- module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states, manager } = {}) => {
22
+ module.exports = async ({ url, appSk, moveDir, context = {}, states, manager } = {}) => {
23
23
  // TODO: support more url schema feature (http, did-spaces)
24
24
  if (!url.startsWith('file://')) {
25
25
  throw new Error('url must starts with file://');
@@ -71,17 +71,17 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
71
71
  throw new Error('blocklet is already exist');
72
72
  }
73
73
 
74
- if (blockletSecretKey) {
74
+ if (appSk) {
75
75
  extra.configs = extra.configs || [];
76
76
  const skConfig = extra.configs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK);
77
77
  if (skConfig) {
78
- skConfig.value = blockletSecretKey;
78
+ skConfig.value = appSk;
79
79
  skConfig.secure = true;
80
80
  skConfig.shared = false;
81
81
  } else {
82
82
  extra.configs.push({
83
83
  key: BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SK,
84
- value: blockletSecretKey,
84
+ value: appSk,
85
85
  secure: true,
86
86
  shared: false,
87
87
  });
@@ -90,8 +90,9 @@ module.exports = async ({ url, blockletSecretKey, moveDir, context = {}, states,
90
90
 
91
91
  // validate appDid
92
92
  const nodeInfo = await states.node.read();
93
+ const appIdList = getBlockletAppIdList(state);
93
94
  const { wallet } = getBlockletInfo({ meta: state.meta, configs: extra.configs, environments: [] }, nodeInfo.sk);
94
- if (state.appDid !== wallet.address) {
95
+ if (appIdList.includes(wallet.address) === false) {
95
96
  throw new Error('blocklet appDid is different from the previous one');
96
97
  }
97
98
  await checkDuplicateAppSk({ sk: wallet.secretKey, states });
@@ -19,8 +19,8 @@ class AuditLogBackup extends BaseBackup {
19
19
  scope: this.blocklet.meta.did,
20
20
  });
21
21
 
22
- removeSync(join(this.blockletBackupDir, this.filename));
23
- outputJsonSync(join(this.blockletBackupDir, this.filename), auditLogs);
22
+ removeSync(join(this.backupDir, this.filename));
23
+ outputJsonSync(join(this.backupDir, this.filename), auditLogs);
24
24
  }
25
25
  }
26
26
 
@@ -1,6 +1,5 @@
1
1
  class BaseBackup {
2
2
  /**
3
- *
4
3
  * @type {import('./spaces').SpaceBackupInput}
5
4
  * @memberof BaseBackup
6
5
  */
@@ -18,15 +17,15 @@ class BaseBackup {
18
17
  * @type {string}
19
18
  * @memberof BaseBackup
20
19
  */
21
- blockletBackupDir;
20
+ backupDir;
22
21
 
23
22
  /**
24
23
  *
25
24
  * @description spaces 的 endpoint
26
- * @type {import('@ocap/wallet').WalletObject}
25
+ * @type {import('./spaces').SecurityContext}
27
26
  * @memberof BaseBackup
28
27
  */
29
- blockletWallet;
28
+ securityContext;
30
29
 
31
30
  /**
32
31
  *
@@ -34,7 +33,7 @@ class BaseBackup {
34
33
  * @type {string}
35
34
  * @memberof BaseBackup
36
35
  */
37
- serverDataDir;
36
+ serverDir;
38
37
 
39
38
  constructor(input) {
40
39
  this.input = input;
@@ -48,9 +47,9 @@ class BaseBackup {
48
47
  */
49
48
  ensureParams(spacesBackup) {
50
49
  this.blocklet = spacesBackup.blocklet;
51
- this.serverDataDir = spacesBackup.serverDataDir;
52
- this.blockletBackupDir = spacesBackup.blockletBackupDir;
53
- this.blockletWallet = spacesBackup.blockletWallet;
50
+ this.serverDir = spacesBackup.serverDir;
51
+ this.backupDir = spacesBackup.backupDir;
52
+ this.securityContext = spacesBackup.securityContext;
54
53
  }
55
54
 
56
55
  async export() {