@abtnode/core 1.6.14 → 1.6.18

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,36 @@
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
+ isDeletableBlocklet,
29
+ getRequiredMissingConfigs,
30
+ hasRunnableComponent,
31
+ } = require('@blocklet/meta/lib/util');
28
32
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
29
- const { getBlockletInfo } = require('@blocklet/meta/lib');
33
+ const toBlockletDid = require('@blocklet/meta/lib/did');
34
+ const { validateMeta } = require('@blocklet/meta/lib/validate');
35
+ const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
30
36
 
31
37
  const {
32
38
  BlockletStatus,
@@ -37,6 +43,15 @@ const {
37
43
  BlockletGroup,
38
44
  fromBlockletStatus,
39
45
  fromBlockletSource,
46
+ BLOCKLET_DEFAULT_PORT_NAME,
47
+ BLOCKLET_INTERFACE_TYPE_WEB,
48
+ BLOCKLET_INTERFACE_PUBLIC,
49
+ BLOCKLET_DYNAMIC_PATH_PREFIX,
50
+ BLOCKLET_INTERFACE_PROTOCOL_HTTP,
51
+ BLOCKLET_DEFAULT_PATH_REWRITE,
52
+ BLOCKLET_DEFAULT_VERSION,
53
+ BLOCKLET_LATEST_SPEC_VERSION,
54
+ BLOCKLET_META_FILE,
40
55
  } = require('@blocklet/meta/lib/constants');
41
56
  const util = require('../../util');
42
57
  const {
@@ -60,16 +75,19 @@ const {
60
75
  getBlockletStatusFromProcess,
61
76
  checkBlockletProcessHealthy,
62
77
  validateBlocklet,
63
- getChildrenMeta,
64
78
  statusMap,
65
79
  expandTarball,
66
80
  verifyIntegrity,
67
81
  pruneBlockletBundle,
68
82
  getDiskInfo,
69
83
  getRuntimeInfo,
70
- mergeMeta,
71
84
  getUpdateMetaList,
72
85
  getRuntimeEnvironments,
86
+ getSourceFromInstallParams,
87
+ parseChildren,
88
+ checkDuplicateComponents,
89
+ getDiffFiles,
90
+ getBundleDir,
73
91
  } = require('../../util/blocklet');
74
92
  const states = require('../../states');
75
93
  const BlockletRegistry = require('../registry');
@@ -153,20 +171,43 @@ class BlockletManager extends BaseBlockletManager {
153
171
  */
154
172
  async install(params, context) {
155
173
  logger.debug('install blocklet', { params, context });
156
- if (params.url) {
174
+
175
+ const source = getSourceFromInstallParams(params);
176
+
177
+ if (source === BlockletSource.url) {
157
178
  return this._installFromUrl({ url: params.url, sync: params.sync }, context);
158
179
  }
159
180
 
160
- if (params.file) {
181
+ if (source === BlockletSource.upload) {
161
182
  const { file, did, diffVersion, deleteSet } = params;
162
183
  return this._installFromUpload({ file, did, diffVersion, deleteSet, context });
163
184
  }
164
185
 
165
- if (params.did) {
166
- return this._installFromRegistry({ did: params.did }, context);
186
+ if (source === BlockletSource.registry) {
187
+ return this._installFromStore({ did: params.did }, context);
188
+ }
189
+
190
+ if (source === BlockletSource.custom) {
191
+ return this._installFromCreate({ title: params.title, description: params.description }, context);
192
+ }
193
+
194
+ // should not be here
195
+ throw new Error('Unknown source');
196
+ }
197
+
198
+ async installComponent({ rootDid, mountPoint, url, file, did, diffVersion, deleteSet }, context = {}) {
199
+ logger.debug('start install component', { rootDid, mountPoint, url });
200
+
201
+ if (file) {
202
+ return this._installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context });
203
+ }
204
+
205
+ if (url) {
206
+ return this._installComponentFromUrl({ rootDid, mountPoint, url, context });
167
207
  }
168
208
 
169
- throw new Error('Can only install blocklet from url/file/did');
209
+ // should not be here
210
+ throw new Error('Unknown source');
170
211
  }
171
212
 
172
213
  // eslint-disable-next-line no-unused-vars
@@ -189,9 +230,9 @@ class BlockletManager extends BaseBlockletManager {
189
230
  }
190
231
  }
191
232
 
192
- meta.isFree = isFree;
233
+ const blocklet = await states.blocklet.getBlocklet(meta.did);
193
234
 
194
- return meta;
235
+ return { meta, isFree, isInstalled: !!blocklet };
195
236
  }
196
237
 
197
238
  async installBlockletFromVc({ vcPresentation, challenge }, context) {
@@ -210,7 +251,7 @@ class BlockletManager extends BaseBlockletManager {
210
251
  const did = get(vc, 'credentialSubject.purchased.blocklet.id');
211
252
  const registry = urlObject.origin;
212
253
 
213
- return this._installFromRegistry({ did, registry }, { ...context, blockletPurchaseVerified: true });
254
+ return this._installFromStore({ did, registry }, { ...context, blockletPurchaseVerified: true });
214
255
  }
215
256
 
216
257
  async start({ did, checkHealthImmediately = false, throwOnError }, context) {
@@ -218,6 +259,10 @@ class BlockletManager extends BaseBlockletManager {
218
259
  const blocklet = await this.ensureBlocklet(did);
219
260
 
220
261
  try {
262
+ if (!hasRunnableComponent(blocklet)) {
263
+ throw new Error('No runnable component found');
264
+ }
265
+
221
266
  // check required config
222
267
  const missingProps = getRequiredMissingConfigs(blocklet);
223
268
  if (missingProps.length) {
@@ -246,7 +291,6 @@ class BlockletManager extends BaseBlockletManager {
246
291
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
247
292
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
248
293
  did, // root blocklet did,
249
- progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
250
294
  }),
251
295
  nodeEnvironments,
252
296
  nodeInfo: await states.node.read(),
@@ -297,7 +341,7 @@ class BlockletManager extends BaseBlockletManager {
297
341
  }
298
342
  }
299
343
 
300
- async stop({ did, updateStatus = true }, context) {
344
+ async stop({ did, updateStatus = true, silent = false }, context) {
301
345
  logger.info('stop blocklet', { did });
302
346
 
303
347
  const blocklet = await this.ensureBlocklet(did);
@@ -313,7 +357,7 @@ class BlockletManager extends BaseBlockletManager {
313
357
 
314
358
  await stopBlockletProcess(blocklet, {
315
359
  preStop: (b) =>
316
- hooks.preStop({
360
+ hooks.preStop(b.env.appId, {
317
361
  appDir: b.env.appDir,
318
362
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
319
363
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
@@ -321,7 +365,7 @@ class BlockletManager extends BaseBlockletManager {
321
365
  notification: states.notification,
322
366
  context,
323
367
  exitOnError: false,
324
- progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
368
+ silent,
325
369
  }),
326
370
  });
327
371
 
@@ -389,7 +433,7 @@ class BlockletManager extends BaseBlockletManager {
389
433
 
390
434
  await deleteBlockletProcess(blocklet, {
391
435
  preDelete: (b) =>
392
- hooks.preUninstall({
436
+ hooks.preUninstall(b.env.appId, {
393
437
  appDir: b.env.appDir,
394
438
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
395
439
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
@@ -412,6 +456,22 @@ class BlockletManager extends BaseBlockletManager {
412
456
  }
413
457
  }
414
458
 
459
+ async deleteComponent({ did, rootDid }, context) {
460
+ logger.info('delete blocklet component', { did, rootDid });
461
+
462
+ const doc = await states.blocklet.getBlocklet(rootDid);
463
+
464
+ doc.children = doc.children.filter((x) => x.meta.did !== did);
465
+ const children = (await this._getDynamicChildrenFromSettings(rootDid)).filter((x) => x.meta.did !== did);
466
+
467
+ await states.blocklet.updateBlocklet(rootDid, doc);
468
+ states.blockletExtras.setSettings(rootDid, { children });
469
+
470
+ const newBlocklet = await this.ensureBlocklet(rootDid);
471
+ this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context }); // trigger router refresh
472
+ return newBlocklet;
473
+ }
474
+
415
475
  async cancelDownload({ did }, context) {
416
476
  await preDownloadLock.acquire();
417
477
  try {
@@ -449,12 +509,14 @@ class BlockletManager extends BaseBlockletManager {
449
509
  return result;
450
510
  }
451
511
 
452
- async detail({ did, attachRuntimeInfo = true }, context) {
512
+ async detail({ did, attachConfig = true, attachRuntimeInfo = true }, context) {
453
513
  if (!did) {
454
514
  throw new Error('did should not be empty');
455
515
  }
456
516
 
457
- const nodeInfo = await states.node.read();
517
+ if (!attachConfig) {
518
+ return states.blocklet.getBlocklet(did);
519
+ }
458
520
 
459
521
  if (!attachRuntimeInfo) {
460
522
  try {
@@ -465,6 +527,8 @@ class BlockletManager extends BaseBlockletManager {
465
527
  }
466
528
  }
467
529
 
530
+ const nodeInfo = await states.node.read();
531
+
468
532
  return this.attachRuntimeInfo({ did, nodeInfo, diskInfo: true, context });
469
533
  }
470
534
 
@@ -549,19 +613,25 @@ class BlockletManager extends BaseBlockletManager {
549
613
  }
550
614
  }
551
615
 
616
+ if (x.key === 'BLOCKLET_PASSPORT_COLOR') {
617
+ if (x.value && x.value !== 'auto') {
618
+ if (x.value.length !== 7 || !isHex(x.value.slice(-6))) {
619
+ throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
620
+ }
621
+ }
622
+ }
623
+
552
624
  blocklet.configObj[x.key] = x.value;
553
625
  }
554
626
 
555
627
  // FIXME: we should also call preConfig for child blocklets
556
- await hooks.preConfig({
628
+ await hooks.preConfig(blocklet.env.appId, {
557
629
  appDir: blocklet.env.appDir,
558
630
  hooks: Object.assign(blocklet.meta.hooks || {}, blocklet.meta.scripts || {}),
559
631
  exitOnError: true,
560
632
  env: { ...getRuntimeEnvironments(blocklet, nodeEnvironments), ...blocklet.configObj },
561
- notification: states.notification,
562
633
  did,
563
634
  context,
564
- progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
565
635
  });
566
636
 
567
637
  // update db
@@ -657,56 +727,45 @@ class BlockletManager extends BaseBlockletManager {
657
727
  }
658
728
 
659
729
  // eslint-disable-next-line no-unused-vars
660
- async diff({ did, hashFiles: clientFiles }, context) {
730
+ async diff({ did, hashFiles: clientFiles, rootDid: inputRootDid }, context) {
661
731
  if (!did) {
662
732
  throw new Error('did is empty');
663
733
  }
734
+
664
735
  if (!clientFiles || !clientFiles.length) {
665
736
  throw new Error('hashFiles is empty');
666
737
  }
667
738
 
668
- logger.info('Get blocklet diff', { did, clientFilesNumber: clientFiles.length });
739
+ const rootDid = inputRootDid || did;
740
+ const childDid = inputRootDid ? did : '';
741
+
742
+ if (childDid === rootDid) {
743
+ throw new Error('Cannot add self as a component');
744
+ }
745
+
746
+ logger.info('Get blocklet diff', { rootDid, childDid, clientFilesNumber: clientFiles.length });
747
+
748
+ const rootBlocklet = await states.blocklet.getBlocklet(rootDid);
749
+ if (childDid && !rootBlocklet) {
750
+ throw new Error('Root blocklet does not exist');
751
+ }
752
+
753
+ const state = childDid ? await (rootBlocklet.children || []).find((x) => x.meta.did === childDid) : rootBlocklet;
669
754
 
670
- const state = await states.blocklet.getBlocklet(did);
671
755
  if (!state) {
672
756
  return {
673
757
  hasBlocklet: false,
674
758
  };
675
759
  }
760
+
676
761
  if (state.source === BlockletSource.local) {
677
762
  throw new Error(`Blocklet ${state.meta.name} is already deployed from local, can not deployed from remote.`);
678
763
  }
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
764
 
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
- }
765
+ const { version } = state.meta;
766
+ const bundleDir = getBundleDir(this.installDir, state.meta);
767
+ const { addSet, changeSet, deleteSet } = await getDiffFiles(clientFiles, bundleDir);
768
+
710
769
  logger.info('Diff files', {
711
770
  name: state.meta.name,
712
771
  did: state.meta.did,
@@ -715,6 +774,7 @@ class BlockletManager extends BaseBlockletManager {
715
774
  changeNum: changeSet.length,
716
775
  deleteNum: deleteSet.length,
717
776
  });
777
+
718
778
  return {
719
779
  hasBlocklet: true,
720
780
  version,
@@ -726,10 +786,20 @@ class BlockletManager extends BaseBlockletManager {
726
786
 
727
787
  async checkChildrenForUpdates({ did }) {
728
788
  const blocklet = await states.blocklet.getBlocklet(did);
729
- const childrenMeta = await getChildrenMeta(blocklet.meta);
789
+ const newStaticChildren = await parseChildren(blocklet.meta);
790
+
791
+ const oldDynamicChildren = await this._getDynamicChildrenFromSettings(did);
792
+ const noneSourceUrlChildren = oldDynamicChildren.filter((x) => !x.sourceUrl);
793
+ const dynamicConfig = oldDynamicChildren
794
+ .filter((x) => x.sourceUrl)
795
+ .map((x) => ({ resolved: x.sourceUrl, name: x.meta.name, mountPoint: x.mountPoint }));
796
+ const newDynamicChildren = [...noneSourceUrlChildren, ...(await parseChildren(dynamicConfig, { dynamic: true }))];
797
+
798
+ checkDuplicateComponents(newDynamicChildren, newStaticChildren);
799
+
730
800
  const updateList = getUpdateMetaList(
731
- blocklet.children.map((x) => x.meta),
732
- childrenMeta
801
+ [...blocklet.children.map((x) => x.meta), ...oldDynamicChildren.map((x) => x.meta)],
802
+ [...newStaticChildren.map((x) => x.meta), ...newDynamicChildren.map((x) => x.meta)]
733
803
  );
734
804
 
735
805
  if (!updateList.length) {
@@ -739,7 +809,8 @@ class BlockletManager extends BaseBlockletManager {
739
809
  // start session
740
810
  const { id: updateId } = await states.session.start({
741
811
  did,
742
- childrenMeta,
812
+ staticChildren: newStaticChildren,
813
+ dynamicChildren: newDynamicChildren,
743
814
  });
744
815
 
745
816
  return {
@@ -748,8 +819,18 @@ class BlockletManager extends BaseBlockletManager {
748
819
  };
749
820
  }
750
821
 
751
- async updateChildren({ updateId }, context) {
752
- const { did, childrenMeta } = await states.session.end(updateId);
822
+ async updateChildren({ updateId, did: inputDid, children: inputChildren }, context) {
823
+ let did;
824
+ let children;
825
+ if (!updateId && inputDid && inputChildren) {
826
+ did = inputDid;
827
+ children = inputChildren;
828
+ } else {
829
+ const sessionData = await states.session.end(updateId);
830
+ did = sessionData.did;
831
+ const { staticChildren = [], dynamicChildren = [] } = sessionData;
832
+ children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
833
+ }
753
834
 
754
835
  // get old blocklet
755
836
  const oldBlocklet = await states.blocklet.getBlocklet(did);
@@ -762,14 +843,13 @@ class BlockletManager extends BaseBlockletManager {
762
843
  did,
763
844
  name,
764
845
  version,
765
- children: childrenMeta.map((x) => ({ name: x.name, version: x.version })),
846
+ children: children.map((x) => ({ name: x.meta.name, version: x.meta.version })),
766
847
  });
767
848
 
768
849
  // new blocklet
769
850
  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);
851
+
852
+ newBlocklet.children = children;
773
853
  await validateBlocklet(newBlocklet);
774
854
 
775
855
  this.emit(BlockletEvents.statusChange, newBlocklet);
@@ -807,6 +887,11 @@ class BlockletManager extends BaseBlockletManager {
807
887
  // ============================================================================================
808
888
  // Internal API that are used by public APIs and called from CLI
809
889
  // ============================================================================================
890
+
891
+ /**
892
+ * After the dev function finished, the caller should send a BlockletEvents.deployed event to the daemon
893
+ * @returns {Object} blocklet
894
+ */
810
895
  async dev(folder) {
811
896
  logger.info('dev blocklet', { folder });
812
897
 
@@ -839,30 +924,33 @@ class BlockletManager extends BaseBlockletManager {
839
924
  await this.deleteProcess({ did });
840
925
  logger.info('delete blocklet precess for dev', { did, version });
841
926
  } catch (err) {
842
- logger.error('failed to delete blocklet process for dev', { error: err });
927
+ if (process.env.NODE_ENV !== 'development') {
928
+ logger.error('failed to delete blocklet process for dev', { error: err });
929
+ }
843
930
  }
844
931
 
845
- const childrenMeta = await getChildrenMeta(meta);
846
- mergeMeta(meta, childrenMeta);
847
- const blocklet = await states.blocklet.addBlocklet({
932
+ const children = await this._getChildren(meta);
933
+ const added = await states.blocklet.addBlocklet({
848
934
  did,
849
935
  meta,
850
936
  source: BlockletSource.local,
851
937
  deployedFrom: folder,
852
938
  mode: BLOCKLET_MODES.DEVELOPMENT,
853
- childrenMeta,
939
+ children,
854
940
  });
855
941
  logger.info('add blocklet for dev', { did, version, meta });
856
942
 
857
- await this._downloadBlocklet(blocklet);
943
+ const oldBlocklet = { children: children.filter((x) => x.dynamic) }; // let downloader skip re-downloading dynamic blocklet
944
+ await this._downloadBlocklet(added, oldBlocklet);
858
945
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
859
946
 
860
947
  // Add environments
861
948
  await this._setConfigs(did);
862
949
  await this.updateBlockletEnvironment(did);
863
950
 
864
- this.emit(BlockletEvents.deployed, { blocklet, context: {} });
865
- return this.ensureBlocklet(did);
951
+ const blocklet = await this.ensureBlocklet(did);
952
+
953
+ return blocklet;
866
954
  }
867
955
 
868
956
  async ensureBlocklet(did) {
@@ -997,7 +1085,8 @@ class BlockletManager extends BaseBlockletManager {
997
1085
  blocklet.diskInfo = await getDiskInfo(blocklet, { useFakeDiskInfo: !diskInfo });
998
1086
 
999
1087
  try {
1000
- const { appId } = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1088
+ const app = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1089
+ const { appId } = app;
1001
1090
  blocklet.runtimeInfo = await getRuntimeInfo(appId);
1002
1091
  if (blocklet.runtimeInfo.status && shouldUpdateBlockletStatus(blocklet.status)) {
1003
1092
  blocklet.status = statusMap[blocklet.runtimeInfo.status];
@@ -1271,7 +1360,7 @@ class BlockletManager extends BaseBlockletManager {
1271
1360
  }
1272
1361
  }
1273
1362
 
1274
- async _installFromRegistry({ did, registry }, context) {
1363
+ async _installFromStore({ did, registry }, context) {
1275
1364
  logger.debug('start install blocklet', { did });
1276
1365
  if (!isValidDid(did)) {
1277
1366
  throw new Error('Blocklet did is invalid');
@@ -1333,9 +1422,84 @@ class BlockletManager extends BaseBlockletManager {
1333
1422
  });
1334
1423
  }
1335
1424
 
1336
- async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1337
- logger.info('install blocklet', { from: 'upload file' });
1338
- // download
1425
+ async _installComponentFromUrl({ rootDid, mountPoint, url, context }) {
1426
+ const blocklet = await states.blocklet.getBlocklet(rootDid);
1427
+ if (!blocklet) {
1428
+ throw new Error('Root blocklet does not exist');
1429
+ }
1430
+
1431
+ const meta = await getBlockletMetaFromUrl(url);
1432
+
1433
+ if (meta.did === rootDid) {
1434
+ throw new Error('Cannot add self as a component');
1435
+ }
1436
+
1437
+ const newChildren = await parseChildren(blocklet.meta, {
1438
+ children: [
1439
+ {
1440
+ meta,
1441
+ mountPoint,
1442
+ sourceUrl: url,
1443
+ },
1444
+ ],
1445
+ dynamic: true,
1446
+ });
1447
+
1448
+ checkDuplicateComponents(blocklet.children, newChildren);
1449
+
1450
+ return this.updateChildren(
1451
+ {
1452
+ did: rootDid,
1453
+ children: [...blocklet.children, ...newChildren],
1454
+ },
1455
+ context
1456
+ );
1457
+ }
1458
+
1459
+ async _installFromCreate({ title, description }, context = {}) {
1460
+ logger.debug('create blocklet', { title, description });
1461
+
1462
+ await joi.string().label('title').max(20).required().validateAsync(title);
1463
+ await joi.string().label('description').max(80).required().validateAsync(description);
1464
+
1465
+ const { did, name } = await this._findNextCustomBlockletName();
1466
+ const rawMeta = {
1467
+ name,
1468
+ did,
1469
+ title,
1470
+ description,
1471
+ version: BLOCKLET_DEFAULT_VERSION,
1472
+ group: BlockletGroup.gateway,
1473
+ interfaces: [
1474
+ {
1475
+ type: BLOCKLET_INTERFACE_TYPE_WEB,
1476
+ name: BLOCKLET_INTERFACE_PUBLIC,
1477
+ path: BLOCKLET_DEFAULT_PATH_REWRITE,
1478
+ prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
1479
+ port: BLOCKLET_DEFAULT_PORT_NAME,
1480
+ protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
1481
+ },
1482
+ ],
1483
+ specVersion: BLOCKLET_LATEST_SPEC_VERSION,
1484
+ };
1485
+ const meta = validateMeta(rawMeta);
1486
+
1487
+ await states.blocklet.addBlocklet({
1488
+ did: meta.did,
1489
+ meta,
1490
+ source: BlockletSource.custom,
1491
+ });
1492
+ await this._setConfigs(did);
1493
+
1494
+ // fake install bundle
1495
+ const bundleDir = getBundleDir(this.installDir, meta);
1496
+ fs.mkdirSync(bundleDir, { recursive: true });
1497
+ updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
1498
+
1499
+ return this._installBlocklet({ did, context });
1500
+ }
1501
+
1502
+ async _downloadFromUpload(file) {
1339
1503
  // const { filename, mimetype, encoding, createReadStream } = await file;
1340
1504
  const { filename, createReadStream } = await file;
1341
1505
  const cwd = path.join(this.dataDirs.tmp, 'download');
@@ -1359,6 +1523,13 @@ class BlockletManager extends BaseBlockletManager {
1359
1523
  writeStream.on('finish', resolve);
1360
1524
  });
1361
1525
 
1526
+ return { cwd, tarFile };
1527
+ }
1528
+
1529
+ async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1530
+ logger.info('install blocklet', { from: 'upload file' });
1531
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1532
+
1362
1533
  // diff deploy
1363
1534
  if (did && diffVersion) {
1364
1535
  const oldBlocklet = await states.blocklet.getBlocklet(did);
@@ -1379,14 +1550,12 @@ class BlockletManager extends BaseBlockletManager {
1379
1550
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1380
1551
  }
1381
1552
 
1382
- const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet);
1383
- const childrenMeta = await getChildrenMeta(meta);
1384
- mergeMeta(meta, childrenMeta);
1553
+ const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet.meta);
1385
1554
  const newBlocklet = await states.blocklet.getBlocklet(did);
1386
1555
  newBlocklet.meta = meta;
1387
1556
  newBlocklet.source = BlockletSource.upload;
1388
1557
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1389
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1558
+ newBlocklet.children = await this._getChildren(meta);
1390
1559
  await validateBlocklet(newBlocklet);
1391
1560
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
1392
1561
 
@@ -1401,19 +1570,18 @@ class BlockletManager extends BaseBlockletManager {
1401
1570
  const { meta } = await this._resolveDownload(cwd, tarFile);
1402
1571
  const oldBlocklet = await states.blocklet.getBlocklet(meta.did);
1403
1572
 
1573
+ // full deploy - upgrade
1404
1574
  if (oldBlocklet) {
1405
1575
  if (isInProgress(oldBlocklet.status)) {
1406
1576
  logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1407
1577
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1408
1578
  }
1409
1579
 
1410
- const childrenMeta = await getChildrenMeta(meta);
1411
- mergeMeta(meta, childrenMeta);
1412
1580
  const newBlocklet = await states.blocklet.getBlocklet(meta.did);
1413
1581
  newBlocklet.meta = meta;
1414
1582
  newBlocklet.source = BlockletSource.upload;
1415
1583
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1416
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1584
+ newBlocklet.children = await this._getChildren(meta);
1417
1585
 
1418
1586
  await validateBlocklet(newBlocklet);
1419
1587
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
@@ -1425,14 +1593,14 @@ class BlockletManager extends BaseBlockletManager {
1425
1593
  });
1426
1594
  }
1427
1595
 
1428
- const childrenMeta = await getChildrenMeta(meta);
1429
- mergeMeta(meta, childrenMeta);
1596
+ // full deploy - install
1597
+ const children = await this._getChildren(meta);
1430
1598
  const blocklet = await states.blocklet.addBlocklet({
1431
1599
  did: meta.did,
1432
1600
  meta,
1433
1601
  source: BlockletSource.upload,
1434
1602
  deployedFrom: `Upload by ${context.user.did}`,
1435
- childrenMeta,
1603
+ children,
1436
1604
  });
1437
1605
 
1438
1606
  await validateBlocklet(blocklet);
@@ -1440,13 +1608,92 @@ class BlockletManager extends BaseBlockletManager {
1440
1608
  await this._setConfigs(meta.did);
1441
1609
 
1442
1610
  // download
1443
- await this._downloadBlocklet(blocklet);
1611
+ await this._downloadBlocklet(
1612
+ blocklet,
1613
+ // let downloader skip re-downloading dynamic blocklet
1614
+ { children: children.filter((x) => x.dynamic) }
1615
+ );
1444
1616
  return this._installBlocklet({
1445
1617
  did: meta.did,
1446
1618
  context,
1447
1619
  });
1448
1620
  }
1449
1621
 
1622
+ async _installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context }) {
1623
+ logger.info('install blocklet', { from: 'upload file' });
1624
+ // download
1625
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1626
+
1627
+ const oldBlocklet = await states.blocklet.getBlocklet(rootDid);
1628
+ if (!oldBlocklet) {
1629
+ throw new Error('Root blocklet does not exist');
1630
+ }
1631
+
1632
+ if (isInProgress(oldBlocklet.status)) {
1633
+ logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1634
+ throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1635
+ }
1636
+
1637
+ let meta;
1638
+ // diff upload
1639
+ if (did && diffVersion) {
1640
+ const oldChild = oldBlocklet.children.find((x) => x.meta.did === did);
1641
+ if (!oldChild) {
1642
+ throw new Error(`Blocklet ${did} not found when diff deploying`);
1643
+ }
1644
+ if (oldChild.meta.version !== diffVersion) {
1645
+ logger.error('Diff deploy: Blocklet version changed', {
1646
+ preVersion: diffVersion,
1647
+ changedVersion: oldChild.meta.version,
1648
+ name: oldChild.meta.name,
1649
+ did: oldChild.meta.did,
1650
+ });
1651
+ throw new Error('Blocklet version changed when diff deploying');
1652
+ }
1653
+
1654
+ meta = (await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldChild.meta)).meta;
1655
+ } else {
1656
+ // full deploy
1657
+ meta = (await this._resolveDownload(cwd, tarFile)).meta;
1658
+ }
1659
+
1660
+ if (meta.did === rootDid) {
1661
+ // should not be here
1662
+ throw new Error('Cannot add self as a component');
1663
+ }
1664
+
1665
+ const newBlocklet = await states.blocklet.getBlocklet(rootDid);
1666
+ const dynamicChildren = await this._getDynamicChildrenFromSettings(rootDid);
1667
+
1668
+ const newChild = {
1669
+ meta,
1670
+ mountPoint,
1671
+ source: BlockletSource.upload,
1672
+ deployedFrom: `Upload by ${context.user.did}`,
1673
+ sourceUrl: '',
1674
+ dynamic: true,
1675
+ };
1676
+ const index = dynamicChildren.findIndex((child) => child.meta.did === meta.did);
1677
+ if (index >= 0) {
1678
+ dynamicChildren.splice(index, 1, newChild);
1679
+ } else {
1680
+ dynamicChildren.push(newChild);
1681
+ }
1682
+
1683
+ const staticChildren = newBlocklet.children.filter((x) => !x.dynamic);
1684
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1685
+
1686
+ newBlocklet.children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
1687
+
1688
+ await validateBlocklet(newBlocklet);
1689
+
1690
+ return this._upgradeBlocklet({
1691
+ oldBlocklet,
1692
+ newBlocklet,
1693
+ context,
1694
+ });
1695
+ }
1696
+
1450
1697
  /**
1451
1698
  * add to download job queue
1452
1699
  * @param {string} did blocklet did
@@ -1466,22 +1713,16 @@ class BlockletManager extends BaseBlockletManager {
1466
1713
  );
1467
1714
  }
1468
1715
 
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
1716
  async prune() {
1483
1717
  const blocklets = await states.blocklet.getBlocklets();
1484
- await pruneBlockletBundle(blocklets, this.dataDirs.blocklets);
1718
+ const settings = await states.blockletExtras.listSettings();
1719
+ await pruneBlockletBundle({
1720
+ installDir: this.dataDirs.blocklets,
1721
+ blocklets,
1722
+ blockletSettings: settings
1723
+ .filter((x) => x.settings.children && x.settings.children.length)
1724
+ .map((x) => x.settings),
1725
+ });
1485
1726
  }
1486
1727
 
1487
1728
  async getLatestBlockletVersion({ did, version }) {
@@ -1569,20 +1810,35 @@ class BlockletManager extends BaseBlockletManager {
1569
1810
  blocklets.forEach(run);
1570
1811
  }
1571
1812
 
1813
+ async _getChildren(meta) {
1814
+ const staticChildren = await parseChildren(meta);
1815
+ const dynamicChildren = await parseChildren(meta, {
1816
+ children: await states.blockletExtras.getSettings(meta.did, 'children', []),
1817
+ dynamic: true,
1818
+ });
1819
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1820
+
1821
+ return [...staticChildren, ...dynamicChildren];
1822
+ }
1823
+
1824
+ async _getDynamicChildrenFromSettings(did) {
1825
+ const children = await states.blockletExtras.getSettings(did, 'children', []);
1826
+ return children.map((x) => ({ ...x, dynamic: true }));
1827
+ }
1828
+
1572
1829
  async _install({ meta, source, deployedFrom, context, sync }) {
1573
1830
  validateBlockletMeta(meta, { ensureDist: true });
1574
1831
 
1575
1832
  const { name, did, version } = meta;
1576
1833
 
1577
- const childrenMeta = await getChildrenMeta(meta);
1578
- mergeMeta(meta, childrenMeta);
1834
+ const children = await this._getChildren(meta);
1579
1835
  try {
1580
1836
  const blocklet = await states.blocklet.addBlocklet({
1581
1837
  did: meta.did,
1582
1838
  meta,
1583
1839
  source,
1584
1840
  deployedFrom,
1585
- childrenMeta,
1841
+ children,
1586
1842
  });
1587
1843
 
1588
1844
  await validateBlocklet(blocklet);
@@ -1597,6 +1853,7 @@ class BlockletManager extends BaseBlockletManager {
1597
1853
  // download
1598
1854
  const downloadParams = {
1599
1855
  blocklet: { ...blocklet1 },
1856
+ oldBlocklet: { children: children.filter((x) => x.dynamic) }, // let downloader skip re-downloading dynamic blocklet
1600
1857
  context,
1601
1858
  postAction: 'install',
1602
1859
  };
@@ -1655,7 +1912,7 @@ class BlockletManager extends BaseBlockletManager {
1655
1912
  }
1656
1913
 
1657
1914
  async _upgrade({ meta, source, deployedFrom, context, sync }) {
1658
- validateBlockletMeta(meta, { ensureDist: true });
1915
+ validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
1659
1916
 
1660
1917
  const { name, version, did } = meta;
1661
1918
 
@@ -1666,12 +1923,11 @@ class BlockletManager extends BaseBlockletManager {
1666
1923
  logger.info(`${action} blocklet`, { did, version });
1667
1924
 
1668
1925
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
1669
- const childrenMeta = await getChildrenMeta(meta);
1670
- mergeMeta(meta, childrenMeta);
1926
+
1671
1927
  newBlocklet.meta = meta;
1672
1928
  newBlocklet.source = source;
1673
1929
  newBlocklet.deployedFrom = deployedFrom;
1674
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1930
+ newBlocklet.children = await this._getChildren(meta);
1675
1931
 
1676
1932
  await validateBlocklet(newBlocklet);
1677
1933
 
@@ -1774,7 +2030,7 @@ class BlockletManager extends BaseBlockletManager {
1774
2030
 
1775
2031
  await ensureBlockletExpanded(meta, downloadDir);
1776
2032
 
1777
- installDir = path.join(this.installDir, name, version);
2033
+ installDir = getBundleDir(this.installDir, meta);
1778
2034
  if (fs.existsSync(installDir)) {
1779
2035
  fs.removeSync(installDir);
1780
2036
  logger.info('cleanup blocklet upgrade dir', { name, version, installDir });
@@ -1791,7 +2047,7 @@ class BlockletManager extends BaseBlockletManager {
1791
2047
  return { meta, installDir };
1792
2048
  }
1793
2049
 
1794
- async _resolveDiffDownload(cwd, tarFile, deleteSet, blocklet) {
2050
+ async _resolveDiffDownload(cwd, tarFile, deleteSet, bundle) {
1795
2051
  logger.info('Resolve diff download', { tarFile, cwd });
1796
2052
  const downloadDir = path.join(cwd, `${path.basename(tarFile, path.extname(tarFile))}`);
1797
2053
  const diffDir = `${downloadDir}-diff`;
@@ -1804,7 +2060,7 @@ class BlockletManager extends BaseBlockletManager {
1804
2060
  throw error;
1805
2061
  }
1806
2062
  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);
2063
+ await fs.copy(getBundleDir(this.installDir, bundle), downloadDir);
1808
2064
  try {
1809
2065
  // delete
1810
2066
  logger.info('Delete files from downloadDir', { fileNum: deleteSet.length });
@@ -1833,11 +2089,11 @@ class BlockletManager extends BaseBlockletManager {
1833
2089
 
1834
2090
  await ensureBlockletExpanded(meta, downloadDir);
1835
2091
 
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 });
2092
+ const bundleDir = getBundleDir(this.installDir, meta);
2093
+ logger.info('Move downloadDir to installDir', { downloadDir, bundleDir });
2094
+ await fs.move(downloadDir, bundleDir, { overwrite: true });
1839
2095
 
1840
- return { meta, installDir };
2096
+ return { meta, installDir: bundleDir };
1841
2097
  } catch (error) {
1842
2098
  fs.removeSync(downloadDir);
1843
2099
  fs.removeSync(diffDir);
@@ -1865,10 +2121,10 @@ class BlockletManager extends BaseBlockletManager {
1865
2121
  // pre install
1866
2122
  const nodeEnvironments = await states.node.getEnvironments();
1867
2123
  const preInstall = (b) =>
1868
- hooks.preInstall({
2124
+ hooks.preInstall(b.env.appId, {
1869
2125
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1870
2126
  env: { ...nodeEnvironments },
1871
- appDir: blocklet.env.appDir,
2127
+ appDir: b.env.appDir,
1872
2128
  did, // root blocklet did
1873
2129
  notification: states.notification,
1874
2130
  context,
@@ -1881,10 +2137,10 @@ class BlockletManager extends BaseBlockletManager {
1881
2137
 
1882
2138
  // post install
1883
2139
  const postInstall = (b) =>
1884
- hooks.postInstall({
2140
+ hooks.postInstall(b.env.appId, {
1885
2141
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1886
2142
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1887
- appDir: blocklet.env.appDir,
2143
+ appDir: b.env.appDir,
1888
2144
  did, // root blocklet did
1889
2145
  notification: states.notification,
1890
2146
  context,
@@ -1894,17 +2150,6 @@ class BlockletManager extends BaseBlockletManager {
1894
2150
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
1895
2151
  blocklet = await this.ensureBlocklet(did);
1896
2152
  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
2153
 
1909
2154
  this.emit(BlockletEvents.installed, { blocklet, context });
1910
2155
 
@@ -1965,10 +2210,10 @@ class BlockletManager extends BaseBlockletManager {
1965
2210
  // pre install
1966
2211
  const nodeEnvironments = await states.node.getEnvironments();
1967
2212
  const preInstall = (b) =>
1968
- hooks.preInstall({
2213
+ hooks.preInstall(b.env.appId, {
1969
2214
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1970
2215
  env: { ...nodeEnvironments },
1971
- appDir: blocklet.env.appDir,
2216
+ appDir: b.env.appDir,
1972
2217
  did, // root blocklet did
1973
2218
  notification: states.notification,
1974
2219
  context,
@@ -1981,7 +2226,7 @@ class BlockletManager extends BaseBlockletManager {
1981
2226
 
1982
2227
  // post install
1983
2228
  const postInstall = (b) =>
1984
- hooks.postInstall({
2229
+ hooks.postInstall(b.env.appId, {
1985
2230
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1986
2231
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1987
2232
  appDir: b.env.appDir,
@@ -1997,10 +2242,10 @@ class BlockletManager extends BaseBlockletManager {
1997
2242
  if (b.meta.did === did) {
1998
2243
  return runMigrationScripts({
1999
2244
  blocklet: b,
2245
+ appDir: b.env.appDir,
2246
+ env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
2000
2247
  oldVersion,
2001
2248
  newVersion: version,
2002
- env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
2003
- appDir: b.env.appDir,
2004
2249
  did: b.meta.did,
2005
2250
  notification: states.notification,
2006
2251
  context,
@@ -2053,6 +2298,12 @@ class BlockletManager extends BaseBlockletManager {
2053
2298
  logger.error('emit upgrade notification failed', { name, version, error });
2054
2299
  }
2055
2300
 
2301
+ // Update dynamic component meta in blocklet settings
2302
+ const dynamicChildren = blocklet.children
2303
+ .filter((x) => x.dynamic)
2304
+ .map((x) => pick(x, ['meta', 'mountPoint', 'sourceUrl', 'source']));
2305
+ await states.blockletExtras.setSettings(did, { children: dynamicChildren });
2306
+
2056
2307
  return blocklet;
2057
2308
  } catch (err) {
2058
2309
  const b = await this._rollback(action, did, oldBlocklet);
@@ -2259,12 +2510,18 @@ class BlockletManager extends BaseBlockletManager {
2259
2510
 
2260
2511
  for (const child of blocklet.children) {
2261
2512
  const oldChild = oldChildren[child.meta.did];
2262
- if (!oldChild || oldChild.meta.dist.integrity !== child.meta.dist.integrity) {
2513
+ if (
2514
+ !oldChild ||
2515
+ (![BlockletSource.upload, BlockletSource.local].includes(child.source) &&
2516
+ child.sourceUrl &&
2517
+ get(oldChild, 'meta.dist.integrity') !== get(child, 'meta.dist.integrity'))
2518
+ ) {
2263
2519
  metas.push(child.meta);
2264
2520
  }
2265
2521
  }
2266
2522
 
2267
2523
  try {
2524
+ logger.info('Download Blocklet', { name, did, bundles: metas.map((x) => get(x, 'dist.tarball')) });
2268
2525
  const tasks = [];
2269
2526
  for (const meta of metas) {
2270
2527
  const url = await this.registry.resolveTarballURL({
@@ -2332,7 +2589,7 @@ class BlockletManager extends BaseBlockletManager {
2332
2589
  async _rollback(action, did, oldBlocklet) {
2333
2590
  if (action === 'install') {
2334
2591
  // remove blocklet
2335
- return this._deleteBlocklet({ did, keepData: false });
2592
+ return this._deleteBlocklet({ did, keepData: true });
2336
2593
  }
2337
2594
 
2338
2595
  if (['upgrade', 'downgrade'].includes(action)) {
@@ -2374,7 +2631,18 @@ class BlockletManager extends BaseBlockletManager {
2374
2631
  const result = await states.blocklet.deleteBlocklet(did);
2375
2632
  logger.info('blocklet removed successfully', { did });
2376
2633
 
2377
- this.emit(BlockletEvents.removed, { blocklet: result, context });
2634
+ let keepRouting = true;
2635
+ if (keepData === false || keepConfigs === false) {
2636
+ keepRouting = false;
2637
+ }
2638
+
2639
+ this.emit(BlockletEvents.removed, {
2640
+ blocklet: result,
2641
+ context: {
2642
+ ...context,
2643
+ keepRouting,
2644
+ },
2645
+ });
2378
2646
  return blocklet;
2379
2647
  }
2380
2648
 
@@ -2395,6 +2663,20 @@ class BlockletManager extends BaseBlockletManager {
2395
2663
  await states.blockletExtras.delChildConfigs(did, child.meta.did);
2396
2664
  }
2397
2665
  }
2666
+
2667
+ async _findNextCustomBlockletName(leftTimes = 10) {
2668
+ if (leftTimes <= 0) {
2669
+ throw new Error('Generate custom blocklet did too many times');
2670
+ }
2671
+ const number = await states.node.increaseCustomBlockletNumber();
2672
+ const name = `custom-${number}`;
2673
+ const did = toBlockletDid(name);
2674
+ const blocklet = await states.blocklet.getBlocklet(did);
2675
+ if (blocklet) {
2676
+ return this._findNextCustomBlockletName(leftTimes - 1);
2677
+ }
2678
+ return { did, name };
2679
+ }
2398
2680
  }
2399
2681
 
2400
2682
  module.exports = BlockletManager;