@abtnode/core 1.6.13 → 1.6.17

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,20 +3,20 @@
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');
@@ -24,9 +24,17 @@ const { updateBlocklet: updateDidDocument } = require('@abtnode/util/lib/did-doc
24
24
  const { BLOCKLET_PURCHASE_NFT_TYPE } = require('@abtnode/constant');
25
25
 
26
26
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
27
- const { isFreeBlocklet, isDeletableBlocklet, getRequiredMissingConfigs } = require('@blocklet/meta/lib/util');
27
+ const {
28
+ isFreeBlocklet,
29
+ isDeletableBlocklet,
30
+ getRequiredMissingConfigs,
31
+ hasRunnableComponent,
32
+ } = require('@blocklet/meta/lib/util');
28
33
  const validateBlockletEntry = require('@blocklet/meta/lib/entry');
29
34
  const { getBlockletInfo } = require('@blocklet/meta/lib');
35
+ const toBlockletDid = require('@blocklet/meta/lib/did');
36
+ const { validateMeta } = require('@blocklet/meta/lib/validate');
37
+ const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
30
38
 
31
39
  const {
32
40
  BlockletStatus,
@@ -37,6 +45,15 @@ const {
37
45
  BlockletGroup,
38
46
  fromBlockletStatus,
39
47
  fromBlockletSource,
48
+ BLOCKLET_DEFAULT_PORT_NAME,
49
+ BLOCKLET_INTERFACE_TYPE_WEB,
50
+ BLOCKLET_INTERFACE_PUBLIC,
51
+ BLOCKLET_DYNAMIC_PATH_PREFIX,
52
+ BLOCKLET_INTERFACE_PROTOCOL_HTTP,
53
+ BLOCKLET_DEFAULT_PATH_REWRITE,
54
+ BLOCKLET_DEFAULT_VERSION,
55
+ BLOCKLET_LATEST_SPEC_VERSION,
56
+ BLOCKLET_META_FILE,
40
57
  } = require('@blocklet/meta/lib/constants');
41
58
  const util = require('../../util');
42
59
  const {
@@ -60,16 +77,19 @@ const {
60
77
  getBlockletStatusFromProcess,
61
78
  checkBlockletProcessHealthy,
62
79
  validateBlocklet,
63
- getChildrenMeta,
64
80
  statusMap,
65
81
  expandTarball,
66
82
  verifyIntegrity,
67
83
  pruneBlockletBundle,
68
84
  getDiskInfo,
69
85
  getRuntimeInfo,
70
- mergeMeta,
71
86
  getUpdateMetaList,
72
87
  getRuntimeEnvironments,
88
+ getSourceFromInstallParams,
89
+ parseChildren,
90
+ checkDuplicateComponents,
91
+ getDiffFiles,
92
+ getBundleDir,
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) {
@@ -412,6 +459,22 @@ class BlockletManager extends BaseBlockletManager {
412
459
  }
413
460
  }
414
461
 
462
+ async deleteComponent({ did, rootDid }, context) {
463
+ logger.info('delete blocklet component', { did, rootDid });
464
+
465
+ const doc = await states.blocklet.getBlocklet(rootDid);
466
+
467
+ doc.children = doc.children.filter((x) => x.meta.did !== did);
468
+ const children = (await this._getDynamicChildrenFromSettings(rootDid)).filter((x) => x.meta.did !== did);
469
+
470
+ await states.blocklet.updateBlocklet(rootDid, doc);
471
+ states.blockletExtras.setSettings(rootDid, { children });
472
+
473
+ const newBlocklet = await this.ensureBlocklet(rootDid);
474
+ this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context }); // trigger router refresh
475
+ return newBlocklet;
476
+ }
477
+
415
478
  async cancelDownload({ did }, context) {
416
479
  await preDownloadLock.acquire();
417
480
  try {
@@ -549,6 +612,14 @@ class BlockletManager extends BaseBlockletManager {
549
612
  }
550
613
  }
551
614
 
615
+ if (x.key === 'BLOCKLET_PASSPORT_COLOR') {
616
+ if (x.value && x.value !== 'auto') {
617
+ if (x.value.length !== 7 || !isHex(x.value.slice(-6))) {
618
+ throw new Error('BLOCKLET_PASSPORT_COLOR must be a hex encoded color, eg. #ffeeaa');
619
+ }
620
+ }
621
+ }
622
+
552
623
  blocklet.configObj[x.key] = x.value;
553
624
  }
554
625
 
@@ -558,7 +629,6 @@ class BlockletManager extends BaseBlockletManager {
558
629
  hooks: Object.assign(blocklet.meta.hooks || {}, blocklet.meta.scripts || {}),
559
630
  exitOnError: true,
560
631
  env: { ...getRuntimeEnvironments(blocklet, nodeEnvironments), ...blocklet.configObj },
561
- notification: states.notification,
562
632
  did,
563
633
  context,
564
634
  progress: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT,
@@ -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);
@@ -842,19 +922,19 @@ class BlockletManager extends BaseBlockletManager {
842
922
  logger.error('failed to delete blocklet process for dev', { error: err });
843
923
  }
844
924
 
845
- const childrenMeta = await getChildrenMeta(meta);
846
- mergeMeta(meta, childrenMeta);
925
+ const children = await this._getChildren(meta);
847
926
  const blocklet = await states.blocklet.addBlocklet({
848
927
  did,
849
928
  meta,
850
929
  source: BlockletSource.local,
851
930
  deployedFrom: folder,
852
931
  mode: BLOCKLET_MODES.DEVELOPMENT,
853
- childrenMeta,
932
+ children,
854
933
  });
855
934
  logger.info('add blocklet for dev', { did, version, meta });
856
935
 
857
- await this._downloadBlocklet(blocklet);
936
+ const oldBlocklet = { children: children.filter((x) => x.dynamic) }; // let downloader skip re-downloading dynamic blocklet
937
+ await this._downloadBlocklet(blocklet, oldBlocklet);
858
938
  await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
859
939
 
860
940
  // Add environments
@@ -997,7 +1077,8 @@ class BlockletManager extends BaseBlockletManager {
997
1077
  blocklet.diskInfo = await getDiskInfo(blocklet, { useFakeDiskInfo: !diskInfo });
998
1078
 
999
1079
  try {
1000
- const { appId } = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1080
+ const app = blocklet.meta.group === BlockletGroup.gateway ? blocklet.children[0].env : blocklet.env;
1081
+ const { appId } = app;
1001
1082
  blocklet.runtimeInfo = await getRuntimeInfo(appId);
1002
1083
  if (blocklet.runtimeInfo.status && shouldUpdateBlockletStatus(blocklet.status)) {
1003
1084
  blocklet.status = statusMap[blocklet.runtimeInfo.status];
@@ -1271,7 +1352,7 @@ class BlockletManager extends BaseBlockletManager {
1271
1352
  }
1272
1353
  }
1273
1354
 
1274
- async _installFromRegistry({ did, registry }, context) {
1355
+ async _installFromStore({ did, registry }, context) {
1275
1356
  logger.debug('start install blocklet', { did });
1276
1357
  if (!isValidDid(did)) {
1277
1358
  throw new Error('Blocklet did is invalid');
@@ -1333,9 +1414,84 @@ class BlockletManager extends BaseBlockletManager {
1333
1414
  });
1334
1415
  }
1335
1416
 
1336
- async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1337
- logger.info('install blocklet', { from: 'upload file' });
1338
- // download
1417
+ async _installComponentFromUrl({ rootDid, mountPoint, url, context }) {
1418
+ const blocklet = await states.blocklet.getBlocklet(rootDid);
1419
+ if (!blocklet) {
1420
+ throw new Error('Root blocklet does not exist');
1421
+ }
1422
+
1423
+ const meta = await getBlockletMetaFromUrl(url);
1424
+
1425
+ if (meta.did === rootDid) {
1426
+ throw new Error('Cannot add self as a component');
1427
+ }
1428
+
1429
+ const newChildren = await parseChildren(blocklet.meta, {
1430
+ children: [
1431
+ {
1432
+ meta,
1433
+ mountPoint,
1434
+ sourceUrl: url,
1435
+ },
1436
+ ],
1437
+ dynamic: true,
1438
+ });
1439
+
1440
+ checkDuplicateComponents(blocklet.children, newChildren);
1441
+
1442
+ return this.updateChildren(
1443
+ {
1444
+ did: rootDid,
1445
+ children: [...blocklet.children, ...newChildren],
1446
+ },
1447
+ context
1448
+ );
1449
+ }
1450
+
1451
+ async _installFromCreate({ title, description }, context = {}) {
1452
+ logger.debug('create blocklet', { title, description });
1453
+
1454
+ await joi.string().label('title').max(20).required().validateAsync(title);
1455
+ await joi.string().label('description').max(80).required().validateAsync(description);
1456
+
1457
+ const { did, name } = await this._findNextCustomBlockletName();
1458
+ const rawMeta = {
1459
+ name,
1460
+ did,
1461
+ title,
1462
+ description,
1463
+ version: BLOCKLET_DEFAULT_VERSION,
1464
+ group: BlockletGroup.gateway,
1465
+ interfaces: [
1466
+ {
1467
+ type: BLOCKLET_INTERFACE_TYPE_WEB,
1468
+ name: BLOCKLET_INTERFACE_PUBLIC,
1469
+ path: BLOCKLET_DEFAULT_PATH_REWRITE,
1470
+ prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
1471
+ port: BLOCKLET_DEFAULT_PORT_NAME,
1472
+ protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
1473
+ },
1474
+ ],
1475
+ specVersion: BLOCKLET_LATEST_SPEC_VERSION,
1476
+ };
1477
+ const meta = validateMeta(rawMeta);
1478
+
1479
+ await states.blocklet.addBlocklet({
1480
+ did: meta.did,
1481
+ meta,
1482
+ source: BlockletSource.custom,
1483
+ });
1484
+ await this._setConfigs(did);
1485
+
1486
+ // fake install bundle
1487
+ const bundleDir = getBundleDir(this.installDir, meta);
1488
+ fs.mkdirSync(bundleDir, { recursive: true });
1489
+ updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
1490
+
1491
+ return this._installBlocklet({ did, context });
1492
+ }
1493
+
1494
+ async _downloadFromUpload(file) {
1339
1495
  // const { filename, mimetype, encoding, createReadStream } = await file;
1340
1496
  const { filename, createReadStream } = await file;
1341
1497
  const cwd = path.join(this.dataDirs.tmp, 'download');
@@ -1359,6 +1515,13 @@ class BlockletManager extends BaseBlockletManager {
1359
1515
  writeStream.on('finish', resolve);
1360
1516
  });
1361
1517
 
1518
+ return { cwd, tarFile };
1519
+ }
1520
+
1521
+ async _installFromUpload({ file, did, diffVersion, deleteSet, context }) {
1522
+ logger.info('install blocklet', { from: 'upload file' });
1523
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1524
+
1362
1525
  // diff deploy
1363
1526
  if (did && diffVersion) {
1364
1527
  const oldBlocklet = await states.blocklet.getBlocklet(did);
@@ -1379,14 +1542,12 @@ class BlockletManager extends BaseBlockletManager {
1379
1542
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1380
1543
  }
1381
1544
 
1382
- const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet);
1383
- const childrenMeta = await getChildrenMeta(meta);
1384
- mergeMeta(meta, childrenMeta);
1545
+ const { meta } = await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldBlocklet.meta);
1385
1546
  const newBlocklet = await states.blocklet.getBlocklet(did);
1386
1547
  newBlocklet.meta = meta;
1387
1548
  newBlocklet.source = BlockletSource.upload;
1388
1549
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1389
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1550
+ newBlocklet.children = await this._getChildren(meta);
1390
1551
  await validateBlocklet(newBlocklet);
1391
1552
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
1392
1553
 
@@ -1401,19 +1562,18 @@ class BlockletManager extends BaseBlockletManager {
1401
1562
  const { meta } = await this._resolveDownload(cwd, tarFile);
1402
1563
  const oldBlocklet = await states.blocklet.getBlocklet(meta.did);
1403
1564
 
1565
+ // full deploy - upgrade
1404
1566
  if (oldBlocklet) {
1405
1567
  if (isInProgress(oldBlocklet.status)) {
1406
1568
  logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1407
1569
  throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1408
1570
  }
1409
1571
 
1410
- const childrenMeta = await getChildrenMeta(meta);
1411
- mergeMeta(meta, childrenMeta);
1412
1572
  const newBlocklet = await states.blocklet.getBlocklet(meta.did);
1413
1573
  newBlocklet.meta = meta;
1414
1574
  newBlocklet.source = BlockletSource.upload;
1415
1575
  newBlocklet.deployedFrom = `Upload by ${context.user.did}`;
1416
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1576
+ newBlocklet.children = await this._getChildren(meta);
1417
1577
 
1418
1578
  await validateBlocklet(newBlocklet);
1419
1579
  await this._downloadBlocklet(newBlocklet, oldBlocklet);
@@ -1425,14 +1585,14 @@ class BlockletManager extends BaseBlockletManager {
1425
1585
  });
1426
1586
  }
1427
1587
 
1428
- const childrenMeta = await getChildrenMeta(meta);
1429
- mergeMeta(meta, childrenMeta);
1588
+ // full deploy - install
1589
+ const children = await this._getChildren(meta);
1430
1590
  const blocklet = await states.blocklet.addBlocklet({
1431
1591
  did: meta.did,
1432
1592
  meta,
1433
1593
  source: BlockletSource.upload,
1434
1594
  deployedFrom: `Upload by ${context.user.did}`,
1435
- childrenMeta,
1595
+ children,
1436
1596
  });
1437
1597
 
1438
1598
  await validateBlocklet(blocklet);
@@ -1440,13 +1600,92 @@ class BlockletManager extends BaseBlockletManager {
1440
1600
  await this._setConfigs(meta.did);
1441
1601
 
1442
1602
  // download
1443
- await this._downloadBlocklet(blocklet);
1603
+ await this._downloadBlocklet(
1604
+ blocklet,
1605
+ // let downloader skip re-downloading dynamic blocklet
1606
+ { children: children.filter((x) => x.dynamic) }
1607
+ );
1444
1608
  return this._installBlocklet({
1445
1609
  did: meta.did,
1446
1610
  context,
1447
1611
  });
1448
1612
  }
1449
1613
 
1614
+ async _installComponentFromUpload({ rootDid, mountPoint, file, did, diffVersion, deleteSet, context }) {
1615
+ logger.info('install blocklet', { from: 'upload file' });
1616
+ // download
1617
+ const { cwd, tarFile } = await this._downloadFromUpload(file);
1618
+
1619
+ const oldBlocklet = await states.blocklet.getBlocklet(rootDid);
1620
+ if (!oldBlocklet) {
1621
+ throw new Error('Root blocklet does not exist');
1622
+ }
1623
+
1624
+ if (isInProgress(oldBlocklet.status)) {
1625
+ logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1626
+ throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
1627
+ }
1628
+
1629
+ let meta;
1630
+ // diff upload
1631
+ if (did && diffVersion) {
1632
+ const oldChild = oldBlocklet.children.find((x) => x.meta.did === did);
1633
+ if (!oldChild) {
1634
+ throw new Error(`Blocklet ${did} not found when diff deploying`);
1635
+ }
1636
+ if (oldChild.meta.version !== diffVersion) {
1637
+ logger.error('Diff deploy: Blocklet version changed', {
1638
+ preVersion: diffVersion,
1639
+ changedVersion: oldChild.meta.version,
1640
+ name: oldChild.meta.name,
1641
+ did: oldChild.meta.did,
1642
+ });
1643
+ throw new Error('Blocklet version changed when diff deploying');
1644
+ }
1645
+
1646
+ meta = (await this._resolveDiffDownload(cwd, tarFile, deleteSet, oldChild.meta)).meta;
1647
+ } else {
1648
+ // full deploy
1649
+ meta = (await this._resolveDownload(cwd, tarFile)).meta;
1650
+ }
1651
+
1652
+ if (meta.did === rootDid) {
1653
+ // should not be here
1654
+ throw new Error('Cannot add self as a component');
1655
+ }
1656
+
1657
+ const newBlocklet = await states.blocklet.getBlocklet(rootDid);
1658
+ const dynamicChildren = await this._getDynamicChildrenFromSettings(rootDid);
1659
+
1660
+ const newChild = {
1661
+ meta,
1662
+ mountPoint,
1663
+ source: BlockletSource.upload,
1664
+ deployedFrom: `Upload by ${context.user.did}`,
1665
+ sourceUrl: '',
1666
+ dynamic: true,
1667
+ };
1668
+ const index = dynamicChildren.findIndex((child) => child.meta.did === meta.did);
1669
+ if (index >= 0) {
1670
+ dynamicChildren.splice(index, 1, newChild);
1671
+ } else {
1672
+ dynamicChildren.push(newChild);
1673
+ }
1674
+
1675
+ const staticChildren = newBlocklet.children.filter((x) => !x.dynamic);
1676
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1677
+
1678
+ newBlocklet.children = [...staticChildren, ...dynamicChildren.map((x) => ({ ...x, dynamic: true }))];
1679
+
1680
+ await validateBlocklet(newBlocklet);
1681
+
1682
+ return this._upgradeBlocklet({
1683
+ oldBlocklet,
1684
+ newBlocklet,
1685
+ context,
1686
+ });
1687
+ }
1688
+
1450
1689
  /**
1451
1690
  * add to download job queue
1452
1691
  * @param {string} did blocklet did
@@ -1481,7 +1720,14 @@ class BlockletManager extends BaseBlockletManager {
1481
1720
 
1482
1721
  async prune() {
1483
1722
  const blocklets = await states.blocklet.getBlocklets();
1484
- await pruneBlockletBundle(blocklets, this.dataDirs.blocklets);
1723
+ const settings = await states.blockletExtras.listSettings();
1724
+ await pruneBlockletBundle({
1725
+ installDir: this.dataDirs.blocklets,
1726
+ blocklets,
1727
+ blockletSettings: settings
1728
+ .filter((x) => x.settings.children && x.settings.children.length)
1729
+ .map((x) => x.settings),
1730
+ });
1485
1731
  }
1486
1732
 
1487
1733
  async getLatestBlockletVersion({ did, version }) {
@@ -1569,20 +1815,35 @@ class BlockletManager extends BaseBlockletManager {
1569
1815
  blocklets.forEach(run);
1570
1816
  }
1571
1817
 
1818
+ async _getChildren(meta) {
1819
+ const staticChildren = await parseChildren(meta);
1820
+ const dynamicChildren = await parseChildren(meta, {
1821
+ children: await states.blockletExtras.getSettings(meta.did, 'children', []),
1822
+ dynamic: true,
1823
+ });
1824
+ checkDuplicateComponents(dynamicChildren, staticChildren);
1825
+
1826
+ return [...staticChildren, ...dynamicChildren];
1827
+ }
1828
+
1829
+ async _getDynamicChildrenFromSettings(did) {
1830
+ const children = await states.blockletExtras.getSettings(did, 'children', []);
1831
+ return children.map((x) => ({ ...x, dynamic: true }));
1832
+ }
1833
+
1572
1834
  async _install({ meta, source, deployedFrom, context, sync }) {
1573
1835
  validateBlockletMeta(meta, { ensureDist: true });
1574
1836
 
1575
1837
  const { name, did, version } = meta;
1576
1838
 
1577
- const childrenMeta = await getChildrenMeta(meta);
1578
- mergeMeta(meta, childrenMeta);
1839
+ const children = await this._getChildren(meta);
1579
1840
  try {
1580
1841
  const blocklet = await states.blocklet.addBlocklet({
1581
1842
  did: meta.did,
1582
1843
  meta,
1583
1844
  source,
1584
1845
  deployedFrom,
1585
- childrenMeta,
1846
+ children,
1586
1847
  });
1587
1848
 
1588
1849
  await validateBlocklet(blocklet);
@@ -1597,6 +1858,7 @@ class BlockletManager extends BaseBlockletManager {
1597
1858
  // download
1598
1859
  const downloadParams = {
1599
1860
  blocklet: { ...blocklet1 },
1861
+ oldBlocklet: { children: children.filter((x) => x.dynamic) }, // let downloader skip re-downloading dynamic blocklet
1600
1862
  context,
1601
1863
  postAction: 'install',
1602
1864
  };
@@ -1655,7 +1917,7 @@ class BlockletManager extends BaseBlockletManager {
1655
1917
  }
1656
1918
 
1657
1919
  async _upgrade({ meta, source, deployedFrom, context, sync }) {
1658
- validateBlockletMeta(meta, { ensureDist: true });
1920
+ validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
1659
1921
 
1660
1922
  const { name, version, did } = meta;
1661
1923
 
@@ -1666,12 +1928,11 @@ class BlockletManager extends BaseBlockletManager {
1666
1928
  logger.info(`${action} blocklet`, { did, version });
1667
1929
 
1668
1930
  const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
1669
- const childrenMeta = await getChildrenMeta(meta);
1670
- mergeMeta(meta, childrenMeta);
1931
+
1671
1932
  newBlocklet.meta = meta;
1672
1933
  newBlocklet.source = source;
1673
1934
  newBlocklet.deployedFrom = deployedFrom;
1674
- newBlocklet.children = await states.blocklet.getChildrenFromMetas(childrenMeta);
1935
+ newBlocklet.children = await this._getChildren(meta);
1675
1936
 
1676
1937
  await validateBlocklet(newBlocklet);
1677
1938
 
@@ -1774,7 +2035,7 @@ class BlockletManager extends BaseBlockletManager {
1774
2035
 
1775
2036
  await ensureBlockletExpanded(meta, downloadDir);
1776
2037
 
1777
- installDir = path.join(this.installDir, name, version);
2038
+ installDir = getBundleDir(this.installDir, meta);
1778
2039
  if (fs.existsSync(installDir)) {
1779
2040
  fs.removeSync(installDir);
1780
2041
  logger.info('cleanup blocklet upgrade dir', { name, version, installDir });
@@ -1791,7 +2052,7 @@ class BlockletManager extends BaseBlockletManager {
1791
2052
  return { meta, installDir };
1792
2053
  }
1793
2054
 
1794
- async _resolveDiffDownload(cwd, tarFile, deleteSet, blocklet) {
2055
+ async _resolveDiffDownload(cwd, tarFile, deleteSet, bundle) {
1795
2056
  logger.info('Resolve diff download', { tarFile, cwd });
1796
2057
  const downloadDir = path.join(cwd, `${path.basename(tarFile, path.extname(tarFile))}`);
1797
2058
  const diffDir = `${downloadDir}-diff`;
@@ -1804,7 +2065,7 @@ class BlockletManager extends BaseBlockletManager {
1804
2065
  throw error;
1805
2066
  }
1806
2067
  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);
2068
+ await fs.copy(getBundleDir(this.installDir, bundle), downloadDir);
1808
2069
  try {
1809
2070
  // delete
1810
2071
  logger.info('Delete files from downloadDir', { fileNum: deleteSet.length });
@@ -1833,11 +2094,11 @@ class BlockletManager extends BaseBlockletManager {
1833
2094
 
1834
2095
  await ensureBlockletExpanded(meta, downloadDir);
1835
2096
 
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 });
2097
+ const bundleDir = getBundleDir(this.installDir, meta);
2098
+ logger.info('Move downloadDir to installDir', { downloadDir, bundleDir });
2099
+ await fs.move(downloadDir, bundleDir, { overwrite: true });
1839
2100
 
1840
- return { meta, installDir };
2101
+ return { meta, installDir: bundleDir };
1841
2102
  } catch (error) {
1842
2103
  fs.removeSync(downloadDir);
1843
2104
  fs.removeSync(diffDir);
@@ -1868,7 +2129,7 @@ class BlockletManager extends BaseBlockletManager {
1868
2129
  hooks.preInstall({
1869
2130
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1870
2131
  env: { ...nodeEnvironments },
1871
- appDir: blocklet.env.appDir,
2132
+ appDir: b.env.appDir,
1872
2133
  did, // root blocklet did
1873
2134
  notification: states.notification,
1874
2135
  context,
@@ -1884,7 +2145,7 @@ class BlockletManager extends BaseBlockletManager {
1884
2145
  hooks.postInstall({
1885
2146
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1886
2147
  env: getRuntimeEnvironments(b, nodeEnvironments, blocklet),
1887
- appDir: blocklet.env.appDir,
2148
+ appDir: b.env.appDir,
1888
2149
  did, // root blocklet did
1889
2150
  notification: states.notification,
1890
2151
  context,
@@ -1968,7 +2229,7 @@ class BlockletManager extends BaseBlockletManager {
1968
2229
  hooks.preInstall({
1969
2230
  hooks: Object.assign(b.meta.hooks || {}, b.meta.scripts || {}),
1970
2231
  env: { ...nodeEnvironments },
1971
- appDir: blocklet.env.appDir,
2232
+ appDir: b.env.appDir,
1972
2233
  did, // root blocklet did
1973
2234
  notification: states.notification,
1974
2235
  context,
@@ -2053,6 +2314,12 @@ class BlockletManager extends BaseBlockletManager {
2053
2314
  logger.error('emit upgrade notification failed', { name, version, error });
2054
2315
  }
2055
2316
 
2317
+ // Update dynamic component meta in blocklet settings
2318
+ const dynamicChildren = blocklet.children
2319
+ .filter((x) => x.dynamic)
2320
+ .map((x) => pick(x, ['meta', 'mountPoint', 'sourceUrl', 'source']));
2321
+ await states.blockletExtras.setSettings(did, { children: dynamicChildren });
2322
+
2056
2323
  return blocklet;
2057
2324
  } catch (err) {
2058
2325
  const b = await this._rollback(action, did, oldBlocklet);
@@ -2259,12 +2526,18 @@ class BlockletManager extends BaseBlockletManager {
2259
2526
 
2260
2527
  for (const child of blocklet.children) {
2261
2528
  const oldChild = oldChildren[child.meta.did];
2262
- if (!oldChild || oldChild.meta.dist.integrity !== child.meta.dist.integrity) {
2529
+ if (
2530
+ !oldChild ||
2531
+ (![BlockletSource.upload, BlockletSource.local].includes(child.source) &&
2532
+ child.sourceUrl &&
2533
+ get(oldChild, 'meta.dist.integrity') !== get(child, 'meta.dist.integrity'))
2534
+ ) {
2263
2535
  metas.push(child.meta);
2264
2536
  }
2265
2537
  }
2266
2538
 
2267
2539
  try {
2540
+ logger.info('Download Blocklet', { name, did, bundles: metas.map((x) => get(x, 'dist.tarball')) });
2268
2541
  const tasks = [];
2269
2542
  for (const meta of metas) {
2270
2543
  const url = await this.registry.resolveTarballURL({
@@ -2332,7 +2605,7 @@ class BlockletManager extends BaseBlockletManager {
2332
2605
  async _rollback(action, did, oldBlocklet) {
2333
2606
  if (action === 'install') {
2334
2607
  // remove blocklet
2335
- return this._deleteBlocklet({ did, keepData: false });
2608
+ return this._deleteBlocklet({ did, keepData: true });
2336
2609
  }
2337
2610
 
2338
2611
  if (['upgrade', 'downgrade'].includes(action)) {
@@ -2374,7 +2647,18 @@ class BlockletManager extends BaseBlockletManager {
2374
2647
  const result = await states.blocklet.deleteBlocklet(did);
2375
2648
  logger.info('blocklet removed successfully', { did });
2376
2649
 
2377
- this.emit(BlockletEvents.removed, { blocklet: result, context });
2650
+ let keepRouting = true;
2651
+ if (keepData === false || keepConfigs === false) {
2652
+ keepRouting = false;
2653
+ }
2654
+
2655
+ this.emit(BlockletEvents.removed, {
2656
+ blocklet: result,
2657
+ context: {
2658
+ ...context,
2659
+ keepRouting,
2660
+ },
2661
+ });
2378
2662
  return blocklet;
2379
2663
  }
2380
2664
 
@@ -2395,6 +2679,20 @@ class BlockletManager extends BaseBlockletManager {
2395
2679
  await states.blockletExtras.delChildConfigs(did, child.meta.did);
2396
2680
  }
2397
2681
  }
2682
+
2683
+ async _findNextCustomBlockletName(leftTimes = 10) {
2684
+ if (leftTimes <= 0) {
2685
+ throw new Error('Generate custom blocklet did too many times');
2686
+ }
2687
+ const number = await states.node.increaseCustomBlockletNumber();
2688
+ const name = `custom-${number}`;
2689
+ const did = toBlockletDid(name);
2690
+ const blocklet = await states.blocklet.getBlocklet(did);
2691
+ if (blocklet) {
2692
+ return this._findNextCustomBlockletName(leftTimes - 1);
2693
+ }
2694
+ return { did, name };
2695
+ }
2398
2696
  }
2399
2697
 
2400
2698
  module.exports = BlockletManager;