@abtnode/core 1.6.16 → 1.6.20

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.
@@ -3,12 +3,13 @@
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const get = require('lodash/get');
6
+ const pick = require('lodash/pick');
6
7
  const cloneDeep = require('lodash/cloneDeep');
7
8
  const semver = require('semver');
8
9
  const capitalize = require('lodash/capitalize');
9
- const diff = require('deep-diff');
10
10
  const { Throttle } = require('stream-throttle');
11
11
  const LRU = require('lru-cache');
12
+ const joi = require('joi');
12
13
 
13
14
  const { isValid: isValidDid } = require('@arcblock/did');
14
15
  const { verifyPresentation } = require('@arcblock/vc');
@@ -16,17 +17,23 @@ const { toBase58, isHex } = require('@ocap/util');
16
17
  const { fromSecretKey } = require('@ocap/wallet');
17
18
 
18
19
  const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
19
- const hashFiles = require('@abtnode/util/lib/hash-files');
20
20
  const downloadFile = require('@abtnode/util/lib/download-file');
21
21
  const Lock = require('@abtnode/util/lib/lock');
22
22
  const { getVcFromPresentation } = require('@abtnode/util/lib/vc');
23
- const { updateBlocklet: updateDidDocument } = require('@abtnode/util/lib/did-document');
24
23
  const { BLOCKLET_PURCHASE_NFT_TYPE } = require('@abtnode/constant');
25
24
 
26
25
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
27
- const { isFreeBlocklet, isDeletableBlocklet, getRequiredMissingConfigs } = require('@blocklet/meta/lib/util');
26
+ const {
27
+ isFreeBlocklet,
28
+ isComponentBlocklet,
29
+ isDeletableBlocklet,
30
+ getRequiredMissingConfigs,
31
+ hasRunnableComponent,
32
+ } = require('@blocklet/meta/lib/util');
28
33
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
29
- const { getBlockletInfo } = require('@blocklet/meta/lib');
34
+ const toBlockletDid = require('@blocklet/meta/lib/did');
35
+ const { validateMeta } = require('@blocklet/meta/lib/validate');
36
+ const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
30
37
 
31
38
  const {
32
39
  BlockletStatus,
@@ -37,6 +44,15 @@ const {
37
44
  BlockletGroup,
38
45
  fromBlockletStatus,
39
46
  fromBlockletSource,
47
+ BLOCKLET_DEFAULT_PORT_NAME,
48
+ BLOCKLET_INTERFACE_TYPE_WEB,
49
+ BLOCKLET_INTERFACE_PUBLIC,
50
+ BLOCKLET_DYNAMIC_PATH_PREFIX,
51
+ BLOCKLET_INTERFACE_PROTOCOL_HTTP,
52
+ BLOCKLET_DEFAULT_PATH_REWRITE,
53
+ BLOCKLET_DEFAULT_VERSION,
54
+ BLOCKLET_LATEST_SPEC_VERSION,
55
+ BLOCKLET_META_FILE,
40
56
  } = require('@blocklet/meta/lib/constants');
41
57
  const util = require('../../util');
42
58
  const {
@@ -60,16 +76,20 @@ const {
60
76
  getBlockletStatusFromProcess,
61
77
  checkBlockletProcessHealthy,
62
78
  validateBlocklet,
63
- getChildrenMeta,
64
79
  statusMap,
65
80
  expandTarball,
66
81
  verifyIntegrity,
67
82
  pruneBlockletBundle,
68
83
  getDiskInfo,
69
84
  getRuntimeInfo,
70
- mergeMeta,
71
85
  getUpdateMetaList,
72
86
  getRuntimeEnvironments,
87
+ getSourceFromInstallParams,
88
+ parseChildren,
89
+ checkDuplicateComponents,
90
+ getDiffFiles,
91
+ getBundleDir,
92
+ needBlockletDownload,
73
93
  } = require('../../util/blocklet');
74
94
  const states = require('../../states');
75
95
  const BlockletRegistry = require('../registry');
@@ -153,20 +173,43 @@ class BlockletManager extends BaseBlockletManager {
153
173
  */
154
174
  async install(params, context) {
155
175
  logger.debug('install blocklet', { params, context });
156
- if (params.url) {
176
+
177
+ const source = getSourceFromInstallParams(params);
178
+
179
+ if (source === BlockletSource.url) {
157
180
  return this._installFromUrl({ url: params.url, sync: params.sync }, context);
158
181
  }
159
182
 
160
- if (params.file) {
183
+ if (source === BlockletSource.upload) {
161
184
  const { file, did, diffVersion, deleteSet } = params;
162
185
  return this._installFromUpload({ file, did, diffVersion, deleteSet, context });
163
186
  }
164
187
 
165
- if (params.did) {
166
- return this._installFromRegistry({ did: params.did }, context);
188
+ if (source === BlockletSource.registry) {
189
+ return this._installFromStore({ did: params.did }, context);
190
+ }
191
+
192
+ if (source === BlockletSource.custom) {
193
+ return this._installFromCreate({ title: params.title, description: params.description }, context);
194
+ }
195
+
196
+ // should not be here
197
+ throw new Error('Unknown source');
198
+ }
199
+
200
+ async installComponent({ rootDid, mountPoint, url, file, did, diffVersion, deleteSet }, context = {}) {
201
+ logger.debug('start install component', { rootDid, mountPoint, url });
202
+
203
+ if (file) {
204
+ return this._installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context });
205
+ }
206
+
207
+ if (url) {
208
+ return this._installComponentFromUrl({ rootDid, mountPoint, url, context });
167
209
  }
168
210
 
169
- throw new Error('Can only install blocklet from url/file/did');
211
+ // should not be here
212
+ throw new Error('Unknown source');
170
213
  }
171
214
 
172
215
  // eslint-disable-next-line no-unused-vars
@@ -210,7 +253,7 @@ class BlockletManager extends BaseBlockletManager {
210
253
  const did = get(vc, 'credentialSubject.purchased.blocklet.id');
211
254
  const registry = urlObject.origin;
212
255
 
213
- return this._installFromRegistry({ did, registry }, { ...context, blockletPurchaseVerified: true });
256
+ return this._installFromStore({ did, registry }, { ...context, blockletPurchaseVerified: true });
214
257
  }
215
258
 
216
259
  async start({ did, checkHealthImmediately = false, throwOnError }, context) {
@@ -218,6 +261,10 @@ class BlockletManager extends BaseBlockletManager {
218
261
  const blocklet = await this.ensureBlocklet(did);
219
262
 
220
263
  try {
264
+ if (!hasRunnableComponent(blocklet)) {
265
+ throw new Error('No runnable component found');
266
+ }
267
+
221
268
  // check required config
222
269
  const missingProps = getRequiredMissingConfigs(blocklet);
223
270
  if (missingProps.length) {
@@ -246,7 +293,6 @@ class BlockletManager extends BaseBlockletManager {
246
293
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
247
294
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
248
295
  did, // root blocklet did,
249
- progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
250
296
  }),
251
297
  nodeEnvironments,
252
298
  nodeInfo: await states.node.read(),
@@ -297,7 +343,7 @@ class BlockletManager extends BaseBlockletManager {
297
343
  }
298
344
  }
299
345
 
300
- async stop({ did, updateStatus = true }, context) {
346
+ async stop({ did, updateStatus = true, silent = false }, context) {
301
347
  logger.info('stop blocklet', { did });
302
348
 
303
349
  const blocklet = await this.ensureBlocklet(did);
@@ -313,7 +359,7 @@ class BlockletManager extends BaseBlockletManager {
313
359
 
314
360
  await stopBlockletProcess(blocklet, {
315
361
  preStop: (b) =>
316
- hooks.preStop({
362
+ hooks.preStop(b.env.appId, {
317
363
  appDir: b.env.appDir,
318
364
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
319
365
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
@@ -321,7 +367,7 @@ class BlockletManager extends BaseBlockletManager {
321
367
  notification: states.notification,
322
368
  context,
323
369
  exitOnError: false,
324
- progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
370
+ silent,
325
371
  }),
326
372
  });
327
373
 
@@ -389,7 +435,7 @@ class BlockletManager extends BaseBlockletManager {
389
435
 
390
436
  await deleteBlockletProcess(blocklet, {
391
437
  preDelete: (b) =>
392
- hooks.preUninstall({
438
+ hooks.preUninstall(b.env.appId, {
393
439
  appDir: b.env.appDir,
394
440
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
395
441
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
@@ -412,6 +458,22 @@ class BlockletManager extends BaseBlockletManager {
412
458
  }
413
459
  }
414
460
 
461
+ async deleteComponent({ did, rootDid }, context) {
462
+ logger.info('delete blocklet component', { did, rootDid });
463
+
464
+ const doc = await states.blocklet.getBlocklet(rootDid);
465
+
466
+ doc.children = doc.children.filter((x) => x.meta.did !== did);
467
+ const children = (await this._getDynamicChildrenFromSettings(rootDid)).filter((x) => x.meta.did !== did);
468
+
469
+ await states.blocklet.updateBlocklet(rootDid, doc);
470
+ states.blockletExtras.setSettings(rootDid, { children });
471
+
472
+ const newBlocklet = await this.ensureBlocklet(rootDid);
473
+ this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context }); // trigger router refresh
474
+ return newBlocklet;
475
+ }
476
+
415
477
  async cancelDownload({ did }, context) {
416
478
  await preDownloadLock.acquire();
417
479
  try {
@@ -449,12 +511,14 @@ class BlockletManager extends BaseBlockletManager {
449
511
  return result;
450
512
  }
451
513
 
452
- async detail({ did, attachRuntimeInfo = true }, context) {
514
+ async detail({ did, attachConfig = true, attachRuntimeInfo = true }, context) {
453
515
  if (!did) {
454
516
  throw new Error('did should not be empty');
455
517
  }
456
518
 
457
- const nodeInfo = await states.node.read();
519
+ if (!attachConfig) {
520
+ return states.blocklet.getBlocklet(did);
521
+ }
458
522
 
459
523
  if (!attachRuntimeInfo) {
460
524
  try {
@@ -465,6 +529,8 @@ class BlockletManager extends BaseBlockletManager {
465
529
  }
466
530
  }
467
531
 
532
+ const nodeInfo = await states.node.read();
533
+
468
534
  return this.attachRuntimeInfo({ did, nodeInfo, diskInfo: true, context });
469
535
  }
470
536
 
@@ -561,14 +627,13 @@ class BlockletManager extends BaseBlockletManager {
561
627
  }
562
628
 
563
629
  // FIXME: we should also call preConfig for child blocklets
564
- await hooks.preConfig({
630
+ await hooks.preConfig(blocklet.env.appId, {
565
631
  appDir: blocklet.env.appDir,
566
632
  hooks: Object.assign(blocklet.meta.hooks || {}, blocklet.meta.scripts || {}),
567
633
  exitOnError: true,
568
634
  env: { ...getRuntimeEnvironments(blocklet, nodeEnvironments), ...blocklet.configObj },
569
635
  did,
570
636
  context,
571
- progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
572
637
  });
573
638
 
574
639
  // update db
@@ -664,56 +729,45 @@ class BlockletManager extends BaseBlockletManager {
664
729
  }
665
730
 
666
731
  // eslint-disable-next-line no-unused-vars
667
- async diff({ did, hashFiles: clientFiles }, context) {
732
+ async diff({ did, hashFiles: clientFiles, rootDid: inputRootDid }, context) {
668
733
  if (!did) {
669
734
  throw new Error('did is empty');
670
735
  }
736
+
671
737
  if (!clientFiles || !clientFiles.length) {
672
738
  throw new Error('hashFiles is empty');
673
739
  }
674
740
 
675
- logger.info('Get blocklet diff', { did, clientFilesNumber: clientFiles.length });
741
+ const rootDid = inputRootDid || did;
742
+ const childDid = inputRootDid ? did : '';
743
+
744
+ if (childDid === rootDid) {
745
+ throw new Error('Cannot add self as a component');
746
+ }
747
+
748
+ logger.info('Get blocklet diff', { rootDid, childDid, clientFilesNumber: clientFiles.length });
749
+
750
+ const rootBlocklet = await states.blocklet.getBlocklet(rootDid);
751
+ if (childDid && !rootBlocklet) {
752
+ throw new Error('Root blocklet does not exist');
753
+ }
754
+
755
+ const state = childDid ? await (rootBlocklet.children || []).find((x) => x.meta.did === childDid) : rootBlocklet;
676
756
 
677
- const state = await states.blocklet.getBlocklet(did);
678
757
  if (!state) {
679
758
  return {
680
759
  hasBlocklet: false,
681
760
  };
682
761
  }
762
+
683
763
  if (state.source === BlockletSource.local) {
684
764
  throw new Error(`Blocklet ${state.meta.name} is already deployed from local, can not deployed from remote.`);
685
765
  }
686
- const { name, version } = state.meta;
687
- const installDir = path.join(this.installDir, name, version);
688
- // eslint-disable-next-line no-param-reassign
689
- clientFiles = clientFiles.reduce((obj, item) => {
690
- obj[item.file] = item.hash;
691
- return obj;
692
- }, {});
693
766
 
694
- const { files } = await hashFiles(installDir, {
695
- filter: (x) => x.indexOf('node_modules') === -1,
696
- concurrentHash: 1,
697
- });
698
- logger.info('Get files hash', { filesNum: Object.keys(files).length });
699
-
700
- const addSet = [];
701
- const changeSet = [];
702
- const deleteSet = [];
703
- const diffFiles = diff(files, clientFiles);
704
- if (diffFiles) {
705
- diffFiles.forEach((item) => {
706
- if (item.kind === 'D') {
707
- deleteSet.push(item.path[0]);
708
- }
709
- if (item.kind === 'E') {
710
- changeSet.push(item.path[0]);
711
- }
712
- if (item.kind === 'N') {
713
- addSet.push(item.path[0]);
714
- }
715
- });
716
- }
767
+ const { version } = state.meta;
768
+ const bundleDir = getBundleDir(this.installDir, state.meta);
769
+ const { addSet, changeSet, deleteSet } = await getDiffFiles(clientFiles, bundleDir);
770
+
717
771
  logger.info('Diff files', {
718
772
  name: state.meta.name,
719
773
  did: state.meta.did,
@@ -722,6 +776,7 @@ class BlockletManager extends BaseBlockletManager {
722
776
  changeNum: changeSet.length,
723
777
  deleteNum: deleteSet.length,
724
778
  });
779
+
725
780
  return {
726
781
  hasBlocklet: true,
727
782
  version,
@@ -733,10 +788,20 @@ class BlockletManager extends BaseBlockletManager {
733
788
 
734
789
  async checkChildrenForUpdates({ did }) {
735
790
  const blocklet = await states.blocklet.getBlocklet(did);
736
- const childrenMeta = await getChildrenMeta(blocklet.meta);
791
+ const newStaticChildren = await parseChildren(blocklet.meta);
792
+
793
+ const oldDynamicChildren = await this._getDynamicChildrenFromSettings(did);
794
+ const noneSourceUrlChildren = oldDynamicChildren.filter((x) => !x.sourceUrl);
795
+ const dynamicConfig = oldDynamicChildren
796
+ .filter((x) => x.sourceUrl)
797
+ .map((x) => ({ resolved: x.sourceUrl, name: x.meta.name, mountPoint: x.mountPoint }));
798
+ const newDynamicChildren = [...noneSourceUrlChildren, ...(await parseChildren(dynamicConfig, { dynamic: true }))];
799
+
800
+ checkDuplicateComponents(newDynamicChildren, newStaticChildren);
801
+
737
802
  const updateList = getUpdateMetaList(
738
- blocklet.children.map((x) => x.meta),
739
- childrenMeta
803
+ [...blocklet.children.map((x) => x.meta), ...oldDynamicChildren.map((x) => x.meta)],
804
+ [...newStaticChildren.map((x) => x.meta), ...newDynamicChildren.map((x) => x.meta)]
740
805
  );
741
806
 
742
807
  if (!updateList.length) {
@@ -746,7 +811,8 @@ class BlockletManager extends BaseBlockletManager {
746
811
  // start session
747
812
  const { id: updateId } = await states.session.start({
748
813
  did,
749
- childrenMeta,
814
+ staticChildren: newStaticChildren,
815
+ dynamicChildren: newDynamicChildren,
750
816
  });
751
817
 
752
818
  return {
@@ -755,11 +821,23 @@ class BlockletManager extends BaseBlockletManager {
755
821
  };
756
822
  }
757
823
 
758
- async updateChildren({ updateId }, context) {
759
- const { did, childrenMeta } = await states.session.end(updateId);
824
+ async updateChildren({ updateId, did: inputDid, children: inputChildren, oldBlocklet: inputOldBlocklet }, context) {
825
+ let did;
826
+ let children;
827
+ let oldBlocklet;
828
+ if (!updateId && inputDid && inputChildren) {
829
+ did = inputDid;
830
+ children = inputChildren;
831
+ oldBlocklet = inputOldBlocklet;
832
+ } else {
833
+ const sessionData = await states.session.end(updateId);
834
+ did = sessionData.did;
835
+ const { staticChildren = [], dynamicChildren = [] } = sessionData;
836
+ children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
837
+ oldBlocklet = await states.blocklet.getBlocklet(did);
838
+ }
760
839
 
761
840
  // get old blocklet
762
- const oldBlocklet = await states.blocklet.getBlocklet(did);
763
841
  const { meta } = oldBlocklet;
764
842
  const { name, version } = meta;
765
843
 
@@ -769,14 +847,13 @@ class BlockletManager extends BaseBlockletManager {
769
847
  did,
770
848
  name,
771
849
  version,
772
- children: childrenMeta.map((x) => ({ name: x.name, version: x.version })),
850
+ children: children.map((x) => ({ name: x.meta.name, version: x.meta.version })),
773
851
  });
774
852
 
775
853
  // new blocklet
776
854
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
777
- mergeMeta(meta, childrenMeta);
778
- newBlocklet.meta = meta;
779
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
855
+
856
+ newBlocklet.children = children;
780
857
  await validateBlocklet(newBlocklet);
781
858
 
782
859
  this.emit(BlockletEvents.statusChange, newBlocklet);
@@ -814,6 +891,11 @@ class BlockletManager extends BaseBlockletManager {
814
891
  // ============================================================================================
815
892
  // Internal API that are used by public APIs and called from CLI
816
893
  // ============================================================================================
894
+
895
+ /**
896
+ * After the dev function finished, the caller should send a BlockletEvents.deployed event to the daemon
897
+ * @returns {Object} blocklet
898
+ */
817
899
  async dev(folder) {
818
900
  logger.info('dev blocklet', { folder });
819
901
 
@@ -846,30 +928,33 @@ class BlockletManager extends BaseBlockletManager {
846
928
  await this.deleteProcess({ did });
847
929
  logger.info('delete blocklet precess for dev', { did, version });
848
930
  } catch (err) {
849
- logger.error('failed to delete blocklet process for dev', { error: err });
931
+ if (process.env.NODE_ENV !== 'development') {
932
+ logger.error('failed to delete blocklet process for dev', { error: err });
933
+ }
850
934
  }
851
935
 
852
- const childrenMeta = await getChildrenMeta(meta);
853
- mergeMeta(meta, childrenMeta);
854
- const blocklet = await states.blocklet.addBlocklet({
936
+ const children = await this._getChildren(meta);
937
+ const added = await states.blocklet.addBlocklet({
855
938
  did,
856
939
  meta,
857
940
  source: BlockletSource.local,
858
941
  deployedFrom: folder,
859
942
  mode: BLOCKLET_MODES.DEVELOPMENT,
860
- childrenMeta,
943
+ children,
861
944
  });
862
945
  logger.info('add blocklet for dev', { did, version, meta });
863
946
 
864
- await this._downloadBlocklet(blocklet);
947
+ const oldBlocklet = { children: children.filter((x) => x.dynamic) }; // let downloader skip re-downloading dynamic blocklet
948
+ await this._downloadBlocklet(added, oldBlocklet);
865
949
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
866
950
 
867
951
  // Add environments
868
952
  await this._setConfigs(did);
869
953
  await this.updateBlockletEnvironment(did);
870
954
 
871
- this.emit(BlockletEvents.deployed, { blocklet, context: {} });
872
- return this.ensureBlocklet(did);
955
+ const blocklet = await this.ensureBlocklet(did);
956
+
957
+ return blocklet;
873
958
  }
874
959
 
875
960
  async ensureBlocklet(did) {
@@ -1004,7 +1089,8 @@ class BlockletManager extends BaseBlockletManager {
1004
1089
  blocklet.diskInfo = await getDiskInfo(blocklet, { useFakeDiskInfo: !diskInfo });
1005
1090
 
1006
1091
  try {
1007
- const { appId } = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1092
+ const app = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1093
+ const { appId } = app;
1008
1094
  blocklet.runtimeInfo = await getRuntimeInfo(appId);
1009
1095
  if (blocklet.runtimeInfo.status && shouldUpdateBlockletStatus(blocklet.status)) {
1010
1096
  blocklet.status = statusMap[blocklet.runtimeInfo.status];
@@ -1097,9 +1183,6 @@ class BlockletManager extends BaseBlockletManager {
1097
1183
  return;
1098
1184
  }
1099
1185
 
1100
- const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.downloading);
1101
- this.emit(BlockletEvents.statusChange, blocklet1);
1102
-
1103
1186
  preDownloadLock.release();
1104
1187
 
1105
1188
  const { isCancelled } = await this._downloadBlocklet(blocklet, oldBlocklet);
@@ -1245,14 +1328,15 @@ class BlockletManager extends BaseBlockletManager {
1245
1328
  const blocklet = await states.blocklet.getBlocklet(did);
1246
1329
  const nodeInfo = await states.node.read();
1247
1330
 
1248
- const rootSystemEnvironments = getRootSystemEnvironments(blockletWithEnv, nodeInfo);
1249
- const overwrittenEnvironments = getOverwrittenEnvironments(blockletWithEnv, nodeInfo);
1331
+ const rootSystemEnvironments = {
1332
+ ...getRootSystemEnvironments(blockletWithEnv, nodeInfo),
1333
+ ...getOverwrittenEnvironments(blockletWithEnv, nodeInfo),
1334
+ };
1250
1335
 
1251
1336
  // fill environments to blocklet and blocklet.children
1252
1337
  blocklet.environments = formatEnvironments({
1253
1338
  ...getSystemEnvironments(blockletWithEnv),
1254
1339
  ...rootSystemEnvironments,
1255
- ...overwrittenEnvironments,
1256
1340
  });
1257
1341
 
1258
1342
  for (const child of blocklet.children) {
@@ -1261,11 +1345,13 @@ class BlockletManager extends BaseBlockletManager {
1261
1345
  child.environments = formatEnvironments({
1262
1346
  ...getSystemEnvironments(childWithEnv), // system env of child blocklet
1263
1347
  ...rootSystemEnvironments, // system env of root blocklet
1264
- ...overwrittenEnvironments,
1265
1348
  });
1266
1349
  }
1267
1350
  }
1268
1351
 
1352
+ // put BLOCKLET_APP_ID at root level for indexing
1353
+ blocklet.appDid = rootSystemEnvironments.BLOCKLET_APP_ID;
1354
+
1269
1355
  // update state to db
1270
1356
  return states.blocklet.updateBlocklet(did, blocklet);
1271
1357
  }
@@ -1278,7 +1364,7 @@ class BlockletManager extends BaseBlockletManager {
1278
1364
  }
1279
1365
  }
1280
1366
 
1281
- async _installFromRegistry({ did, registry }, context) {
1367
+ async _installFromStore({ did, registry }, context) {
1282
1368
  logger.debug('start install blocklet', { did });
1283
1369
  if (!isValidDid(did)) {
1284
1370
  throw new Error('Blocklet did is invalid');
@@ -1340,9 +1426,92 @@ class BlockletManager extends BaseBlockletManager {
1340
1426
  });
1341
1427
  }
1342
1428
 
1343
- async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1344
- logger.info('install blocklet', { from: 'upload file' });
1345
- // download
1429
+ async _installComponentFromUrl({ rootDid, mountPoint, url, context }) {
1430
+ const blocklet = await states.blocklet.getBlocklet(rootDid);
1431
+ if (!blocklet) {
1432
+ throw new Error('Root blocklet does not exist');
1433
+ }
1434
+
1435
+ const meta = await getBlockletMetaFromUrl(url);
1436
+
1437
+ if (meta.did === rootDid) {
1438
+ throw new Error('Cannot add self as a component');
1439
+ }
1440
+
1441
+ if (!isComponentBlocklet(meta)) {
1442
+ throw new Error('The blocklet cannot be a component');
1443
+ }
1444
+
1445
+ const newChildren = await parseChildren(blocklet.meta, {
1446
+ children: [
1447
+ {
1448
+ meta,
1449
+ mountPoint,
1450
+ sourceUrl: url,
1451
+ },
1452
+ ],
1453
+ dynamic: true,
1454
+ });
1455
+
1456
+ checkDuplicateComponents(blocklet.children, newChildren);
1457
+
1458
+ // add component to db
1459
+ await states.blocklet.addChildren(rootDid, newChildren);
1460
+
1461
+ return this.updateChildren(
1462
+ {
1463
+ did: rootDid,
1464
+ children: [...blocklet.children, ...newChildren],
1465
+ oldBlocklet: blocklet,
1466
+ },
1467
+ context
1468
+ );
1469
+ }
1470
+
1471
+ async _installFromCreate({ title, description }, context = {}) {
1472
+ logger.debug('create blocklet', { title, description });
1473
+
1474
+ await joi.string().label('title').max(20).required().validateAsync(title);
1475
+ await joi.string().label('description').max(80).required().validateAsync(description);
1476
+
1477
+ const { did, name } = await this._findNextCustomBlockletName();
1478
+ const rawMeta = {
1479
+ name,
1480
+ did,
1481
+ title,
1482
+ description,
1483
+ version: BLOCKLET_DEFAULT_VERSION,
1484
+ group: BlockletGroup.gateway,
1485
+ interfaces: [
1486
+ {
1487
+ type: BLOCKLET_INTERFACE_TYPE_WEB,
1488
+ name: BLOCKLET_INTERFACE_PUBLIC,
1489
+ path: BLOCKLET_DEFAULT_PATH_REWRITE,
1490
+ prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
1491
+ port: BLOCKLET_DEFAULT_PORT_NAME,
1492
+ protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
1493
+ },
1494
+ ],
1495
+ specVersion: BLOCKLET_LATEST_SPEC_VERSION,
1496
+ };
1497
+ const meta = validateMeta(rawMeta);
1498
+
1499
+ await states.blocklet.addBlocklet({
1500
+ did: meta.did,
1501
+ meta,
1502
+ source: BlockletSource.custom,
1503
+ });
1504
+ await this._setConfigs(did);
1505
+
1506
+ // fake install bundle
1507
+ const bundleDir = getBundleDir(this.installDir, meta);
1508
+ fs.mkdirSync(bundleDir, { recursive: true });
1509
+ updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
1510
+
1511
+ return this._installBlocklet({ did, context });
1512
+ }
1513
+
1514
+ async _downloadFromUpload(file) {
1346
1515
  // const { filename, mimetype, encoding, createReadStream } = await file;
1347
1516
  const { filename, createReadStream } = await file;
1348
1517
  const cwd = path.join(this.dataDirs.tmp, 'download');
@@ -1366,6 +1535,13 @@ class BlockletManager extends BaseBlockletManager {
1366
1535
  writeStream.on('finish', resolve);
1367
1536
  });
1368
1537
 
1538
+ return { cwd, tarFile };
1539
+ }
1540
+
1541
+ async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1542
+ logger.info('install blocklet', { from: 'upload file' });
1543
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1544
+
1369
1545
  // diff deploy
1370
1546
  if (did && diffVersion) {
1371
1547
  const oldBlocklet = await states.blocklet.getBlocklet(did);
@@ -1386,14 +1562,12 @@ class BlockletManager extends BaseBlockletManager {
1386
1562
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1387
1563
  }
1388
1564
 
1389
- const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet);
1390
- const childrenMeta = await getChildrenMeta(meta);
1391
- mergeMeta(meta, childrenMeta);
1565
+ const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet.meta);
1392
1566
  const newBlocklet = await states.blocklet.getBlocklet(did);
1393
1567
  newBlocklet.meta = meta;
1394
1568
  newBlocklet.source = BlockletSource.upload;
1395
1569
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1396
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1570
+ newBlocklet.children = await this._getChildren(meta);
1397
1571
  await validateBlocklet(newBlocklet);
1398
1572
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
1399
1573
 
@@ -1408,19 +1582,18 @@ class BlockletManager extends BaseBlockletManager {
1408
1582
  const { meta } = await this._resolveDownload(cwd, tarFile);
1409
1583
  const oldBlocklet = await states.blocklet.getBlocklet(meta.did);
1410
1584
 
1585
+ // full deploy - upgrade
1411
1586
  if (oldBlocklet) {
1412
1587
  if (isInProgress(oldBlocklet.status)) {
1413
1588
  logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1414
1589
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1415
1590
  }
1416
1591
 
1417
- const childrenMeta = await getChildrenMeta(meta);
1418
- mergeMeta(meta, childrenMeta);
1419
1592
  const newBlocklet = await states.blocklet.getBlocklet(meta.did);
1420
1593
  newBlocklet.meta = meta;
1421
1594
  newBlocklet.source = BlockletSource.upload;
1422
1595
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1423
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1596
+ newBlocklet.children = await this._getChildren(meta);
1424
1597
 
1425
1598
  await validateBlocklet(newBlocklet);
1426
1599
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
@@ -1432,14 +1605,14 @@ class BlockletManager extends BaseBlockletManager {
1432
1605
  });
1433
1606
  }
1434
1607
 
1435
- const childrenMeta = await getChildrenMeta(meta);
1436
- mergeMeta(meta, childrenMeta);
1608
+ // full deploy - install
1609
+ const children = await this._getChildren(meta);
1437
1610
  const blocklet = await states.blocklet.addBlocklet({
1438
1611
  did: meta.did,
1439
1612
  meta,
1440
1613
  source: BlockletSource.upload,
1441
1614
  deployedFrom: `Upload by ${context.user.did}`,
1442
- childrenMeta,
1615
+ children,
1443
1616
  });
1444
1617
 
1445
1618
  await validateBlocklet(blocklet);
@@ -1447,13 +1620,92 @@ class BlockletManager extends BaseBlockletManager {
1447
1620
  await this._setConfigs(meta.did);
1448
1621
 
1449
1622
  // download
1450
- await this._downloadBlocklet(blocklet);
1623
+ await this._downloadBlocklet(
1624
+ blocklet,
1625
+ // let downloader skip re-downloading dynamic blocklet
1626
+ { children: children.filter((x) => x.dynamic) }
1627
+ );
1451
1628
  return this._installBlocklet({
1452
1629
  did: meta.did,
1453
1630
  context,
1454
1631
  });
1455
1632
  }
1456
1633
 
1634
+ async _installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context }) {
1635
+ logger.info('install blocklet', { from: 'upload file' });
1636
+ // download
1637
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1638
+
1639
+ const oldBlocklet = await states.blocklet.getBlocklet(rootDid);
1640
+ if (!oldBlocklet) {
1641
+ throw new Error('Root blocklet does not exist');
1642
+ }
1643
+
1644
+ if (isInProgress(oldBlocklet.status)) {
1645
+ logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1646
+ throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1647
+ }
1648
+
1649
+ let meta;
1650
+ // diff upload
1651
+ if (did && diffVersion) {
1652
+ const oldChild = oldBlocklet.children.find((x) => x.meta.did === did);
1653
+ if (!oldChild) {
1654
+ throw new Error(`Blocklet ${did} not found when diff deploying`);
1655
+ }
1656
+ if (oldChild.meta.version !== diffVersion) {
1657
+ logger.error('Diff deploy: Blocklet version changed', {
1658
+ preVersion: diffVersion,
1659
+ changedVersion: oldChild.meta.version,
1660
+ name: oldChild.meta.name,
1661
+ did: oldChild.meta.did,
1662
+ });
1663
+ throw new Error('Blocklet version changed when diff deploying');
1664
+ }
1665
+
1666
+ meta = (await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldChild.meta)).meta;
1667
+ } else {
1668
+ // full deploy
1669
+ meta = (await this._resolveDownload(cwd, tarFile)).meta;
1670
+ }
1671
+
1672
+ if (meta.did === rootDid) {
1673
+ // should not be here
1674
+ throw new Error('Cannot add self as a component');
1675
+ }
1676
+
1677
+ const newBlocklet = await states.blocklet.getBlocklet(rootDid);
1678
+ const dynamicChildren = await this._getDynamicChildrenFromSettings(rootDid);
1679
+
1680
+ const newChild = {
1681
+ meta,
1682
+ mountPoint,
1683
+ source: BlockletSource.upload,
1684
+ deployedFrom: `Upload by ${context.user.did}`,
1685
+ sourceUrl: '',
1686
+ dynamic: true,
1687
+ };
1688
+ const index = dynamicChildren.findIndex((child) => child.meta.did === meta.did);
1689
+ if (index >= 0) {
1690
+ dynamicChildren.splice(index, 1, newChild);
1691
+ } else {
1692
+ dynamicChildren.push(newChild);
1693
+ }
1694
+
1695
+ const staticChildren = newBlocklet.children.filter((x) => !x.dynamic);
1696
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1697
+
1698
+ newBlocklet.children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
1699
+
1700
+ await validateBlocklet(newBlocklet);
1701
+
1702
+ return this._upgradeBlocklet({
1703
+ oldBlocklet,
1704
+ newBlocklet,
1705
+ context,
1706
+ });
1707
+ }
1708
+
1457
1709
  /**
1458
1710
  * add to download job queue
1459
1711
  * @param {string} did blocklet did
@@ -1473,22 +1725,16 @@ class BlockletManager extends BaseBlockletManager {
1473
1725
  );
1474
1726
  }
1475
1727
 
1476
- async getStatus(did) {
1477
- if (!did) {
1478
- throw new Error('did is required');
1479
- }
1480
-
1481
- const blocklet = await states.blocklet.getBlocklet(did);
1482
- if (!blocklet) {
1483
- return null;
1484
- }
1485
-
1486
- return { name: blocklet.meta.name, did: blocklet.meta.did, status: blocklet.status };
1487
- }
1488
-
1489
1728
  async prune() {
1490
1729
  const blocklets = await states.blocklet.getBlocklets();
1491
- await pruneBlockletBundle(blocklets, this.dataDirs.blocklets);
1730
+ const settings = await states.blockletExtras.listSettings();
1731
+ await pruneBlockletBundle({
1732
+ installDir: this.dataDirs.blocklets,
1733
+ blocklets,
1734
+ blockletSettings: settings
1735
+ .filter((x) => x.settings.children && x.settings.children.length)
1736
+ .map((x) => x.settings),
1737
+ });
1492
1738
  }
1493
1739
 
1494
1740
  async getLatestBlockletVersion({ did, version }) {
@@ -1576,20 +1822,35 @@ class BlockletManager extends BaseBlockletManager {
1576
1822
  blocklets.forEach(run);
1577
1823
  }
1578
1824
 
1825
+ async _getChildren(meta) {
1826
+ const staticChildren = await parseChildren(meta);
1827
+ const dynamicChildren = await parseChildren(meta, {
1828
+ children: await states.blockletExtras.getSettings(meta.did, 'children', []),
1829
+ dynamic: true,
1830
+ });
1831
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1832
+
1833
+ return [...staticChildren, ...dynamicChildren];
1834
+ }
1835
+
1836
+ async _getDynamicChildrenFromSettings(did) {
1837
+ const children = await states.blockletExtras.getSettings(did, 'children', []);
1838
+ return children.map((x) => ({ ...x, dynamic: true }));
1839
+ }
1840
+
1579
1841
  async _install({ meta, source, deployedFrom, context, sync }) {
1580
1842
  validateBlockletMeta(meta, { ensureDist: true });
1581
1843
 
1582
1844
  const { name, did, version } = meta;
1583
1845
 
1584
- const childrenMeta = await getChildrenMeta(meta);
1585
- mergeMeta(meta, childrenMeta);
1846
+ const children = await this._getChildren(meta);
1586
1847
  try {
1587
1848
  const blocklet = await states.blocklet.addBlocklet({
1588
1849
  did: meta.did,
1589
1850
  meta,
1590
1851
  source,
1591
1852
  deployedFrom,
1592
- childrenMeta,
1853
+ children,
1593
1854
  });
1594
1855
 
1595
1856
  await validateBlocklet(blocklet);
@@ -1604,6 +1865,7 @@ class BlockletManager extends BaseBlockletManager {
1604
1865
  // download
1605
1866
  const downloadParams = {
1606
1867
  blocklet: { ...blocklet1 },
1868
+ oldBlocklet: { children: children.filter((x) => x.dynamic) }, // let downloader skip re-downloading dynamic blocklet
1607
1869
  context,
1608
1870
  postAction: 'install',
1609
1871
  };
@@ -1662,7 +1924,7 @@ class BlockletManager extends BaseBlockletManager {
1662
1924
  }
1663
1925
 
1664
1926
  async _upgrade({ meta, source, deployedFrom, context, sync }) {
1665
- validateBlockletMeta(meta, { ensureDist: true });
1927
+ validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
1666
1928
 
1667
1929
  const { name, version, did } = meta;
1668
1930
 
@@ -1673,12 +1935,11 @@ class BlockletManager extends BaseBlockletManager {
1673
1935
  logger.info(`${action} blocklet`, { did, version });
1674
1936
 
1675
1937
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
1676
- const childrenMeta = await getChildrenMeta(meta);
1677
- mergeMeta(meta, childrenMeta);
1938
+
1678
1939
  newBlocklet.meta = meta;
1679
1940
  newBlocklet.source = source;
1680
1941
  newBlocklet.deployedFrom = deployedFrom;
1681
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1942
+ newBlocklet.children = await this._getChildren(meta);
1682
1943
 
1683
1944
  await validateBlocklet(newBlocklet);
1684
1945
 
@@ -1781,7 +2042,7 @@ class BlockletManager extends BaseBlockletManager {
1781
2042
 
1782
2043
  await ensureBlockletExpanded(meta, downloadDir);
1783
2044
 
1784
- installDir = path.join(this.installDir, name, version);
2045
+ installDir = getBundleDir(this.installDir, meta);
1785
2046
  if (fs.existsSync(installDir)) {
1786
2047
  fs.removeSync(installDir);
1787
2048
  logger.info('cleanup blocklet upgrade dir', { name, version, installDir });
@@ -1798,7 +2059,7 @@ class BlockletManager extends BaseBlockletManager {
1798
2059
  return { meta, installDir };
1799
2060
  }
1800
2061
 
1801
- async _resolveDiffDownload(cwd, tarFile, deleteSet, blocklet) {
2062
+ async _resolveDiffDownload(cwd, tarFile, deleteSet, bundle) {
1802
2063
  logger.info('Resolve diff download', { tarFile, cwd });
1803
2064
  const downloadDir = path.join(cwd, `${path.basename(tarFile, path.extname(tarFile))}`);
1804
2065
  const diffDir = `${downloadDir}-diff`;
@@ -1811,7 +2072,7 @@ class BlockletManager extends BaseBlockletManager {
1811
2072
  throw error;
1812
2073
  }
1813
2074
  logger.info('Copy installDir to downloadDir', { installDir: this.installDir, downloadDir });
1814
- await fs.copy(path.join(this.installDir, blocklet.meta.name, blocklet.meta.version), downloadDir);
2075
+ await fs.copy(getBundleDir(this.installDir, bundle), downloadDir);
1815
2076
  try {
1816
2077
  // delete
1817
2078
  logger.info('Delete files from downloadDir', { fileNum: deleteSet.length });
@@ -1840,11 +2101,11 @@ class BlockletManager extends BaseBlockletManager {
1840
2101
 
1841
2102
  await ensureBlockletExpanded(meta, downloadDir);
1842
2103
 
1843
- const installDir = path.join(this.installDir, meta.name, meta.version);
1844
- logger.info('Move downloadDir to installDir', { downloadDir, installDir });
1845
- await fs.move(downloadDir, installDir, { overwrite: true });
2104
+ const bundleDir = getBundleDir(this.installDir, meta);
2105
+ logger.info('Move downloadDir to installDir', { downloadDir, bundleDir });
2106
+ await fs.move(downloadDir, bundleDir, { overwrite: true });
1846
2107
 
1847
- return { meta, installDir };
2108
+ return { meta, installDir: bundleDir };
1848
2109
  } catch (error) {
1849
2110
  fs.removeSync(downloadDir);
1850
2111
  fs.removeSync(diffDir);
@@ -1872,10 +2133,10 @@ class BlockletManager extends BaseBlockletManager {
1872
2133
  // pre install
1873
2134
  const nodeEnvironments = await states.node.getEnvironments();
1874
2135
  const preInstall = (b) =>
1875
- hooks.preInstall({
2136
+ hooks.preInstall(b.env.appId, {
1876
2137
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1877
2138
  env: { ...nodeEnvironments },
1878
- appDir: blocklet.env.appDir,
2139
+ appDir: b.env.appDir,
1879
2140
  did, // root blocklet did
1880
2141
  notification: states.notification,
1881
2142
  context,
@@ -1888,10 +2149,10 @@ class BlockletManager extends BaseBlockletManager {
1888
2149
 
1889
2150
  // post install
1890
2151
  const postInstall = (b) =>
1891
- hooks.postInstall({
2152
+ hooks.postInstall(b.env.appId, {
1892
2153
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1893
2154
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1894
- appDir: blocklet.env.appDir,
2155
+ appDir: b.env.appDir,
1895
2156
  did, // root blocklet did
1896
2157
  notification: states.notification,
1897
2158
  context,
@@ -1901,17 +2162,6 @@ class BlockletManager extends BaseBlockletManager {
1901
2162
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
1902
2163
  blocklet = await this.ensureBlocklet(did);
1903
2164
  logger.info('blocklet installed', { source, did: meta.did });
1904
- if (process.env.NODE_ENV !== 'test') {
1905
- const nodeInfo = await states.node.read();
1906
- const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
1907
- const updateDidDnsResult = await updateDidDocument({
1908
- wallet: blockletInfo.wallet,
1909
- domain: nodeInfo.didDomain,
1910
- nodeDid: nodeInfo.did,
1911
- didRegistryUrl: nodeInfo.didRegistry,
1912
- });
1913
- logger.info('updated did document', { updateDidDnsResult, blockletId: blocklet.meta.did });
1914
- }
1915
2165
 
1916
2166
  this.emit(BlockletEvents.installed, { blocklet, context });
1917
2167
 
@@ -1972,10 +2222,10 @@ class BlockletManager extends BaseBlockletManager {
1972
2222
  // pre install
1973
2223
  const nodeEnvironments = await states.node.getEnvironments();
1974
2224
  const preInstall = (b) =>
1975
- hooks.preInstall({
2225
+ hooks.preInstall(b.env.appId, {
1976
2226
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1977
2227
  env: { ...nodeEnvironments },
1978
- appDir: blocklet.env.appDir,
2228
+ appDir: b.env.appDir,
1979
2229
  did, // root blocklet did
1980
2230
  notification: states.notification,
1981
2231
  context,
@@ -1988,7 +2238,7 @@ class BlockletManager extends BaseBlockletManager {
1988
2238
 
1989
2239
  // post install
1990
2240
  const postInstall = (b) =>
1991
- hooks.postInstall({
2241
+ hooks.postInstall(b.env.appId, {
1992
2242
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1993
2243
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1994
2244
  appDir: b.env.appDir,
@@ -2004,10 +2254,10 @@ class BlockletManager extends BaseBlockletManager {
2004
2254
  if (b.meta.did === did) {
2005
2255
  return runMigrationScripts({
2006
2256
  blocklet: b,
2257
+ appDir: b.env.appDir,
2258
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
2007
2259
  oldVersion,
2008
2260
  newVersion: version,
2009
- env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
2010
- appDir: b.env.appDir,
2011
2261
  did: b.meta.did,
2012
2262
  notification: states.notification,
2013
2263
  context,
@@ -2034,7 +2284,7 @@ class BlockletManager extends BaseBlockletManager {
2034
2284
  } else {
2035
2285
  const status =
2036
2286
  oldBlocklet.status === BlockletStatus.installed ? BlockletStatus.installed : BlockletStatus.stopped;
2037
- await states.blocklet.setBlockletStatus(did, status);
2287
+ await states.blocklet.setBlockletStatus(did, status, { children: 'all' });
2038
2288
  }
2039
2289
 
2040
2290
  blocklet = await this.ensureBlocklet(did, context);
@@ -2060,6 +2310,12 @@ class BlockletManager extends BaseBlockletManager {
2060
2310
  logger.error('emit upgrade notification failed', { name, version, error });
2061
2311
  }
2062
2312
 
2313
+ // Update dynamic component meta in blocklet settings
2314
+ const dynamicChildren = blocklet.children
2315
+ .filter((x) => x.dynamic)
2316
+ .map((x) => pick(x, ['meta', 'mountPoint', 'sourceUrl', 'source']));
2317
+ await states.blockletExtras.setSettings(did, { children: dynamicChildren });
2318
+
2063
2319
  return blocklet;
2064
2320
  } catch (err) {
2065
2321
  const b = await this._rollback(action, did, oldBlocklet);
@@ -2252,10 +2508,7 @@ class BlockletManager extends BaseBlockletManager {
2252
2508
  } = blocklet;
2253
2509
 
2254
2510
  const metas = [];
2255
- if (
2256
- ![BlockletSource.upload, BlockletSource.local].includes(blocklet.source) &&
2257
- get(oldBlocklet, 'meta.dist.integrity') !== get(blocklet, 'meta.dist.integrity')
2258
- ) {
2511
+ if (needBlockletDownload(blocklet)) {
2259
2512
  metas.push(blocklet.meta);
2260
2513
  }
2261
2514
 
@@ -2266,12 +2519,19 @@ class BlockletManager extends BaseBlockletManager {
2266
2519
 
2267
2520
  for (const child of blocklet.children) {
2268
2521
  const oldChild = oldChildren[child.meta.did];
2269
- if (!oldChild || oldChild.meta.dist.integrity !== child.meta.dist.integrity) {
2522
+ if (needBlockletDownload(child, oldChild)) {
2270
2523
  metas.push(child.meta);
2271
2524
  }
2272
2525
  }
2273
2526
 
2527
+ // update children status
2528
+ const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.downloading, {
2529
+ children: metas.map((x) => ({ did: x.did })),
2530
+ });
2531
+ this.emit(BlockletEvents.statusChange, blocklet1);
2532
+
2274
2533
  try {
2534
+ logger.info('Download Blocklet', { name, did, bundles: metas.map((x) => get(x, 'dist.tarball')) });
2275
2535
  const tasks = [];
2276
2536
  for (const meta of metas) {
2277
2537
  const url = await this.registry.resolveTarballURL({
@@ -2413,6 +2673,20 @@ class BlockletManager extends BaseBlockletManager {
2413
2673
  await states.blockletExtras.delChildConfigs(did, child.meta.did);
2414
2674
  }
2415
2675
  }
2676
+
2677
+ async _findNextCustomBlockletName(leftTimes = 10) {
2678
+ if (leftTimes <= 0) {
2679
+ throw new Error('Generate custom blocklet did too many times');
2680
+ }
2681
+ const number = await states.node.increaseCustomBlockletNumber();
2682
+ const name = `custom-${number}`;
2683
+ const did = toBlockletDid(name);
2684
+ const blocklet = await states.blocklet.getBlocklet(did);
2685
+ if (blocklet) {
2686
+ return this._findNextCustomBlockletName(leftTimes - 1);
2687
+ }
2688
+ return { did, name };
2689
+ }
2416
2690
  }
2417
2691
 
2418
2692
  module.exports = BlockletManager;