@abtnode/core 1.6.15 → 1.6.19

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,30 +3,37 @@
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');
15
- const { toBase58 } = require('@ocap/util');
16
+ 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
@@ -189,9 +232,9 @@ class BlockletManager extends BaseBlockletManager {
189
232
  }
190
233
  }
191
234
 
192
- meta.isFree = isFree;
235
+ const blocklet = await states.blocklet.getBlocklet(meta.did);
193
236
 
194
- return meta;
237
+ return { meta, isFree, isInstalled: !!blocklet };
195
238
  }
196
239
 
197
240
  async installBlockletFromVc({ vcPresentation, challenge }, context) {
@@ -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
 
@@ -549,19 +615,25 @@ class BlockletManager extends BaseBlockletManager {
549
615
  }
550
616
  }
551
617
 
618
+ if (x.key === 'BLOCKLET_PASSPORT_COLOR') {
619
+ if (x.value && x.value !== 'auto') {
620
+ if (x.value.length !== 7 || !isHex(x.value.slice(-6))) {
621
+ throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
622
+ }
623
+ }
624
+ }
625
+
552
626
  blocklet.configObj[x.key] = x.value;
553
627
  }
554
628
 
555
629
  // FIXME: we should also call preConfig for child blocklets
556
- await hooks.preConfig({
630
+ await hooks.preConfig(blocklet.env.appId, {
557
631
  appDir: blocklet.env.appDir,
558
632
  hooks: Object.assign(blocklet.meta.hooks || {}, blocklet.meta.scripts || {}),
559
633
  exitOnError: true,
560
634
  env: { ...getRuntimeEnvironments(blocklet, nodeEnvironments), ...blocklet.configObj },
561
- notification: states.notification,
562
635
  did,
563
636
  context,
564
- progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
565
637
  });
566
638
 
567
639
  // update db
@@ -657,56 +729,45 @@ class BlockletManager extends BaseBlockletManager {
657
729
  }
658
730
 
659
731
  // eslint-disable-next-line no-unused-vars
660
- async diff({ did, hashFiles: clientFiles }, context) {
732
+ async diff({ did, hashFiles: clientFiles, rootDid: inputRootDid }, context) {
661
733
  if (!did) {
662
734
  throw new Error('did is empty');
663
735
  }
736
+
664
737
  if (!clientFiles || !clientFiles.length) {
665
738
  throw new Error('hashFiles is empty');
666
739
  }
667
740
 
668
- 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;
669
756
 
670
- const state = await states.blocklet.getBlocklet(did);
671
757
  if (!state) {
672
758
  return {
673
759
  hasBlocklet: false,
674
760
  };
675
761
  }
762
+
676
763
  if (state.source === BlockletSource.local) {
677
764
  throw new Error(`Blocklet ${state.meta.name} is already deployed from local, can not deployed from remote.`);
678
765
  }
679
- const { name, version } = state.meta;
680
- const installDir = path.join(this.installDir, name, version);
681
- // eslint-disable-next-line no-param-reassign
682
- clientFiles = clientFiles.reduce((obj, item) => {
683
- obj[item.file] = item.hash;
684
- return obj;
685
- }, {});
686
766
 
687
- const { files } = await hashFiles(installDir, {
688
- filter: (x) => x.indexOf('node_modules') === -1,
689
- concurrentHash: 1,
690
- });
691
- logger.info('Get files hash', { filesNum: Object.keys(files).length });
692
-
693
- const addSet = [];
694
- const changeSet = [];
695
- const deleteSet = [];
696
- const diffFiles = diff(files, clientFiles);
697
- if (diffFiles) {
698
- diffFiles.forEach((item) => {
699
- if (item.kind === 'D') {
700
- deleteSet.push(item.path[0]);
701
- }
702
- if (item.kind === 'E') {
703
- changeSet.push(item.path[0]);
704
- }
705
- if (item.kind === 'N') {
706
- addSet.push(item.path[0]);
707
- }
708
- });
709
- }
767
+ const { version } = state.meta;
768
+ const bundleDir = getBundleDir(this.installDir, state.meta);
769
+ const { addSet, changeSet, deleteSet } = await getDiffFiles(clientFiles, bundleDir);
770
+
710
771
  logger.info('Diff files', {
711
772
  name: state.meta.name,
712
773
  did: state.meta.did,
@@ -715,6 +776,7 @@ class BlockletManager extends BaseBlockletManager {
715
776
  changeNum: changeSet.length,
716
777
  deleteNum: deleteSet.length,
717
778
  });
779
+
718
780
  return {
719
781
  hasBlocklet: true,
720
782
  version,
@@ -726,10 +788,20 @@ class BlockletManager extends BaseBlockletManager {
726
788
 
727
789
  async checkChildrenForUpdates({ did }) {
728
790
  const blocklet = await states.blocklet.getBlocklet(did);
729
- 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
+
730
802
  const updateList = getUpdateMetaList(
731
- blocklet.children.map((x) => x.meta),
732
- childrenMeta
803
+ [...blocklet.children.map((x) => x.meta), ...oldDynamicChildren.map((x) => x.meta)],
804
+ [...newStaticChildren.map((x) => x.meta), ...newDynamicChildren.map((x) => x.meta)]
733
805
  );
734
806
 
735
807
  if (!updateList.length) {
@@ -739,7 +811,8 @@ class BlockletManager extends BaseBlockletManager {
739
811
  // start session
740
812
  const { id: updateId } = await states.session.start({
741
813
  did,
742
- childrenMeta,
814
+ staticChildren: newStaticChildren,
815
+ dynamicChildren: newDynamicChildren,
743
816
  });
744
817
 
745
818
  return {
@@ -748,11 +821,23 @@ class BlockletManager extends BaseBlockletManager {
748
821
  };
749
822
  }
750
823
 
751
- async updateChildren({ updateId }, context) {
752
- 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
+ }
753
839
 
754
840
  // get old blocklet
755
- const oldBlocklet = await states.blocklet.getBlocklet(did);
756
841
  const { meta } = oldBlocklet;
757
842
  const { name, version } = meta;
758
843
 
@@ -762,14 +847,13 @@ class BlockletManager extends BaseBlockletManager {
762
847
  did,
763
848
  name,
764
849
  version,
765
- children: childrenMeta.map((x) => ({ name: x.name, version: x.version })),
850
+ children: children.map((x) => ({ name: x.meta.name, version: x.meta.version })),
766
851
  });
767
852
 
768
853
  // new blocklet
769
854
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
770
- mergeMeta(meta, childrenMeta);
771
- newBlocklet.meta = meta;
772
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
855
+
856
+ newBlocklet.children = children;
773
857
  await validateBlocklet(newBlocklet);
774
858
 
775
859
  this.emit(BlockletEvents.statusChange, newBlocklet);
@@ -807,6 +891,11 @@ class BlockletManager extends BaseBlockletManager {
807
891
  // ============================================================================================
808
892
  // Internal API that are used by public APIs and called from CLI
809
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
+ */
810
899
  async dev(folder) {
811
900
  logger.info('dev blocklet', { folder });
812
901
 
@@ -839,30 +928,33 @@ class BlockletManager extends BaseBlockletManager {
839
928
  await this.deleteProcess({ did });
840
929
  logger.info('delete blocklet precess for dev', { did, version });
841
930
  } catch (err) {
842
- 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
+ }
843
934
  }
844
935
 
845
- const childrenMeta = await getChildrenMeta(meta);
846
- mergeMeta(meta, childrenMeta);
847
- const blocklet = await states.blocklet.addBlocklet({
936
+ const children = await this._getChildren(meta);
937
+ const added = await states.blocklet.addBlocklet({
848
938
  did,
849
939
  meta,
850
940
  source: BlockletSource.local,
851
941
  deployedFrom: folder,
852
942
  mode: BLOCKLET_MODES.DEVELOPMENT,
853
- childrenMeta,
943
+ children,
854
944
  });
855
945
  logger.info('add blocklet for dev', { did, version, meta });
856
946
 
857
- 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);
858
949
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
859
950
 
860
951
  // Add environments
861
952
  await this._setConfigs(did);
862
953
  await this.updateBlockletEnvironment(did);
863
954
 
864
- this.emit(BlockletEvents.deployed, { blocklet, context: {} });
865
- return this.ensureBlocklet(did);
955
+ const blocklet = await this.ensureBlocklet(did);
956
+
957
+ return blocklet;
866
958
  }
867
959
 
868
960
  async ensureBlocklet(did) {
@@ -997,7 +1089,8 @@ class BlockletManager extends BaseBlockletManager {
997
1089
  blocklet.diskInfo = await getDiskInfo(blocklet, { useFakeDiskInfo: !diskInfo });
998
1090
 
999
1091
  try {
1000
- 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;
1001
1094
  blocklet.runtimeInfo = await getRuntimeInfo(appId);
1002
1095
  if (blocklet.runtimeInfo.status && shouldUpdateBlockletStatus(blocklet.status)) {
1003
1096
  blocklet.status = statusMap[blocklet.runtimeInfo.status];
@@ -1090,9 +1183,6 @@ class BlockletManager extends BaseBlockletManager {
1090
1183
  return;
1091
1184
  }
1092
1185
 
1093
- const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.downloading);
1094
- this.emit(BlockletEvents.statusChange, blocklet1);
1095
-
1096
1186
  preDownloadLock.release();
1097
1187
 
1098
1188
  const { isCancelled } = await this._downloadBlocklet(blocklet, oldBlocklet);
@@ -1271,7 +1361,7 @@ class BlockletManager extends BaseBlockletManager {
1271
1361
  }
1272
1362
  }
1273
1363
 
1274
- async _installFromRegistry({ did, registry }, context) {
1364
+ async _installFromStore({ did, registry }, context) {
1275
1365
  logger.debug('start install blocklet', { did });
1276
1366
  if (!isValidDid(did)) {
1277
1367
  throw new Error('Blocklet did is invalid');
@@ -1333,9 +1423,92 @@ class BlockletManager extends BaseBlockletManager {
1333
1423
  });
1334
1424
  }
1335
1425
 
1336
- async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1337
- logger.info('install blocklet', { from: 'upload file' });
1338
- // download
1426
+ async _installComponentFromUrl({ rootDid, mountPoint, url, context }) {
1427
+ const blocklet = await states.blocklet.getBlocklet(rootDid);
1428
+ if (!blocklet) {
1429
+ throw new Error('Root blocklet does not exist');
1430
+ }
1431
+
1432
+ const meta = await getBlockletMetaFromUrl(url);
1433
+
1434
+ if (meta.did === rootDid) {
1435
+ throw new Error('Cannot add self as a component');
1436
+ }
1437
+
1438
+ if (!isComponentBlocklet(meta)) {
1439
+ throw new Error('The blocklet cannot be a component');
1440
+ }
1441
+
1442
+ const newChildren = await parseChildren(blocklet.meta, {
1443
+ children: [
1444
+ {
1445
+ meta,
1446
+ mountPoint,
1447
+ sourceUrl: url,
1448
+ },
1449
+ ],
1450
+ dynamic: true,
1451
+ });
1452
+
1453
+ checkDuplicateComponents(blocklet.children, newChildren);
1454
+
1455
+ // add component to db
1456
+ await states.blocklet.addChildren(rootDid, newChildren);
1457
+
1458
+ return this.updateChildren(
1459
+ {
1460
+ did: rootDid,
1461
+ children: [...blocklet.children, ...newChildren],
1462
+ oldBlocklet: blocklet,
1463
+ },
1464
+ context
1465
+ );
1466
+ }
1467
+
1468
+ async _installFromCreate({ title, description }, context = {}) {
1469
+ logger.debug('create blocklet', { title, description });
1470
+
1471
+ await joi.string().label('title').max(20).required().validateAsync(title);
1472
+ await joi.string().label('description').max(80).required().validateAsync(description);
1473
+
1474
+ const { did, name } = await this._findNextCustomBlockletName();
1475
+ const rawMeta = {
1476
+ name,
1477
+ did,
1478
+ title,
1479
+ description,
1480
+ version: BLOCKLET_DEFAULT_VERSION,
1481
+ group: BlockletGroup.gateway,
1482
+ interfaces: [
1483
+ {
1484
+ type: BLOCKLET_INTERFACE_TYPE_WEB,
1485
+ name: BLOCKLET_INTERFACE_PUBLIC,
1486
+ path: BLOCKLET_DEFAULT_PATH_REWRITE,
1487
+ prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
1488
+ port: BLOCKLET_DEFAULT_PORT_NAME,
1489
+ protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
1490
+ },
1491
+ ],
1492
+ specVersion: BLOCKLET_LATEST_SPEC_VERSION,
1493
+ };
1494
+ const meta = validateMeta(rawMeta);
1495
+
1496
+ await states.blocklet.addBlocklet({
1497
+ did: meta.did,
1498
+ meta,
1499
+ source: BlockletSource.custom,
1500
+ });
1501
+ await this._setConfigs(did);
1502
+
1503
+ // fake install bundle
1504
+ const bundleDir = getBundleDir(this.installDir, meta);
1505
+ fs.mkdirSync(bundleDir, { recursive: true });
1506
+ updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
1507
+
1508
+ return this._installBlocklet({ did, context });
1509
+ }
1510
+
1511
+ async _downloadFromUpload(file) {
1339
1512
  // const { filename, mimetype, encoding, createReadStream } = await file;
1340
1513
  const { filename, createReadStream } = await file;
1341
1514
  const cwd = path.join(this.dataDirs.tmp, 'download');
@@ -1359,6 +1532,13 @@ class BlockletManager extends BaseBlockletManager {
1359
1532
  writeStream.on('finish', resolve);
1360
1533
  });
1361
1534
 
1535
+ return { cwd, tarFile };
1536
+ }
1537
+
1538
+ async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1539
+ logger.info('install blocklet', { from: 'upload file' });
1540
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1541
+
1362
1542
  // diff deploy
1363
1543
  if (did && diffVersion) {
1364
1544
  const oldBlocklet = await states.blocklet.getBlocklet(did);
@@ -1379,14 +1559,12 @@ class BlockletManager extends BaseBlockletManager {
1379
1559
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1380
1560
  }
1381
1561
 
1382
- const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet);
1383
- const childrenMeta = await getChildrenMeta(meta);
1384
- mergeMeta(meta, childrenMeta);
1562
+ const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet.meta);
1385
1563
  const newBlocklet = await states.blocklet.getBlocklet(did);
1386
1564
  newBlocklet.meta = meta;
1387
1565
  newBlocklet.source = BlockletSource.upload;
1388
1566
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1389
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1567
+ newBlocklet.children = await this._getChildren(meta);
1390
1568
  await validateBlocklet(newBlocklet);
1391
1569
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
1392
1570
 
@@ -1401,19 +1579,18 @@ class BlockletManager extends BaseBlockletManager {
1401
1579
  const { meta } = await this._resolveDownload(cwd, tarFile);
1402
1580
  const oldBlocklet = await states.blocklet.getBlocklet(meta.did);
1403
1581
 
1582
+ // full deploy - upgrade
1404
1583
  if (oldBlocklet) {
1405
1584
  if (isInProgress(oldBlocklet.status)) {
1406
1585
  logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1407
1586
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1408
1587
  }
1409
1588
 
1410
- const childrenMeta = await getChildrenMeta(meta);
1411
- mergeMeta(meta, childrenMeta);
1412
1589
  const newBlocklet = await states.blocklet.getBlocklet(meta.did);
1413
1590
  newBlocklet.meta = meta;
1414
1591
  newBlocklet.source = BlockletSource.upload;
1415
1592
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1416
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1593
+ newBlocklet.children = await this._getChildren(meta);
1417
1594
 
1418
1595
  await validateBlocklet(newBlocklet);
1419
1596
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
@@ -1425,14 +1602,14 @@ class BlockletManager extends BaseBlockletManager {
1425
1602
  });
1426
1603
  }
1427
1604
 
1428
- const childrenMeta = await getChildrenMeta(meta);
1429
- mergeMeta(meta, childrenMeta);
1605
+ // full deploy - install
1606
+ const children = await this._getChildren(meta);
1430
1607
  const blocklet = await states.blocklet.addBlocklet({
1431
1608
  did: meta.did,
1432
1609
  meta,
1433
1610
  source: BlockletSource.upload,
1434
1611
  deployedFrom: `Upload by ${context.user.did}`,
1435
- childrenMeta,
1612
+ children,
1436
1613
  });
1437
1614
 
1438
1615
  await validateBlocklet(blocklet);
@@ -1440,13 +1617,92 @@ class BlockletManager extends BaseBlockletManager {
1440
1617
  await this._setConfigs(meta.did);
1441
1618
 
1442
1619
  // download
1443
- await this._downloadBlocklet(blocklet);
1620
+ await this._downloadBlocklet(
1621
+ blocklet,
1622
+ // let downloader skip re-downloading dynamic blocklet
1623
+ { children: children.filter((x) => x.dynamic) }
1624
+ );
1444
1625
  return this._installBlocklet({
1445
1626
  did: meta.did,
1446
1627
  context,
1447
1628
  });
1448
1629
  }
1449
1630
 
1631
+ async _installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context }) {
1632
+ logger.info('install blocklet', { from: 'upload file' });
1633
+ // download
1634
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1635
+
1636
+ const oldBlocklet = await states.blocklet.getBlocklet(rootDid);
1637
+ if (!oldBlocklet) {
1638
+ throw new Error('Root blocklet does not exist');
1639
+ }
1640
+
1641
+ if (isInProgress(oldBlocklet.status)) {
1642
+ logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1643
+ throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1644
+ }
1645
+
1646
+ let meta;
1647
+ // diff upload
1648
+ if (did && diffVersion) {
1649
+ const oldChild = oldBlocklet.children.find((x) => x.meta.did === did);
1650
+ if (!oldChild) {
1651
+ throw new Error(`Blocklet ${did} not found when diff deploying`);
1652
+ }
1653
+ if (oldChild.meta.version !== diffVersion) {
1654
+ logger.error('Diff deploy: Blocklet version changed', {
1655
+ preVersion: diffVersion,
1656
+ changedVersion: oldChild.meta.version,
1657
+ name: oldChild.meta.name,
1658
+ did: oldChild.meta.did,
1659
+ });
1660
+ throw new Error('Blocklet version changed when diff deploying');
1661
+ }
1662
+
1663
+ meta = (await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldChild.meta)).meta;
1664
+ } else {
1665
+ // full deploy
1666
+ meta = (await this._resolveDownload(cwd, tarFile)).meta;
1667
+ }
1668
+
1669
+ if (meta.did === rootDid) {
1670
+ // should not be here
1671
+ throw new Error('Cannot add self as a component');
1672
+ }
1673
+
1674
+ const newBlocklet = await states.blocklet.getBlocklet(rootDid);
1675
+ const dynamicChildren = await this._getDynamicChildrenFromSettings(rootDid);
1676
+
1677
+ const newChild = {
1678
+ meta,
1679
+ mountPoint,
1680
+ source: BlockletSource.upload,
1681
+ deployedFrom: `Upload by ${context.user.did}`,
1682
+ sourceUrl: '',
1683
+ dynamic: true,
1684
+ };
1685
+ const index = dynamicChildren.findIndex((child) => child.meta.did === meta.did);
1686
+ if (index >= 0) {
1687
+ dynamicChildren.splice(index, 1, newChild);
1688
+ } else {
1689
+ dynamicChildren.push(newChild);
1690
+ }
1691
+
1692
+ const staticChildren = newBlocklet.children.filter((x) => !x.dynamic);
1693
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1694
+
1695
+ newBlocklet.children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
1696
+
1697
+ await validateBlocklet(newBlocklet);
1698
+
1699
+ return this._upgradeBlocklet({
1700
+ oldBlocklet,
1701
+ newBlocklet,
1702
+ context,
1703
+ });
1704
+ }
1705
+
1450
1706
  /**
1451
1707
  * add to download job queue
1452
1708
  * @param {string} did blocklet did
@@ -1466,22 +1722,16 @@ class BlockletManager extends BaseBlockletManager {
1466
1722
  );
1467
1723
  }
1468
1724
 
1469
- async getStatus(did) {
1470
- if (!did) {
1471
- throw new Error('did is required');
1472
- }
1473
-
1474
- const blocklet = await states.blocklet.getBlocklet(did);
1475
- if (!blocklet) {
1476
- return null;
1477
- }
1478
-
1479
- return { name: blocklet.meta.name, did: blocklet.meta.did, status: blocklet.status };
1480
- }
1481
-
1482
1725
  async prune() {
1483
1726
  const blocklets = await states.blocklet.getBlocklets();
1484
- await pruneBlockletBundle(blocklets, this.dataDirs.blocklets);
1727
+ const settings = await states.blockletExtras.listSettings();
1728
+ await pruneBlockletBundle({
1729
+ installDir: this.dataDirs.blocklets,
1730
+ blocklets,
1731
+ blockletSettings: settings
1732
+ .filter((x) => x.settings.children && x.settings.children.length)
1733
+ .map((x) => x.settings),
1734
+ });
1485
1735
  }
1486
1736
 
1487
1737
  async getLatestBlockletVersion({ did, version }) {
@@ -1569,20 +1819,35 @@ class BlockletManager extends BaseBlockletManager {
1569
1819
  blocklets.forEach(run);
1570
1820
  }
1571
1821
 
1822
+ async _getChildren(meta) {
1823
+ const staticChildren = await parseChildren(meta);
1824
+ const dynamicChildren = await parseChildren(meta, {
1825
+ children: await states.blockletExtras.getSettings(meta.did, 'children', []),
1826
+ dynamic: true,
1827
+ });
1828
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1829
+
1830
+ return [...staticChildren, ...dynamicChildren];
1831
+ }
1832
+
1833
+ async _getDynamicChildrenFromSettings(did) {
1834
+ const children = await states.blockletExtras.getSettings(did, 'children', []);
1835
+ return children.map((x) => ({ ...x, dynamic: true }));
1836
+ }
1837
+
1572
1838
  async _install({ meta, source, deployedFrom, context, sync }) {
1573
1839
  validateBlockletMeta(meta, { ensureDist: true });
1574
1840
 
1575
1841
  const { name, did, version } = meta;
1576
1842
 
1577
- const childrenMeta = await getChildrenMeta(meta);
1578
- mergeMeta(meta, childrenMeta);
1843
+ const children = await this._getChildren(meta);
1579
1844
  try {
1580
1845
  const blocklet = await states.blocklet.addBlocklet({
1581
1846
  did: meta.did,
1582
1847
  meta,
1583
1848
  source,
1584
1849
  deployedFrom,
1585
- childrenMeta,
1850
+ children,
1586
1851
  });
1587
1852
 
1588
1853
  await validateBlocklet(blocklet);
@@ -1597,6 +1862,7 @@ class BlockletManager extends BaseBlockletManager {
1597
1862
  // download
1598
1863
  const downloadParams = {
1599
1864
  blocklet: { ...blocklet1 },
1865
+ oldBlocklet: { children: children.filter((x) => x.dynamic) }, // let downloader skip re-downloading dynamic blocklet
1600
1866
  context,
1601
1867
  postAction: 'install',
1602
1868
  };
@@ -1655,7 +1921,7 @@ class BlockletManager extends BaseBlockletManager {
1655
1921
  }
1656
1922
 
1657
1923
  async _upgrade({ meta, source, deployedFrom, context, sync }) {
1658
- validateBlockletMeta(meta, { ensureDist: true });
1924
+ validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
1659
1925
 
1660
1926
  const { name, version, did } = meta;
1661
1927
 
@@ -1666,12 +1932,11 @@ class BlockletManager extends BaseBlockletManager {
1666
1932
  logger.info(`${action} blocklet`, { did, version });
1667
1933
 
1668
1934
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
1669
- const childrenMeta = await getChildrenMeta(meta);
1670
- mergeMeta(meta, childrenMeta);
1935
+
1671
1936
  newBlocklet.meta = meta;
1672
1937
  newBlocklet.source = source;
1673
1938
  newBlocklet.deployedFrom = deployedFrom;
1674
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1939
+ newBlocklet.children = await this._getChildren(meta);
1675
1940
 
1676
1941
  await validateBlocklet(newBlocklet);
1677
1942
 
@@ -1774,7 +2039,7 @@ class BlockletManager extends BaseBlockletManager {
1774
2039
 
1775
2040
  await ensureBlockletExpanded(meta, downloadDir);
1776
2041
 
1777
- installDir = path.join(this.installDir, name, version);
2042
+ installDir = getBundleDir(this.installDir, meta);
1778
2043
  if (fs.existsSync(installDir)) {
1779
2044
  fs.removeSync(installDir);
1780
2045
  logger.info('cleanup blocklet upgrade dir', { name, version, installDir });
@@ -1791,7 +2056,7 @@ class BlockletManager extends BaseBlockletManager {
1791
2056
  return { meta, installDir };
1792
2057
  }
1793
2058
 
1794
- async _resolveDiffDownload(cwd, tarFile, deleteSet, blocklet) {
2059
+ async _resolveDiffDownload(cwd, tarFile, deleteSet, bundle) {
1795
2060
  logger.info('Resolve diff download', { tarFile, cwd });
1796
2061
  const downloadDir = path.join(cwd, `${path.basename(tarFile, path.extname(tarFile))}`);
1797
2062
  const diffDir = `${downloadDir}-diff`;
@@ -1804,7 +2069,7 @@ class BlockletManager extends BaseBlockletManager {
1804
2069
  throw error;
1805
2070
  }
1806
2071
  logger.info('Copy installDir to downloadDir', { installDir: this.installDir, downloadDir });
1807
- await fs.copy(path.join(this.installDir, blocklet.meta.name, blocklet.meta.version), downloadDir);
2072
+ await fs.copy(getBundleDir(this.installDir, bundle), downloadDir);
1808
2073
  try {
1809
2074
  // delete
1810
2075
  logger.info('Delete files from downloadDir', { fileNum: deleteSet.length });
@@ -1833,11 +2098,11 @@ class BlockletManager extends BaseBlockletManager {
1833
2098
 
1834
2099
  await ensureBlockletExpanded(meta, downloadDir);
1835
2100
 
1836
- const installDir = path.join(this.installDir, meta.name, meta.version);
1837
- logger.info('Move downloadDir to installDir', { downloadDir, installDir });
1838
- await fs.move(downloadDir, installDir, { overwrite: true });
2101
+ const bundleDir = getBundleDir(this.installDir, meta);
2102
+ logger.info('Move downloadDir to installDir', { downloadDir, bundleDir });
2103
+ await fs.move(downloadDir, bundleDir, { overwrite: true });
1839
2104
 
1840
- return { meta, installDir };
2105
+ return { meta, installDir: bundleDir };
1841
2106
  } catch (error) {
1842
2107
  fs.removeSync(downloadDir);
1843
2108
  fs.removeSync(diffDir);
@@ -1865,10 +2130,10 @@ class BlockletManager extends BaseBlockletManager {
1865
2130
  // pre install
1866
2131
  const nodeEnvironments = await states.node.getEnvironments();
1867
2132
  const preInstall = (b) =>
1868
- hooks.preInstall({
2133
+ hooks.preInstall(b.env.appId, {
1869
2134
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1870
2135
  env: { ...nodeEnvironments },
1871
- appDir: blocklet.env.appDir,
2136
+ appDir: b.env.appDir,
1872
2137
  did, // root blocklet did
1873
2138
  notification: states.notification,
1874
2139
  context,
@@ -1881,10 +2146,10 @@ class BlockletManager extends BaseBlockletManager {
1881
2146
 
1882
2147
  // post install
1883
2148
  const postInstall = (b) =>
1884
- hooks.postInstall({
2149
+ hooks.postInstall(b.env.appId, {
1885
2150
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1886
2151
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1887
- appDir: blocklet.env.appDir,
2152
+ appDir: b.env.appDir,
1888
2153
  did, // root blocklet did
1889
2154
  notification: states.notification,
1890
2155
  context,
@@ -1894,17 +2159,6 @@ class BlockletManager extends BaseBlockletManager {
1894
2159
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
1895
2160
  blocklet = await this.ensureBlocklet(did);
1896
2161
  logger.info('blocklet installed', { source, did: meta.did });
1897
- if (process.env.NODE_ENV !== 'test') {
1898
- const nodeInfo = await states.node.read();
1899
- const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
1900
- const updateDidDnsResult = await updateDidDocument({
1901
- wallet: blockletInfo.wallet,
1902
- domain: nodeInfo.didDomain,
1903
- nodeDid: nodeInfo.did,
1904
- didRegistryUrl: nodeInfo.didRegistry,
1905
- });
1906
- logger.info('updated did document', { updateDidDnsResult, blockletId: blocklet.meta.did });
1907
- }
1908
2162
 
1909
2163
  this.emit(BlockletEvents.installed, { blocklet, context });
1910
2164
 
@@ -1965,10 +2219,10 @@ class BlockletManager extends BaseBlockletManager {
1965
2219
  // pre install
1966
2220
  const nodeEnvironments = await states.node.getEnvironments();
1967
2221
  const preInstall = (b) =>
1968
- hooks.preInstall({
2222
+ hooks.preInstall(b.env.appId, {
1969
2223
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1970
2224
  env: { ...nodeEnvironments },
1971
- appDir: blocklet.env.appDir,
2225
+ appDir: b.env.appDir,
1972
2226
  did, // root blocklet did
1973
2227
  notification: states.notification,
1974
2228
  context,
@@ -1981,7 +2235,7 @@ class BlockletManager extends BaseBlockletManager {
1981
2235
 
1982
2236
  // post install
1983
2237
  const postInstall = (b) =>
1984
- hooks.postInstall({
2238
+ hooks.postInstall(b.env.appId, {
1985
2239
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1986
2240
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1987
2241
  appDir: b.env.appDir,
@@ -1997,10 +2251,10 @@ class BlockletManager extends BaseBlockletManager {
1997
2251
  if (b.meta.did === did) {
1998
2252
  return runMigrationScripts({
1999
2253
  blocklet: b,
2254
+ appDir: b.env.appDir,
2255
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
2000
2256
  oldVersion,
2001
2257
  newVersion: version,
2002
- env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
2003
- appDir: b.env.appDir,
2004
2258
  did: b.meta.did,
2005
2259
  notification: states.notification,
2006
2260
  context,
@@ -2027,7 +2281,7 @@ class BlockletManager extends BaseBlockletManager {
2027
2281
  } else {
2028
2282
  const status =
2029
2283
  oldBlocklet.status === BlockletStatus.installed ? BlockletStatus.installed : BlockletStatus.stopped;
2030
- await states.blocklet.setBlockletStatus(did, status);
2284
+ await states.blocklet.setBlockletStatus(did, status, { children: 'all' });
2031
2285
  }
2032
2286
 
2033
2287
  blocklet = await this.ensureBlocklet(did, context);
@@ -2053,6 +2307,12 @@ class BlockletManager extends BaseBlockletManager {
2053
2307
  logger.error('emit upgrade notification failed', { name, version, error });
2054
2308
  }
2055
2309
 
2310
+ // Update dynamic component meta in blocklet settings
2311
+ const dynamicChildren = blocklet.children
2312
+ .filter((x) => x.dynamic)
2313
+ .map((x) => pick(x, ['meta', 'mountPoint', 'sourceUrl', 'source']));
2314
+ await states.blockletExtras.setSettings(did, { children: dynamicChildren });
2315
+
2056
2316
  return blocklet;
2057
2317
  } catch (err) {
2058
2318
  const b = await this._rollback(action, did, oldBlocklet);
@@ -2245,10 +2505,7 @@ class BlockletManager extends BaseBlockletManager {
2245
2505
  } = blocklet;
2246
2506
 
2247
2507
  const metas = [];
2248
- if (
2249
- ![BlockletSource.upload, BlockletSource.local].includes(blocklet.source) &&
2250
- get(oldBlocklet, 'meta.dist.integrity') !== get(blocklet, 'meta.dist.integrity')
2251
- ) {
2508
+ if (needBlockletDownload(blocklet)) {
2252
2509
  metas.push(blocklet.meta);
2253
2510
  }
2254
2511
 
@@ -2259,12 +2516,19 @@ class BlockletManager extends BaseBlockletManager {
2259
2516
 
2260
2517
  for (const child of blocklet.children) {
2261
2518
  const oldChild = oldChildren[child.meta.did];
2262
- if (!oldChild || oldChild.meta.dist.integrity !== child.meta.dist.integrity) {
2519
+ if (needBlockletDownload(child, oldChild)) {
2263
2520
  metas.push(child.meta);
2264
2521
  }
2265
2522
  }
2266
2523
 
2524
+ // update children status
2525
+ const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.downloading, {
2526
+ children: metas.map((x) => ({ did: x.did })),
2527
+ });
2528
+ this.emit(BlockletEvents.statusChange, blocklet1);
2529
+
2267
2530
  try {
2531
+ logger.info('Download Blocklet', { name, did, bundles: metas.map((x) => get(x, 'dist.tarball')) });
2268
2532
  const tasks = [];
2269
2533
  for (const meta of metas) {
2270
2534
  const url = await this.registry.resolveTarballURL({
@@ -2406,6 +2670,20 @@ class BlockletManager extends BaseBlockletManager {
2406
2670
  await states.blockletExtras.delChildConfigs(did, child.meta.did);
2407
2671
  }
2408
2672
  }
2673
+
2674
+ async _findNextCustomBlockletName(leftTimes = 10) {
2675
+ if (leftTimes <= 0) {
2676
+ throw new Error('Generate custom blocklet did too many times');
2677
+ }
2678
+ const number = await states.node.increaseCustomBlockletNumber();
2679
+ const name = `custom-${number}`;
2680
+ const did = toBlockletDid(name);
2681
+ const blocklet = await states.blocklet.getBlocklet(did);
2682
+ if (blocklet) {
2683
+ return this._findNextCustomBlockletName(leftTimes - 1);
2684
+ }
2685
+ return { did, name };
2686
+ }
2409
2687
  }
2410
2688
 
2411
2689
  module.exports = BlockletManager;