@abtnode/core 1.8.69-beta-54faead3 → 1.8.69-beta-76f8a46f

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.
@@ -7,33 +7,19 @@ const get = require('lodash/get');
7
7
  const merge = require('lodash/merge');
8
8
  const pick = require('lodash/pick');
9
9
  const cloneDeep = require('lodash/cloneDeep');
10
- const semver = require('semver');
11
- const capitalize = require('lodash/capitalize');
12
- const { Throttle } = require('stream-throttle');
13
- const LRU = require('lru-cache');
14
- const joi = require('joi');
15
10
  const { isNFTExpired, getNftExpirationDate } = require('@abtnode/util/lib/nft');
16
11
  const didDocument = require('@abtnode/util/lib/did-document');
17
12
  const { sign } = require('@arcblock/jwt');
18
- const { isValid: isValidDid } = require('@arcblock/did');
19
13
  const { toSvg: createDidLogo } =
20
14
  process.env.NODE_ENV !== 'test' ? require('@arcblock/did-motif') : require('@arcblock/did-motif/dist/did-motif.cjs');
21
15
  const getBlockletInfo = require('@blocklet/meta/lib/info');
22
16
  const sleep = require('@abtnode/util/lib/sleep');
23
17
 
24
18
  const logger = require('@abtnode/logger')('@abtnode/core:blocklet:manager');
25
- const {
26
- WHO_CAN_ACCESS,
27
- SERVER_ROLES,
28
- WHO_CAN_ACCESS_PREFIX_ROLES,
29
- BLOCKLET_INSTALL_TYPE,
30
- NODE_MODES,
31
- } = require('@abtnode/constant');
19
+ const { WHO_CAN_ACCESS, WHO_CAN_ACCESS_PREFIX_ROLES, BLOCKLET_INSTALL_TYPE, NODE_MODES } = require('@abtnode/constant');
32
20
 
33
21
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
34
22
  const {
35
- isFreeBlocklet,
36
- isComponentBlocklet,
37
23
  isDeletableBlocklet,
38
24
  getAppMissingConfigs,
39
25
  hasRunnableComponent,
@@ -45,15 +31,10 @@ const {
45
31
  getRolesFromAuthConfig,
46
32
  } = require('@blocklet/meta/lib/util');
47
33
  const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
48
- const toBlockletDid = require('@blocklet/meta/lib/did');
49
- const { validateMeta } = require('@blocklet/meta/lib/validate');
50
34
  const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
51
35
  const { titleSchema, updateMountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
52
- const hasReservedKey = require('@blocklet/meta/lib/has-reserved-key');
53
36
  const Lock = require('@abtnode/util/lib/lock');
54
37
 
55
- const { toExternalBlocklet } = toBlockletDid;
56
-
57
38
  const {
58
39
  BlockletStatus,
59
40
  BlockletSource,
@@ -79,7 +60,6 @@ const {
79
60
  getFromCache: getAccessibleExternalNodeIp,
80
61
  } = require('../../util/get-accessible-external-node-ip');
81
62
  const {
82
- getBlockletMetaFromUrl,
83
63
  getAppSystemEnvironments,
84
64
  getComponentSystemEnvironments,
85
65
  getAppOverwrittenEnvironments,
@@ -94,45 +74,45 @@ const {
94
74
  statusMap,
95
75
  pruneBlockletBundle,
96
76
  getDiskInfo,
97
- getUpdateMetaList,
98
77
  getRuntimeEnvironments,
99
78
  getTypeFromInstallParams,
100
- parseChildrenFromMeta,
101
- checkDuplicateComponents,
102
- getDiffFiles,
79
+ parseComponents,
80
+ filterDuplicateComponents,
103
81
  getBundleDir,
104
- findAvailableDid,
105
- ensureMeta,
106
82
  getBlocklet,
107
83
  ensureEnvDefault,
108
84
  getConfigFromPreferences,
109
85
  consumeServerlessNFT,
110
86
  validateAppConfig,
111
- checkDuplicateAppSk,
112
87
  checkDuplicateMountPoint,
113
88
  validateStore,
114
- validateInServerless,
115
89
  isRotatingAppSk,
116
90
  isRotatingAppDid,
91
+ checkVersionCompatibility,
92
+ getBlockletKnownAs,
117
93
  } = require('../../util/blocklet');
118
- const StoreUtil = require('../../util/store');
119
94
  const states = require('../../states');
120
95
  const BaseBlockletManager = require('./base');
121
96
  const { get: getEngine } = require('./engine');
122
97
  const blockletPm2Events = require('./pm2-events');
123
- const { getFactoryState } = require('../../util/chain');
124
98
  const runMigrationScripts = require('../migration');
125
99
  const hooks = require('../hooks');
126
- const { formatName, getDidDomainForBlocklet } = require('../../util/get-domain-for-blocklet');
100
+ const { getDidDomainForBlocklet } = require('../../util/get-domain-for-blocklet');
127
101
  const handleInstanceInStore = require('../../util/public-to-store');
128
102
  const { BlockletRuntimeMonitor } = require('../../monitor/blocklet-runtime-monitor');
129
103
  const getHistoryList = require('../../monitor/get-history-list');
130
104
  const { SpacesBackup } = require('../storage/backup/spaces');
131
105
  const { SpacesRestore } = require('../storage/restore/spaces');
132
- const installFromBackup = require('./helper/install-from-backup');
133
- const { resolveDownload, resolveDiffDownload } = require('../downloader/resolve-download');
106
+ const { installApplicationFromGeneral } = require('./helper/install-application-from-general');
107
+ const { installApplicationFromDev } = require('./helper/install-application-from-dev');
108
+ const { installApplicationFromBackup } = require('./helper/install-application-from-backup');
109
+ const { installComponentFromDev } = require('./helper/install-component-from-dev');
110
+ const { installComponentFromUrl } = require('./helper/install-component-from-url');
111
+ const { installComponentFromUpload, diff } = require('./helper/install-component-from-upload');
112
+ const UpgradeComponents = require('./helper/upgrade-components');
134
113
  const BlockletDownloader = require('../downloader/blocklet-downloader');
135
114
  const RollbackCache = require('./helper/rollback-cache');
115
+ const { migrateApplicationToStructV2 } = require('./helper/migrate-application-to-struct-v2');
136
116
 
137
117
  const {
138
118
  isInProgress,
@@ -140,7 +120,6 @@ const {
140
120
  formatEnvironments,
141
121
  shouldUpdateBlockletStatus,
142
122
  getBlockletMeta,
143
- validateBlockletMeta,
144
123
  validateOwner,
145
124
  } = util;
146
125
 
@@ -183,9 +162,6 @@ const getSkippedProcessIds = ({ newBlocklet, oldBlocklet, context = {} }) => {
183
162
  return res;
184
163
  };
185
164
 
186
- const getBlockletIndex = (meta, controller) =>
187
- controller ? toExternalBlocklet(meta.name, controller.nftId) : { did: meta.did, name: meta.name };
188
-
189
165
  // 10s 上报统计一次
190
166
  const MONITOR_RECORD_INTERVAL_SEC = 10;
191
167
 
@@ -209,12 +185,6 @@ class BlockletManager extends BaseBlockletManager {
209
185
  // cached installed blocklets for performance
210
186
  this.cachedBlocklets = null;
211
187
 
212
- // cached blocklet latest versions from each registries
213
- this.cachedBlockletVersions = new LRU({
214
- max: 40, // cache at most 40 blocklets
215
- maxAge: process.env.NODE_ENV === 'test' ? 500 : 0.5 * 60 * 1000, // cache for 0.5 minute
216
- });
217
-
218
188
  this.runtimeMonitor = new BlockletRuntimeMonitor({ historyLength: MONITOR_HISTORY_LENGTH, states });
219
189
 
220
190
  this.blockletDownloader = new BlockletDownloader({
@@ -232,7 +202,7 @@ class BlockletManager extends BaseBlockletManager {
232
202
  }
233
203
 
234
204
  // ============================================================================================
235
- // Public API that can be call from GQL, should have the same signature: doXXX(params, context)
205
+ // Public API for Installing/Upgrading Application or Components
236
206
  // ============================================================================================
237
207
 
238
208
  /**
@@ -240,9 +210,6 @@ class BlockletManager extends BaseBlockletManager {
240
210
  *
241
211
  * @param {{
242
212
  * url: string;
243
- * file: string;
244
- * diffVersion: string;
245
- * deleteSet: Array<string>;
246
213
  * did: string;
247
214
  * title: string;
248
215
  * description: string;
@@ -264,6 +231,13 @@ class BlockletManager extends BaseBlockletManager {
264
231
  async install(params, context = {}) {
265
232
  logger.debug('install blocklet', { params, context });
266
233
 
234
+ const type = getTypeFromInstallParams(params);
235
+
236
+ const { appSk } = params;
237
+ if (!appSk) {
238
+ throw new Error('appSk is required');
239
+ }
240
+
267
241
  if (!params.controller && context?.user?.controller) {
268
242
  params.controller = context.user.controller;
269
243
  }
@@ -280,40 +254,21 @@ class BlockletManager extends BaseBlockletManager {
280
254
  });
281
255
  context.downloadTokenList = params.downloadTokenList || [];
282
256
 
283
- const type = getTypeFromInstallParams(params);
284
257
  if (typeof context.startImmediately === 'undefined') {
285
258
  context.startImmediately = !!params.startImmediately;
286
259
  }
287
260
 
288
- const { appSk } = params;
289
-
290
- if (type === BLOCKLET_INSTALL_TYPE.URL) {
291
- const { url, controller, sync, delay } = params;
292
- return this._installFromUrl({ url, controller, sync, delay, appSk }, context);
293
- }
294
-
295
- if (type === BLOCKLET_INSTALL_TYPE.UPLOAD) {
296
- const { file, did, diffVersion, deleteSet } = params;
297
- return this._installFromUpload({ file, did, diffVersion, deleteSet, appSk }, context);
298
- }
299
-
300
- if (type === BLOCKLET_INSTALL_TYPE.STORE) {
301
- const { did, controller, sync, delay, storeUrl } = params;
302
- return this._installFromStore({ did, controller, sync, delay, storeUrl, appSk }, context);
303
- }
304
-
305
- if (type === BLOCKLET_INSTALL_TYPE.CREATE) {
306
- const { title, description } = params;
307
- return this._installFromCreate({ title, description, appSk }, context);
308
- }
309
-
310
261
  if (type === BLOCKLET_INSTALL_TYPE.RESTORE) {
311
262
  const { url } = params;
312
- return this._installFromBackup({ url, appSk }, context);
263
+ return installApplicationFromBackup({ url, appSk, context, manager: this, states });
264
+ }
265
+
266
+ if ([BLOCKLET_INSTALL_TYPE.URL, BLOCKLET_INSTALL_TYPE.STORE, BLOCKLET_INSTALL_TYPE.CREATE].includes(type)) {
267
+ return installApplicationFromGeneral({ ...params, type, context, manager: this, states });
313
268
  }
314
269
 
315
270
  // should not be here
316
- throw new Error('Unknown type');
271
+ throw new Error(`install from ${type} is not supported`);
317
272
  }
318
273
 
319
274
  /**
@@ -334,7 +289,6 @@ class BlockletManager extends BaseBlockletManager {
334
289
  * @param {String} name custom component name
335
290
  *
336
291
  * @param {ConfigEntry} configs pre configs
337
- * @param {Boolean} skipNavigation
338
292
  */
339
293
  async installComponent(
340
294
  {
@@ -348,8 +302,8 @@ class BlockletManager extends BaseBlockletManager {
348
302
  title,
349
303
  name,
350
304
  configs,
305
+ sync,
351
306
  downloadTokenList,
352
- skipNavigation,
353
307
  },
354
308
  context = {}
355
309
  ) {
@@ -363,15 +317,16 @@ class BlockletManager extends BaseBlockletManager {
363
317
  throw new Error("Can't install component in serverless-mode server via upload");
364
318
  }
365
319
 
366
- return this._installComponentFromUpload({
320
+ return installComponentFromUpload({
367
321
  rootDid,
368
322
  mountPoint,
369
323
  file,
370
324
  did,
371
325
  diffVersion,
372
326
  deleteSet,
373
- skipNavigation,
374
327
  context,
328
+ states,
329
+ manager: this,
375
330
  });
376
331
  }
377
332
 
@@ -381,7 +336,7 @@ class BlockletManager extends BaseBlockletManager {
381
336
  validateStore(info, url);
382
337
  }
383
338
 
384
- return this._installComponentFromUrl({
339
+ return installComponentFromUrl({
385
340
  rootDid,
386
341
  mountPoint,
387
342
  url,
@@ -390,8 +345,10 @@ class BlockletManager extends BaseBlockletManager {
390
345
  did,
391
346
  name,
392
347
  configs,
348
+ sync,
393
349
  downloadTokenList,
394
- skipNavigation,
350
+ states,
351
+ manager: this,
395
352
  });
396
353
  }
397
354
 
@@ -399,57 +356,49 @@ class BlockletManager extends BaseBlockletManager {
399
356
  throw new Error('Unknown source');
400
357
  }
401
358
 
402
- // eslint-disable-next-line no-unused-vars
403
- async getMetaFromUrl({ url, checkPrice = false }, context) {
404
- const meta = await getBlockletMetaFromUrl(url);
405
- let isFree = isFreeBlocklet(meta);
359
+ async diff({ did, hashFiles, rootDid }) {
360
+ return diff({ did, hashFiles, rootDid, states });
361
+ }
406
362
 
407
- if (checkPrice && !isFree && meta.nftFactory) {
408
- try {
409
- const registryMeta = await StoreUtil.getRegistryMeta(new URL(url).origin);
363
+ /**
364
+ * After the dev function finished, the caller should send a BlockletEvents.installed event to the daemon
365
+ * @returns {Object} blocklet
366
+ */
367
+ async dev(folder, { rootDid, mountPoint, defaultStoreUrl } = {}) {
368
+ logger.info('dev component', { folder, rootDid, mountPoint });
410
369
 
411
- if (registryMeta.chainHost) {
412
- const state = await getFactoryState(registryMeta.chainHost, meta.nftFactory);
413
- if (state) {
414
- isFree = false;
415
- }
416
- }
417
- } catch (error) {
418
- logger.warn('failed when checking if the blocklet is free', { did: meta.did, error });
419
- }
370
+ const meta = getBlockletMeta(folder, { defaultStoreUrl });
371
+ if (meta.group !== 'static' && (!meta.scripts || !meta.scripts.dev)) {
372
+ throw new Error('Incorrect blocklet.yml: missing `scripts.dev` field');
420
373
  }
421
374
 
422
- const { inStore, registryUrl } = await StoreUtil.parseSourceUrl(url);
423
-
424
- return { meta, isFree, inStore, registryUrl };
425
- }
426
-
427
- async getBlockletByBundle({ did, name, serverlessNftId }, context) {
428
- if (toBlockletDid(name) !== did) {
429
- throw new Error('did and name does not match');
375
+ if (!rootDid) {
376
+ return installApplicationFromDev({ folder, meta, manager: this, states });
430
377
  }
431
378
 
432
- if (!context?.user) {
433
- throw new Error('user does not exist');
434
- }
379
+ return installComponentFromDev({ folder, meta, rootDid, mountPoint, manager: this, states });
380
+ }
435
381
 
436
- if (context.user.role === SERVER_ROLES.EXTERNAL_BLOCKLET_CONTROLLER && !serverlessNftId) {
437
- throw new Error('serverless nft id is required');
438
- }
382
+ async checkComponentsForUpdates({ did }) {
383
+ return UpgradeComponents.check({ did, states });
384
+ }
439
385
 
440
- let blockletDid = did;
441
- let isExternal = false;
386
+ async upgradeComponents({ updateId, selectedComponents: selectedComponentDids }, context = {}) {
387
+ return UpgradeComponents.upgrade({ updateId, selectedComponentDids, context, states, manager: this });
388
+ }
442
389
 
443
- if (serverlessNftId) {
444
- blockletDid = toExternalBlocklet(name, serverlessNftId, { didOnly: true });
445
- isExternal = true;
446
- }
390
+ async migrateApplicationToStructV2({ did, appSk, context = {} }) {
391
+ return migrateApplicationToStructV2({ did, appSk, context, manager: this, states });
392
+ }
447
393
 
448
- const blocklet = await states.blocklet.getBlocklet(blockletDid);
394
+ // ============================================================================================
395
+ // Public API for GQL or internal
396
+ // ============================================================================================
449
397
 
398
+ async getBlockletForLauncher({ did }) {
399
+ const blocklet = await states.blocklet.getBlocklet(did);
450
400
  const isRunning = blocklet ? blocklet.status === BlockletStatus.running : false;
451
-
452
- return { isInstalled: !!blocklet, isRunning, blockletDid, isExternal };
401
+ return { did, isInstalled: !!blocklet, isRunning };
453
402
  }
454
403
 
455
404
  async start({ did, throwOnError, checkHealthImmediately = false, e2eMode = false }, context) {
@@ -524,7 +473,7 @@ class BlockletManager extends BaseBlockletManager {
524
473
  };
525
474
 
526
475
  if (checkHealthImmediately) {
527
- await this.onCheckIfStarted(params, { throwOnError });
476
+ await this._onCheckIfStarted(params, { throwOnError });
528
477
  } else {
529
478
  this.startQueue.push({
530
479
  entity: 'blocklet',
@@ -662,15 +611,23 @@ class BlockletManager extends BaseBlockletManager {
662
611
  const params = await spacesRestore.restore();
663
612
 
664
613
  this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, message: 'Installing blocklet...' });
665
- await this._installFromBackup({
614
+ await installApplicationFromBackup({
666
615
  url: `file://${spacesRestore.restoreDir}`,
667
616
  moveDir: true,
668
617
  ...merge(...params),
618
+ manager: this,
619
+ states,
669
620
  });
670
621
 
671
622
  this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, completed: true });
672
623
  }
673
624
 
625
+ /**
626
+ *
627
+ * @param {import('@abtnode/client').RequestBlockletInput} param0
628
+ * @param {Record<string, any>} context
629
+ * @returns {import('@abtnode/client').BlockletState}
630
+ */
674
631
  async restart({ did }, context) {
675
632
  logger.info('restart blocklet', { did });
676
633
 
@@ -714,8 +671,9 @@ class BlockletManager extends BaseBlockletManager {
714
671
  async delete({ did, keepData, keepLogsDir, keepConfigs }, context) {
715
672
  logger.info('delete blocklet', { did, keepData });
716
673
 
674
+ const blocklet = await this.getBlocklet(did);
675
+
717
676
  try {
718
- const blocklet = await this.getBlocklet(did);
719
677
  if (isDeletableBlocklet(blocklet) === false) {
720
678
  throw new Error('Blocklet is protected from accidental deletion');
721
679
  }
@@ -785,7 +743,7 @@ class BlockletManager extends BaseBlockletManager {
785
743
  // Reset config in db
786
744
  await states.blockletExtras.remove({ did: blocklet.meta.did });
787
745
  await this._setConfigsFromMeta(did);
788
- await this.updateBlockletEnvironment(did);
746
+ await this._updateBlockletEnvironment(did);
789
747
  await this.resetSiteByDid(did, context);
790
748
  } else {
791
749
  const child = blocklet.children.find((x) => x.meta.did === childDid);
@@ -803,7 +761,7 @@ class BlockletManager extends BaseBlockletManager {
803
761
  // Reset config in db
804
762
  await states.blockletExtras.delConfigs([blocklet.meta.did, child.meta.did]);
805
763
  await this._setConfigsFromMeta(blocklet.meta.did, child.meta.did);
806
- await this.updateBlockletEnvironment(did);
764
+ await this._updateBlockletEnvironment(did);
807
765
  }
808
766
 
809
767
  logger.info('blocklet reset', { did, childDid });
@@ -814,6 +772,7 @@ class BlockletManager extends BaseBlockletManager {
814
772
  logger.info('delete blocklet component', { did, rootDid, keepData });
815
773
 
816
774
  const blocklet = await this.getBlocklet(rootDid);
775
+
817
776
  const child = blocklet.children.find((x) => x.meta.did === did);
818
777
  if (!child) {
819
778
  throw new Error('Component does not exist');
@@ -822,9 +781,9 @@ class BlockletManager extends BaseBlockletManager {
822
781
  // delete state
823
782
  const doc = await states.blocklet.getBlocklet(rootDid);
824
783
  doc.children = doc.children.filter((x) => x.meta.did !== did);
825
- const children = (await this._getDynamicChildrenFromSettings(blocklet.meta.did)).filter((x) => x.meta.did !== did);
784
+ const deletedChildren = await states.blockletExtras.getSettings(did, 'children', []);
826
785
  if (keepData !== false && keepState !== false) {
827
- children.push({
786
+ deletedChildren.push({
828
787
  meta: pick(child.meta, ['did', 'name', 'bundleDid', 'bundleName', 'version', 'title', 'description']),
829
788
  mountPoint: child.mountPoint,
830
789
  status: BlockletStatus.deleted,
@@ -833,7 +792,7 @@ class BlockletManager extends BaseBlockletManager {
833
792
  }
834
793
 
835
794
  await states.blocklet.updateBlocklet(rootDid, doc);
836
- states.blockletExtras.setSettings(doc.meta.did, { children });
795
+ states.blockletExtras.setSettings(doc.meta.did, { children: deletedChildren });
837
796
 
838
797
  // delete process
839
798
  try {
@@ -849,14 +808,6 @@ class BlockletManager extends BaseBlockletManager {
849
808
  logger.error('delete blocklet process for deleting component', { did, rootDid, error: err });
850
809
  }
851
810
 
852
- // delete navigation
853
- const navigation = await states.blockletExtras.getSettings(blocklet.meta.did, 'navigation', []);
854
- await states.blockletExtras.setSettings(
855
- blocklet.meta.did,
856
- 'navigation',
857
- navigation.filter((x) => x.child !== blocklet.meta.name)
858
- );
859
-
860
811
  // delete storage
861
812
  const childBlocklet = blocklet.children.find((x) => x.meta.did === did);
862
813
  const { cacheDir, logsDir, dataDir } = childBlocklet.env;
@@ -868,6 +819,9 @@ class BlockletManager extends BaseBlockletManager {
868
819
  }
869
820
 
870
821
  const newBlocklet = await this.getBlocklet(rootDid);
822
+
823
+ await this._updateDependents(rootDid);
824
+
871
825
  this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
872
826
 
873
827
  this._createNotification(newBlocklet.meta.did, {
@@ -882,8 +836,7 @@ class BlockletManager extends BaseBlockletManager {
882
836
  return newBlocklet;
883
837
  }
884
838
 
885
- // eslint-disable-next-line no-unused-vars
886
- async cancelDownload({ did: inputDid }, context) {
839
+ async cancelDownload({ did: inputDid }) {
887
840
  try {
888
841
  await statusLock.acquire();
889
842
  const blocklet = await states.blocklet.getBlocklet(inputDid);
@@ -1041,6 +994,7 @@ class BlockletManager extends BaseBlockletManager {
1041
994
  // CAUTION: this method currently only support config by blocklet.meta.did
1042
995
  // eslint-disable-next-line no-unused-vars
1043
996
  async config({ did, configs: newConfigs, skipHook, skipDidDocument }, context) {
997
+ // todo: skipDidDocument will be deleted
1044
998
  if (!Array.isArray(newConfigs)) {
1045
999
  throw new Error('configs list is not an array');
1046
1000
  }
@@ -1116,12 +1070,11 @@ class BlockletManager extends BaseBlockletManager {
1116
1070
  this.emit(BlockletEvents.spaceConnected, blocklet);
1117
1071
  }
1118
1072
 
1119
- // FIXME: @zhenqiang best way to handle the did document: allow all appDids to resolve
1120
1073
  if (willAppDidChange && !skipDidDocument) {
1121
1074
  await this._updateDidDocument(blocklet);
1122
1075
  }
1123
1076
 
1124
- await this.updateBlockletEnvironment(rootDid);
1077
+ await this._updateBlockletEnvironment(rootDid);
1125
1078
 
1126
1079
  // response
1127
1080
  const newState = await this.getBlocklet(rootDid);
@@ -1224,23 +1177,7 @@ class BlockletManager extends BaseBlockletManager {
1224
1177
  throw new Error('component does not exist');
1225
1178
  }
1226
1179
 
1227
- if (!component.dynamic) {
1228
- throw new Error('cannot update title of non-dynamic component');
1229
- }
1230
-
1231
1180
  component.meta.title = title;
1232
-
1233
- const navigation = await states.blockletExtras.getSettings(rootDid, 'navigation', []);
1234
- const nav = navigation.find((x) => x.child === component.meta.name);
1235
-
1236
- if (nav) {
1237
- nav.title = title;
1238
- }
1239
-
1240
- // update db
1241
- if (nav) {
1242
- await states.blockletExtras.setSettings(rootDid, { navigation });
1243
- }
1244
1181
  await states.blocklet.updateBlocklet(rootDid, { children });
1245
1182
 
1246
1183
  // trigger meta.js refresh
@@ -1260,6 +1197,7 @@ class BlockletManager extends BaseBlockletManager {
1260
1197
  }
1261
1198
 
1262
1199
  const rootDid = blocklet.meta.did;
1200
+
1263
1201
  const isRootComponent = !did;
1264
1202
 
1265
1203
  const component = isRootComponent ? blocklet : blocklet.children.find((x) => x.meta.did === did);
@@ -1267,10 +1205,6 @@ class BlockletManager extends BaseBlockletManager {
1267
1205
  throw new Error('component does not exist');
1268
1206
  }
1269
1207
 
1270
- if (!isRootComponent && !component.dynamic) {
1271
- throw new Error('cannot update mountPoint of non-dynamic component');
1272
- }
1273
-
1274
1208
  if (isRootComponent && component.group === BlockletGroup.gateway) {
1275
1209
  throw new Error('cannot update mountPoint of gateway blocklet');
1276
1210
  }
@@ -1286,550 +1220,172 @@ class BlockletManager extends BaseBlockletManager {
1286
1220
  return this.getBlocklet(rootDid);
1287
1221
  }
1288
1222
 
1289
- /**
1290
- * upgrade blocklet from registry
1291
- */
1292
- async upgrade({ did, storeUrl, sync }, context) {
1293
- const blocklet = await states.blocklet.getBlocklet(did);
1294
-
1295
- if (!storeUrl && blocklet.source === BlockletSource.url) {
1296
- return this._installFromUrl({ url: blocklet.deployedFrom }, context);
1297
- }
1298
-
1299
- // TODO: 查看了下目前页面中的升级按钮,都是会传 storeUrl 过来的,这个函数里的逻辑感觉需要在以后做一个简化
1300
- if (!storeUrl && blocklet.source !== BlockletSource.registry) {
1301
- throw new Error('Wrong upgrade source, empty storeUrl or not installed from blocklet registry');
1302
- }
1303
-
1304
- const upgradeFromStore = storeUrl || blocklet.deployedFrom;
1305
-
1306
- let newVersionMeta = await StoreUtil.getBlockletMeta({
1307
- did: blocklet.meta.bundleDid,
1308
- storeUrl: upgradeFromStore,
1309
- });
1310
- newVersionMeta = ensureMeta(newVersionMeta, { name: blocklet.meta.name, did: blocklet.meta.did });
1311
-
1312
- function getSignature(signatures = [], oldSignatures = []) {
1313
- // if blocklet installed from local, upload, url, the signature is undefined, should return null
1314
- if (!Array.isArray(signatures) || signatures.length === 0) {
1315
- return null;
1316
- }
1317
- if (signatures.length > 3) {
1318
- throw new Error('Invalid blocklet signature length');
1319
- }
1320
- // if old signature is old registry version, return new signatures last one signature
1321
- if (oldSignatures.length > 0 && oldSignatures.length < 3) {
1322
- return signatures[signatures.length - 1];
1323
- }
1324
- // old registry signatures: [ registry 签名, developer-sk 签名]
1325
- // new registry signatures [ registry 签名, user wallet 签名, access-token 签名 ]
1326
- // old -> old: 需要对比 developer-sk 签名
1327
- // old -> new: 需要对比 developer-sk 和 access-token 签名
1328
- // new -> new: 需要对比 user-wallet 签名
1329
- return signatures.length === 1 ? signatures[0] : signatures[1];
1330
- }
1331
-
1332
- const currentDeveloperSignature = getSignature(blocklet.meta.signatures);
1333
- const newVersionDeveloperSignature = getSignature(newVersionMeta.signatures, blocklet.meta.signatures);
1334
-
1335
- if (!newVersionDeveloperSignature) {
1336
- throw new Error('Invalid upgrade blocklet signature');
1337
- }
1338
-
1339
- if (
1340
- currentDeveloperSignature &&
1341
- blocklet.source === BlockletSource.registry &&
1342
- (currentDeveloperSignature.signer !== newVersionDeveloperSignature.signer ||
1343
- currentDeveloperSignature.pk !== newVersionDeveloperSignature.pk)
1344
- ) {
1345
- logger.error('invalid developer signature', { did, currentDeveloperSignature, newVersionDeveloperSignature });
1346
- throw new Error('Invalid developer signature');
1347
- }
1348
-
1349
- if (blocklet.meta.version === newVersionMeta.version) {
1350
- throw new Error('Upgrade/downgrade blocklet to same version is noop');
1351
- }
1223
+ // eslint-disable-next-line no-unused-vars
1224
+ async getRuntimeHistory({ did, hours }, context) {
1225
+ const metaDid = await states.blocklet.getBlockletMetaDid(did);
1352
1226
 
1353
- const action = semver.gt(blocklet.meta.version, newVersionMeta.version) ? 'downgrade' : 'upgrade';
1354
- logger.info(`${action} blocklet`, { did });
1227
+ const history = this.runtimeMonitor.getHistory(metaDid);
1355
1228
 
1356
- return this._upgrade({
1357
- meta: newVersionMeta,
1358
- source: BlockletSource.registry,
1359
- deployedFrom: upgradeFromStore,
1360
- context,
1361
- sync,
1229
+ return getHistoryList({
1230
+ history,
1231
+ hours,
1232
+ recordIntervalSec: MONITOR_RECORD_INTERVAL_SEC,
1233
+ props: ['date', 'cpu', 'mem'],
1362
1234
  });
1363
1235
  }
1364
1236
 
1365
- // eslint-disable-next-line no-unused-vars
1366
- async diff({ did, hashFiles: clientFiles, rootDid: inputRootDid }, context) {
1367
- if (!did) {
1368
- throw new Error('did is empty');
1369
- }
1370
-
1371
- if (!clientFiles || !clientFiles.length) {
1372
- throw new Error('hashFiles is empty');
1373
- }
1374
-
1375
- const rootDid = inputRootDid || did;
1376
- const childDid = inputRootDid ? did : '';
1237
+ async ensureBlocklet(did, opts = {}) {
1238
+ return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did, ensureIntegrity: true });
1239
+ }
1377
1240
 
1378
- if (childDid === rootDid) {
1379
- throw new Error('Cannot add self as a component');
1380
- }
1241
+ async getBlocklet(did, opts = {}) {
1242
+ return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did, ensureIntegrity: false });
1243
+ }
1381
1244
 
1382
- logger.info('Get blocklet diff', { rootDid, childDid, clientFilesNumber: clientFiles.length });
1245
+ async hasBlocklet({ did }) {
1246
+ return states.blocklet.hasBlocklet(did);
1247
+ }
1383
1248
 
1384
- const rootBlocklet = await states.blocklet.getBlocklet(rootDid);
1385
- if (childDid && !rootBlocklet) {
1386
- throw new Error('Root blocklet does not exist');
1249
+ async setInitialized({ did, owner }) {
1250
+ if (!validateOwner(owner)) {
1251
+ throw new Error('Blocklet owner is invalid');
1387
1252
  }
1388
1253
 
1389
- const state = childDid ? await (rootBlocklet.children || []).find((x) => x.meta.did === childDid) : rootBlocklet;
1254
+ const blocklet = await states.blocklet.getBlocklet(did);
1255
+ await states.blockletExtras.setSettings(blocklet.meta.did, { initialized: true, owner });
1256
+ logger.info('Blocklet initialized', { did, owner });
1390
1257
 
1391
- if (!state) {
1392
- return {
1393
- hasBlocklet: false,
1394
- };
1395
- }
1258
+ this.emit(BlockletEvents.updated, { meta: { did: blocklet.meta.did } });
1396
1259
 
1397
- if (state.source === BlockletSource.local) {
1398
- throw new Error(`Blocklet ${state.meta.name} is already deployed from local, can not deployed from remote.`);
1399
- }
1260
+ return this.getBlocklet(did);
1261
+ }
1400
1262
 
1401
- const { version } = state.meta;
1402
- const bundleDir = getBundleDir(this.installDir, state.meta);
1403
- const { addSet, changeSet, deleteSet } = await getDiffFiles(clientFiles, bundleDir);
1263
+ async status(did, { forceSync = false } = {}) {
1264
+ const fastReturnOnForceSync = async (blocklet = {}) => {
1265
+ const { status } = blocklet;
1266
+ const { added, waiting, downloading, installing, installed, upgrading } = BlockletStatus;
1404
1267
 
1405
- logger.info('Diff files', {
1406
- name: state.meta.name,
1407
- did: state.meta.did,
1408
- version: state.meta.version,
1409
- addNum: addSet.length,
1410
- changeNum: changeSet.length,
1411
- deleteNum: deleteSet.length,
1412
- });
1268
+ if ([added, waiting, downloading, installing, installed, upgrading].includes(status)) {
1269
+ return blocklet;
1270
+ }
1413
1271
 
1414
- return {
1415
- hasBlocklet: true,
1416
- version,
1417
- addSet,
1418
- changeSet,
1419
- deleteSet,
1272
+ const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
1273
+ this.emit(BlockletEvents.statusChange, res);
1274
+ return res;
1420
1275
  };
1421
- }
1422
1276
 
1423
- async checkChildrenForUpdates({ did }) {
1424
- const blocklet = await states.blocklet.getBlocklet(did);
1425
-
1426
- const newChildren = [];
1277
+ const blocklet = await this.getBlocklet(did);
1427
1278
 
1428
- for (const child of blocklet.children || []) {
1429
- if (child.bundleSource) {
1430
- const config = {
1431
- source: child.bundleSource,
1432
- name: child.meta.name,
1433
- title: child.meta.title,
1434
- mountPoint: child.mountPoint,
1435
- };
1436
- newChildren.push(...(await parseChildrenFromMeta([config], { dynamic: child.dynamic })));
1437
- } else {
1438
- newChildren.push({
1439
- ...child,
1440
- children: await parseChildrenFromMeta(child.meta),
1441
- });
1279
+ let shouldUpdateStatus = forceSync || shouldUpdateBlockletStatus(blocklet.status);
1280
+ if (isInProgress(blocklet.status)) {
1281
+ const uptime = Date.now() - new Date(blocklet.updatedAt).getTime();
1282
+ // if uptime great than 30s, sync the status from pm2
1283
+ if (uptime > 30 * 1000) {
1284
+ shouldUpdateStatus = true;
1442
1285
  }
1443
1286
  }
1444
1287
 
1445
- checkDuplicateComponents(newChildren);
1288
+ if (!shouldUpdateStatus) {
1289
+ return blocklet;
1290
+ }
1446
1291
 
1447
- const updateList = getUpdateMetaList(blocklet, { ...blocklet, children: newChildren });
1292
+ try {
1293
+ const status = await getBlockletStatusFromProcess(blocklet);
1294
+ if (blocklet.status !== status) {
1295
+ const res = await states.blocklet.setBlockletStatus(did, status);
1296
+ this.emit(BlockletEvents.statusChange, res);
1297
+ return res;
1298
+ }
1448
1299
 
1449
- if (!updateList.length) {
1450
- return {};
1300
+ return blocklet;
1301
+ } catch (err) {
1302
+ return fastReturnOnForceSync(blocklet);
1451
1303
  }
1304
+ }
1452
1305
 
1453
- // start session
1454
- const { id: updateId } = await states.session.start({
1455
- did,
1456
- children: newChildren,
1306
+ async refreshListCache() {
1307
+ this.list({ useCache: false }).catch((err) => {
1308
+ logger.error('refresh blocklet list failed', { error: err });
1457
1309
  });
1458
-
1459
- return {
1460
- updateId,
1461
- updateList,
1462
- };
1463
1310
  }
1464
1311
 
1465
- async updateChildren(
1466
- {
1467
- updateId,
1468
- did: inputDid,
1469
- children: inputChildren,
1470
- oldBlocklet: inputOldBlocklet,
1471
- selectedComponents: selectedComponentDids,
1472
- },
1473
- context
1474
- ) {
1475
- let did;
1476
- let children;
1477
- let oldBlocklet;
1478
- if (!updateId && inputDid && inputChildren) {
1479
- did = inputDid;
1480
- children = inputChildren;
1481
- oldBlocklet = inputOldBlocklet;
1482
- } else {
1483
- const sessionData = await states.session.end(updateId);
1484
- did = sessionData.did;
1485
- oldBlocklet = await this._getBlockletForInstallation(did);
1486
-
1487
- if (selectedComponentDids && selectedComponentDids.length) {
1488
- children = cloneDeep(oldBlocklet.children).map((oldComponent) => {
1489
- const newComponent = sessionData.children.find((x) => x.meta.did === oldComponent.meta.did);
1490
- if (newComponent && selectedComponentDids.includes(newComponent.meta.did)) {
1491
- return newComponent;
1492
- }
1493
- return oldComponent;
1494
- });
1495
- } else {
1496
- children = sessionData.children;
1497
- }
1312
+ async updateAllBlockletEnvironment() {
1313
+ const blocklets = await states.blocklet.getBlocklets();
1314
+ for (let i = 0; i < blocklets.length; i++) {
1315
+ const blocklet = blocklets[i];
1316
+ await this._updateBlockletEnvironment(blocklet.meta.did);
1498
1317
  }
1318
+ }
1499
1319
 
1500
- // get old blocklet
1501
- const { meta } = oldBlocklet;
1502
- const { name, version } = meta;
1503
-
1504
- const action = 'upgrade';
1505
-
1506
- logger.info(`${action} blocklet children`, {
1507
- did,
1508
- name,
1509
- version,
1510
- children: children.map((x) => ({ name: x.meta.name, version: x.meta.version })),
1511
- });
1512
-
1513
- // new blocklet
1514
- const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
1515
-
1516
- newBlocklet.children = children;
1517
- await validateBlocklet(newBlocklet);
1518
-
1519
- this.emit(BlockletEvents.statusChange, newBlocklet);
1520
-
1521
- // backup rollback data
1522
- await this._rollbackCache.backup({ did, action, oldBlocklet });
1523
-
1524
- // add to queue
1525
- const ticket = this.installQueue.push(
1526
- {
1527
- entity: 'blocklet',
1528
- action: 'download',
1529
- id: did,
1530
- oldBlocklet: { ...oldBlocklet },
1531
- blocklet: { ...newBlocklet },
1532
- selectedComponentDids: selectedComponentDids || [],
1533
- version,
1534
- context,
1535
- postAction: action,
1536
- },
1537
- did
1538
- );
1539
-
1540
- ticket.on('failed', async (err) => {
1541
- logger.error('queue failed', { entity: 'blocklet', action, did, version, name, error: err });
1542
- await this._rollback(action, did, oldBlocklet);
1543
- this.emit(`blocklet.${action}.failed`, { did, version, err });
1544
- this._createNotification(did, {
1545
- title: `Blocklet ${capitalize(action)} Failed`,
1546
- description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
1547
- entityType: 'blocklet',
1548
- entityId: did,
1549
- severity: 'error',
1550
- });
1320
+ async prune() {
1321
+ const blocklets = await states.blocklet.getBlocklets();
1322
+ const settings = await states.blockletExtras.listSettings();
1323
+ await pruneBlockletBundle({
1324
+ installDir: this.dataDirs.blocklets,
1325
+ blocklets,
1326
+ blockletSettings: settings
1327
+ .filter((x) => x.settings.children && x.settings.children.length)
1328
+ .map((x) => x.settings),
1551
1329
  });
1552
- return newBlocklet;
1553
1330
  }
1554
1331
 
1555
- // eslint-disable-next-line no-unused-vars
1556
- async getRuntimeHistory({ did, hours }, context) {
1557
- const metaDid = await states.blocklet.getBlockletMetaDid(did);
1558
-
1559
- const history = this.runtimeMonitor.getHistory(metaDid);
1332
+ async onJob(job) {
1333
+ if (job.entity === 'blocklet') {
1334
+ if (job.action === 'download') {
1335
+ await this._downloadAndInstall(job);
1336
+ }
1337
+ if (job.action === 'restart') {
1338
+ await this._onRestart(job);
1339
+ }
1560
1340
 
1561
- return getHistoryList({
1562
- history,
1563
- hours,
1564
- recordIntervalSec: MONITOR_RECORD_INTERVAL_SEC,
1565
- props: ['date', 'cpu', 'mem'],
1566
- });
1341
+ if (job.action === 'check_if_started') {
1342
+ await this._onCheckIfStarted(job);
1343
+ }
1344
+ }
1567
1345
  }
1568
1346
 
1569
- // ============================================================================================
1570
- // Internal API that are used by public APIs and called from CLI
1571
- // ============================================================================================
1572
-
1573
- /**
1574
- * After the dev function finished, the caller should send a BlockletEvents.installed event to the daemon
1575
- * @returns {Object} blocklet
1576
- */
1577
- async dev(folder, { rootDid, mountPoint, defaultStoreUrl } = {}) {
1578
- logger.info('dev component', { folder, rootDid, mountPoint });
1579
-
1580
- const meta = getBlockletMeta(folder, { defaultStoreUrl });
1581
- if (meta.group !== 'static' && (!meta.scripts || !meta.scripts.dev)) {
1582
- throw new Error('Incorrect blocklet.yml: missing `scripts.dev` field');
1583
- }
1584
-
1585
- if (rootDid) {
1586
- return this._devComponent({ folder, meta, rootDid, mountPoint });
1587
- }
1588
-
1589
- return this._devBlocklet({ folder, meta });
1590
- }
1591
-
1592
- async _devBlocklet({ folder, meta }) {
1593
- const { did, version } = meta;
1594
-
1595
- const exist = await states.blocklet.getBlocklet(did);
1596
- if (exist) {
1597
- if (exist.mode === BLOCKLET_MODES.PRODUCTION) {
1598
- throw new Error('The blocklet of production mode already exists, please remove it before developing');
1599
- }
1600
-
1601
- const status = fromBlockletStatus(exist.status);
1602
- if (['starting', 'running'].includes(status)) {
1603
- throw new Error(`The blocklet is already on ${status}, please stop it before developing`);
1604
- }
1605
-
1606
- logger.info('remove blocklet for dev', { did, version });
1607
-
1608
- await this.delete({ did, keepLogsDir: false });
1609
- }
1610
-
1611
- // delete process
1612
- try {
1613
- await this.deleteProcess({ did });
1614
- logger.info('delete blocklet precess for dev', { did, version });
1615
- } catch (err) {
1616
- if (process.env.NODE_ENV !== 'development') {
1617
- logger.error('failed to delete blocklet process for dev', { error: err });
1618
- }
1619
- }
1620
-
1621
- const children = await this._getChildrenForInstallation(meta);
1622
- await validateBlocklet({ meta, children });
1623
- const added = await states.blocklet.addBlocklet({
1624
- meta,
1625
- source: BlockletSource.local,
1626
- deployedFrom: folder,
1627
- mode: BLOCKLET_MODES.DEVELOPMENT,
1628
- children,
1629
- });
1630
- logger.info('add blocklet for dev', { did, version, meta });
1631
-
1632
- await this._downloadBlocklet(added);
1633
-
1634
- // Add Config
1635
- await this._setConfigsFromMeta(did);
1636
-
1637
- // should ensure blocklet integrity
1638
- let blocklet = await this.ensureBlocklet(did);
1639
-
1640
- // pre install
1641
- await this._runPreInstallHook(blocklet);
1642
-
1643
- // Add environments
1644
- await this.updateBlockletEnvironment(did);
1645
- blocklet = await this.getBlocklet(did);
1646
-
1647
- // post install
1648
- await this._runPostInstallHook(blocklet);
1649
-
1650
- await states.blocklet.setBlockletStatus(did, BlockletStatus.installed);
1651
-
1652
- blocklet = await this.getBlocklet(did);
1653
-
1654
- await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createDidLogo(blocklet.meta.did));
1655
-
1656
- return blocklet;
1657
- }
1658
-
1659
- async _devComponent({ folder, meta, rootDid, mountPoint, skipNavigation = true }) {
1660
- const { did, version } = meta;
1661
-
1662
- const existRoot = await states.blocklet.getBlocklet(rootDid);
1663
- if (!existRoot) {
1664
- throw new Error('Root blocklet does not exist');
1665
- }
1666
-
1667
- const exist = existRoot.children.find((x) => x.meta.did === meta.did);
1668
- if (exist) {
1669
- if (exist.mode === BLOCKLET_MODES.PRODUCTION) {
1670
- throw new Error('The blocklet component of production mode already exists, please remove it before developing');
1671
- }
1672
-
1673
- const status = fromBlockletStatus(exist.status);
1674
- if (['starting', 'running'].includes(status)) {
1675
- throw new Error(`The blocklet component is already on ${status}, please stop it before developing`);
1676
- }
1677
-
1678
- logger.info('remove blocklet component for dev', { did, version });
1679
-
1680
- await this.deleteComponent({ did, rootDid });
1681
- }
1682
-
1683
- const defaultPath = formatName(meta.name);
1684
- const component = {
1685
- meta: ensureMeta(meta),
1686
- mountPoint: mountPoint || `/${defaultPath}`,
1687
- source: BlockletSource.local,
1688
- deployedFrom: folder,
1689
- status: BlockletStatus.installed,
1690
- mode: BLOCKLET_MODES.DEVELOPMENT,
1691
- dynamic: true,
1692
- children: await parseChildrenFromMeta(meta, { ancestors: [{ mountPoint: mountPoint || `/${defaultPath}` }] }),
1693
- };
1694
- await validateBlocklet(component);
1695
- await states.blocklet.addChildren(rootDid, [component]);
1696
-
1697
- const newBlocklet = await states.blocklet.getBlocklet(rootDid);
1698
- await this._downloadBlocklet(newBlocklet);
1699
- await states.blocklet.setBlockletStatus(rootDid, BlockletStatus.stopped);
1700
-
1701
- await this._upsertDynamicNavigation(existRoot.meta.did, component, { skipNavigation });
1702
-
1703
- // Add Config
1704
- await this._setConfigsFromMeta(rootDid);
1705
-
1706
- // should ensure blocklet integrity
1707
- let blocklet = await this.ensureBlocklet(rootDid);
1708
-
1709
- // pre install
1710
- await this._runPreInstallHook(blocklet);
1711
-
1712
- // Add environments
1713
- await this.updateBlockletEnvironment(rootDid);
1714
- blocklet = await this.getBlocklet(rootDid);
1715
-
1716
- // post install
1717
- await this._runPostInstallHook(blocklet);
1718
-
1719
- logger.info('add blocklet component for dev', { did, version, meta });
1720
-
1721
- blocklet = await this.getBlocklet(rootDid);
1722
-
1723
- return blocklet;
1724
- }
1725
-
1726
- /**
1727
- * backup 目录结构
1728
- * /blocklets/<name1>version>
1729
- * /blocklets/<name2>version>
1730
- * /blocklets/<name3>version>
1731
- * /data
1732
- * /blocklet.json
1733
- * /blocklet_extras.json
1734
- * @see Blocklet 端到端备份方案的设计与实现(一期) https://team.arcblock.io/comment/discussions/e62084d5-fafa-489e-91d5-defcd6e93223
1735
- * @param {{ url: string, appSk: string, moveDir: boolean}} [{ url }={}]
1736
- * @param {Record<string, string>} [context={}]
1737
- * @return {Promise<any>}
1738
- * @memberof BlockletManager
1739
- */
1740
- async _installFromBackup({ url, appSk, moveDir } = {}, context = {}) {
1741
- return installFromBackup({ url, appSk, moveDir, context, manager: this, states });
1742
- }
1743
-
1744
- async ensureBlocklet(did, opts = {}) {
1745
- return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did, ensureIntegrity: true });
1746
- }
1747
-
1748
- async getBlocklet(did, opts = {}) {
1749
- return getBlocklet({ ...opts, states, dataDirs: this.dataDirs, did, ensureIntegrity: false });
1750
- }
1751
-
1752
- async hasBlocklet({ did }) {
1753
- return states.blocklet.hasBlocklet(did);
1754
- }
1755
-
1756
- async setInitialized({ did, owner }) {
1757
- if (!validateOwner(owner)) {
1758
- throw new Error('Blocklet owner is invalid');
1759
- }
1760
-
1761
- const blocklet = await states.blocklet.getBlocklet(did);
1762
- await states.blockletExtras.setSettings(blocklet.meta.did, { initialized: true, owner });
1763
- logger.info('Blocklet initialized', { did, owner });
1764
-
1765
- this.emit(BlockletEvents.updated, { meta: { did: blocklet.meta.did } });
1766
-
1767
- return this.getBlocklet(did);
1768
- }
1769
-
1770
- async status(did, { forceSync = false } = {}) {
1771
- const fastReturnOnForceSync = async (blocklet = {}) => {
1772
- const { status } = blocklet;
1773
- const { added, waiting, downloading, installing, installed, upgrading } = BlockletStatus;
1774
-
1775
- if ([added, waiting, downloading, installing, installed, upgrading].includes(status)) {
1776
- return blocklet;
1777
- }
1778
-
1779
- const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
1780
- this.emit(BlockletEvents.statusChange, res);
1781
- return res;
1782
- };
1783
-
1784
- const blocklet = await this.getBlocklet(did);
1785
-
1786
- let shouldUpdateStatus = forceSync || shouldUpdateBlockletStatus(blocklet.status);
1787
- if (isInProgress(blocklet.status)) {
1788
- const uptime = Date.now() - new Date(blocklet.updatedAt).getTime();
1789
- // if uptime great than 30s, sync the status from pm2
1790
- if (uptime > 30 * 1000) {
1791
- shouldUpdateStatus = true;
1792
- }
1793
- }
1794
-
1795
- if (!shouldUpdateStatus) {
1796
- return blocklet;
1797
- }
1798
-
1799
- try {
1800
- const status = await getBlockletStatusFromProcess(blocklet);
1801
- if (blocklet.status !== status) {
1802
- const res = await states.blocklet.setBlockletStatus(did, status);
1803
- this.emit(BlockletEvents.statusChange, res);
1804
- return res;
1805
- }
1806
-
1807
- return blocklet;
1808
- } catch (err) {
1809
- return fastReturnOnForceSync(blocklet);
1810
- }
1811
- }
1812
-
1813
- async refreshListCache() {
1814
- this.list({ useCache: false }).catch((err) => {
1815
- logger.error('refresh blocklet list failed', { error: err });
1816
- });
1347
+ getCrons() {
1348
+ return [
1349
+ {
1350
+ name: 'sync-blocklet-status',
1351
+ time: '*/60 * * * * *', // 60s
1352
+ fn: this._syncBlockletStatus.bind(this),
1353
+ },
1354
+ {
1355
+ name: 'sync-blocklet-list',
1356
+ time: '*/60 * * * * *', // 60s
1357
+ fn: this.refreshListCache.bind(this),
1358
+ },
1359
+ {
1360
+ name: 'refresh-accessible-ip',
1361
+ time: '0 */10 * * * *', // 10min
1362
+ fn: async () => {
1363
+ const nodeInfo = await states.node.read();
1364
+ await refreshAccessibleExternalNodeIp(nodeInfo);
1365
+ },
1366
+ },
1367
+ {
1368
+ name: 'delete-expired-external-blocklet',
1369
+ time: '0 */30 * * * *', // 30min
1370
+ options: { runOnInit: false },
1371
+ fn: () => this._deleteExpiredExternalBlocklet(),
1372
+ },
1373
+ {
1374
+ name: 'clean-expired-blocklet-data',
1375
+ time: '0 */20 0 * * *', // 每天凌晨 0 点的每 20 分钟
1376
+ fn: () => this._cleanExpiredBlockletData(),
1377
+ },
1378
+ {
1379
+ name: 'record-blocklet-runtime-history',
1380
+ time: `*/${MONITOR_RECORD_INTERVAL_SEC} * * * * *`, // 10s
1381
+ fn: () => this.runtimeMonitor.monitAll(),
1382
+ },
1383
+ ];
1817
1384
  }
1818
1385
 
1819
- async onJob(job) {
1820
- if (job.entity === 'blocklet') {
1821
- if (job.action === 'download') {
1822
- await this.downloadAndInstall(job);
1823
- }
1824
- if (job.action === 'restart') {
1825
- await this.onRestart(job);
1826
- }
1827
-
1828
- if (job.action === 'check_if_started') {
1829
- await this.onCheckIfStarted(job);
1830
- }
1831
- }
1832
- }
1386
+ // ============================================================================================
1387
+ // Private API that are used by self of helper function
1388
+ // ============================================================================================
1833
1389
 
1834
1390
  /**
1835
1391
  *
@@ -1837,7 +1393,7 @@ class BlockletManager extends BaseBlockletManager {
1837
1393
  * @param {{
1838
1394
  * blocklet: {},
1839
1395
  * context: {},
1840
- * postAction: 'install' | 'upgrade' | 'downgrade',
1396
+ * postAction: 'install' | 'upgrade',
1841
1397
  * oldBlocklet: {},
1842
1398
  * throwOnError: Error,
1843
1399
  * skipCheckStatusBeforeDownload: boolean,
@@ -1846,7 +1402,7 @@ class BlockletManager extends BaseBlockletManager {
1846
1402
  * @return {*}
1847
1403
  * @memberof BlockletManager
1848
1404
  */
1849
- async downloadAndInstall(params) {
1405
+ async _downloadAndInstall(params) {
1850
1406
  const {
1851
1407
  blocklet,
1852
1408
  context,
@@ -1963,7 +1519,7 @@ class BlockletManager extends BaseBlockletManager {
1963
1519
  this.emit(BlockletEvents.statusChange, state);
1964
1520
  }
1965
1521
 
1966
- if (['upgrade', 'downgrade'].includes(postAction)) {
1522
+ if (postAction === 'upgrade') {
1967
1523
  const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading);
1968
1524
  this.emit(BlockletEvents.statusChange, state);
1969
1525
  }
@@ -1978,13 +1534,13 @@ class BlockletManager extends BaseBlockletManager {
1978
1534
  try {
1979
1535
  // install blocklet
1980
1536
  if (postAction === 'install') {
1981
- await this.onInstall({ blocklet, context, oldBlocklet });
1537
+ await this._onInstall({ blocklet, context, oldBlocklet });
1982
1538
  return;
1983
1539
  }
1984
1540
 
1985
1541
  // upgrade blocklet
1986
- if (['upgrade', 'downgrade'].includes(postAction)) {
1987
- await this.onUpgrade({ oldBlocklet, newBlocklet: blocklet, action: postAction, context });
1542
+ if (postAction === 'upgrade') {
1543
+ await this._onUpgrade({ oldBlocklet, newBlocklet: blocklet, context });
1988
1544
  }
1989
1545
  } catch (error) {
1990
1546
  if (throwOnError) {
@@ -1993,7 +1549,7 @@ class BlockletManager extends BaseBlockletManager {
1993
1549
  }
1994
1550
  }
1995
1551
 
1996
- async onInstall({ blocklet, context, oldBlocklet }) {
1552
+ async _onInstall({ blocklet, context, oldBlocklet }) {
1997
1553
  const { meta } = blocklet;
1998
1554
  const { did, version } = meta;
1999
1555
  logger.info('do install blocklet', { did, version });
@@ -2021,9 +1577,9 @@ class BlockletManager extends BaseBlockletManager {
2021
1577
  }
2022
1578
  }
2023
1579
 
2024
- async onUpgrade({ oldBlocklet, newBlocklet, action, context }) {
1580
+ async _onUpgrade({ oldBlocklet, newBlocklet, context }) {
2025
1581
  const { version, did } = newBlocklet.meta;
2026
- logger.info(`do ${action} blocklet`, { did, version });
1582
+ logger.info('do upgrade blocklet', { did, version });
2027
1583
 
2028
1584
  try {
2029
1585
  await this._upgradeBlocklet({
@@ -2036,12 +1592,12 @@ class BlockletManager extends BaseBlockletManager {
2036
1592
  }
2037
1593
  }
2038
1594
 
2039
- async onRestart({ did, context }) {
1595
+ async _onRestart({ did, context }) {
2040
1596
  await this.stop({ did, updateStatus: false }, context);
2041
1597
  await this.start({ did }, context);
2042
1598
  }
2043
1599
 
2044
- async onCheckIfStarted(jobInfo, { throwOnError } = {}) {
1600
+ async _onCheckIfStarted(jobInfo, { throwOnError } = {}) {
2045
1601
  const { did, context, minConsecutiveTime = 5000, timeout } = jobInfo;
2046
1602
  const blocklet = await this.getBlocklet(did);
2047
1603
 
@@ -2083,7 +1639,7 @@ class BlockletManager extends BaseBlockletManager {
2083
1639
  }
2084
1640
  }
2085
1641
 
2086
- async updateBlockletEnvironment(did) {
1642
+ async _updateBlockletEnvironment(did) {
2087
1643
  const blockletWithEnv = await this.getBlocklet(did);
2088
1644
  const blocklet = await states.blocklet.getBlocklet(did);
2089
1645
  const nodeInfo = await states.node.read();
@@ -2111,628 +1667,25 @@ class BlockletManager extends BaseBlockletManager {
2111
1667
  const childWithEnv = envMap[id];
2112
1668
  if (childWithEnv) {
2113
1669
  child.environments = formatEnvironments({
2114
- ...getComponentSystemEnvironments(childWithEnv), // system env of child blocklet
2115
- ...appSystemEnvironments, // system env of root blocklet
2116
- });
2117
- }
2118
- });
2119
-
2120
- // put BLOCKLET_APP_ID at root level for indexing
2121
- blocklet.appDid = appSystemEnvironments.BLOCKLET_APP_ID;
2122
-
2123
- if (!Array.isArray(blocklet.migratedFrom)) {
2124
- blocklet.migratedFrom = [];
2125
- }
2126
- // This can only be set once, can be used for indexing, will not change ever
2127
- if (!blocklet.appPid) {
2128
- blocklet.appPid = appSystemEnvironments.BLOCKLET_APP_PID;
2129
- }
2130
-
2131
- // update state to db
2132
- return states.blocklet.updateBlocklet(did, blocklet);
2133
- }
2134
-
2135
- async updateAllBlockletEnvironment() {
2136
- const blocklets = await states.blocklet.getBlocklets();
2137
- for (let i = 0; i < blocklets.length; i++) {
2138
- const blocklet = blocklets[i];
2139
- await this.updateBlockletEnvironment(blocklet.meta.did);
2140
- }
2141
- }
2142
-
2143
- /**
2144
- ***
2145
- *
2146
- * @param {{
2147
- * did: string;
2148
- * sync: boolean;
2149
- * delay: number;
2150
- * controller: Controller;
2151
- * appSk: string;
2152
- * storeUrl: string;
2153
- * }} params
2154
- * @param {*} context
2155
- * @return {*}
2156
- * @memberof BlockletManager
2157
- */
2158
- async _installFromStore(params, context) {
2159
- const { did, storeUrl, sync, delay, controller, appSk } = params;
2160
-
2161
- logger.debug('start install blocklet', { did });
2162
- if (!isValidDid(did)) {
2163
- throw new Error('Blocklet did is invalid');
2164
- }
2165
-
2166
- if (!storeUrl) {
2167
- throw new Error('registry url should not be empty');
2168
- }
2169
-
2170
- const info = await StoreUtil.getRegistryMeta(storeUrl);
2171
- const meta = await StoreUtil.getBlockletMeta({ did, storeUrl });
2172
- if (!meta) {
2173
- throw new Error('Can not install blocklet that not found in registry');
2174
- }
2175
-
2176
- const { did: blockletDid, name: blockletName } = getBlockletIndex(meta, controller);
2177
-
2178
- const state = await states.blocklet.getBlocklet(blockletDid);
2179
-
2180
- if (state) {
2181
- throw new Error('Can not install an already installed blocklet');
2182
- }
2183
-
2184
- if (controller?.nftId) {
2185
- // sometimes nedb will throw error if use states.blocklet.count({ ['controller.nftId']: controller.nftId })
2186
- const installedCount = await states.blockletExtras.count({ 'controller.nftId': controller.nftId });
2187
-
2188
- if (installedCount >= (controller.appMaxCount || 1)) {
2189
- throw new Error(
2190
- `You can only install ${controller.appMaxCount} blocklet with this credential: ${controller.nftId}`
2191
- );
2192
- }
2193
- }
2194
-
2195
- // install
2196
- return this._install({
2197
- meta: ensureMeta(meta, { did: blockletDid, name: blockletName }),
2198
- source: BlockletSource.registry,
2199
- deployedFrom: info.cdnUrl || storeUrl,
2200
- sync,
2201
- delay,
2202
- controller,
2203
- appSk,
2204
- context,
2205
- });
2206
- }
2207
-
2208
- /**
2209
- * @type {{
2210
- * id: string;
2211
- * nftId: string;
2212
- * expireDate: Date;
2213
- * }} Controller
2214
- *
2215
- * @param {{
2216
- * url: string;
2217
- * sync: boolean;
2218
- * delay: number;
2219
- * controller: Controller;
2220
- * appSk: string;
2221
- * }} params
2222
- * @param {{}} context
2223
- * @return {*}
2224
- * @memberof BlockletManager
2225
- */
2226
- async _installFromUrl(params, context) {
2227
- const { url, sync, delay, controller, appSk } = params;
2228
-
2229
- logger.debug('start install blocklet', { url });
2230
-
2231
- const bundleMeta = await getBlockletMetaFromUrl(url);
2232
-
2233
- if (!bundleMeta) {
2234
- throw new Error(`Can not install blocklet that not found by url: ${url}`);
2235
- }
2236
-
2237
- const { did: blockletDid, name: blockletName } = getBlockletIndex(bundleMeta, controller);
2238
-
2239
- // install from store if url is a store url
2240
- const { inStore, registryUrl, blockletDid: bundleDid } = await StoreUtil.parseSourceUrl(url, controller);
2241
-
2242
- const nodeInfo = await states.node.read();
2243
-
2244
- await validateStore(nodeInfo, registryUrl);
2245
-
2246
- if (inStore) {
2247
- const exist = await states.blocklet.getBlocklet(blockletDid);
2248
- if (exist) {
2249
- return this.upgrade({ did: blockletDid, storeUrl: registryUrl, sync, delay }, context);
2250
- }
2251
-
2252
- return this._installFromStore({ did: bundleDid, storeUrl: registryUrl, controller, sync, delay, appSk }, context);
2253
- }
2254
-
2255
- const meta = ensureMeta(bundleMeta, { name: blockletName, did: blockletDid });
2256
-
2257
- // upgrade
2258
- const exist = await states.blocklet.getBlocklet(blockletDid);
2259
- if (exist) {
2260
- return this._upgrade({
2261
- meta,
2262
- source: BlockletSource.url,
2263
- deployedFrom: url,
2264
- sync,
2265
- delay,
2266
- context,
2267
- });
2268
- }
2269
-
2270
- // install
2271
- return this._install({
2272
- meta,
2273
- source: BlockletSource.url,
2274
- deployedFrom: url,
2275
- sync,
2276
- delay,
2277
- controller,
2278
- appSk,
2279
- context,
2280
- });
2281
- }
2282
-
2283
- async _installComponentFromUrl({
2284
- rootDid,
2285
- mountPoint,
2286
- url,
2287
- context,
2288
- title,
2289
- name: inputName,
2290
- did: inputDid,
2291
- configs,
2292
- downloadTokenList,
2293
- skipNavigation,
2294
- }) {
2295
- const blocklet = await this._getBlockletForInstallation(rootDid);
2296
- if (!blocklet) {
2297
- throw new Error('Root blocklet does not exist');
2298
- }
2299
-
2300
- const { inStore } = await StoreUtil.parseSourceUrl(url);
2301
-
2302
- const meta = await getBlockletMetaFromUrl(url);
2303
-
2304
- // 如果是一个付费的blocklet,并且url来源为Store, 需要携带token才能下载成功
2305
- if (!isFreeBlocklet(meta) && inStore) {
2306
- const info = await states.node.read();
2307
-
2308
- // eslint-disable-next-line no-param-reassign
2309
- context = {
2310
- ...context,
2311
- headers: {
2312
- 'x-server-did': info.did,
2313
- 'x-server-public-key': info.pk,
2314
- 'x-server-signature': sign(info.did, info.sk, {
2315
- exp: (Date.now() + 5 * 60 * 1000) / 1000,
2316
- }),
2317
- },
2318
- downloadTokenList: downloadTokenList || [],
2319
- };
2320
- }
2321
-
2322
- if (!isComponentBlocklet(meta)) {
2323
- throw new Error('The blocklet cannot be a component');
2324
- }
2325
-
2326
- if (title) {
2327
- meta.title = await titleSchema.validateAsync(title);
2328
- }
2329
-
2330
- let name = inputName;
2331
- let did = inputDid;
2332
- if (!inputName && !inputDid) {
2333
- const found = findAvailableDid(meta, [
2334
- ...blocklet.children.map((x) => x.meta),
2335
- ...(await states.blockletExtras.getSettings(blocklet.meta.did, 'children', [])).map((x) => x.meta),
2336
- ]);
2337
- name = found.name;
2338
- did = found.did;
2339
- }
2340
-
2341
- const newChild = {
2342
- meta: ensureMeta(meta, { did, name }),
2343
- mountPoint,
2344
- bundleSource: { url },
2345
- dynamic: true,
2346
- children: await parseChildrenFromMeta(meta, { ...context, ancestors: [{ mountPoint }] }),
2347
- };
2348
-
2349
- checkDuplicateComponents([...blocklet.children, newChild]);
2350
-
2351
- try {
2352
- // add component to db
2353
- await states.blocklet.addChildren(rootDid, [newChild]);
2354
-
2355
- // update navigation
2356
- await this._upsertDynamicNavigation(blocklet.meta.did, newChild, { skipNavigation });
2357
-
2358
- // update configs
2359
- if (Array.isArray(configs)) {
2360
- if (hasReservedKey(configs)) {
2361
- throw new Error('Component key of environments can not start with `ABT_NODE_` or `BLOCKLET_`');
2362
- }
2363
-
2364
- await states.blockletExtras.setConfigs([blocklet.meta.did, newChild.meta.did], configs);
2365
- }
2366
- } catch (err) {
2367
- logger.error('Add component failed', err);
2368
- await this._rollback('upgrade', rootDid, blocklet);
2369
- throw err;
2370
- }
2371
-
2372
- return this.updateChildren(
2373
- {
2374
- did: rootDid,
2375
- children: [...blocklet.children, newChild],
2376
- oldBlocklet: blocklet,
2377
- selectedComponents: [newChild.meta.did],
2378
- },
2379
- context
2380
- );
2381
- }
2382
-
2383
- async _installFromCreate({ title, description, appSk }, context = {}) {
2384
- logger.debug('create blocklet', { title, description });
2385
-
2386
- await joi.string().label('title').max(20).required().validateAsync(title);
2387
- await joi.string().label('description').max(80).required().validateAsync(description);
2388
-
2389
- const { did, name } = await this._findNextCustomBlockletName();
2390
- const rawMeta = {
2391
- name,
2392
- did,
2393
- title,
2394
- description,
2395
- version: BLOCKLET_DEFAULT_VERSION,
2396
- group: BlockletGroup.gateway,
2397
- interfaces: [
2398
- {
2399
- type: BLOCKLET_INTERFACE_TYPE_WEB,
2400
- name: BLOCKLET_INTERFACE_PUBLIC,
2401
- path: BLOCKLET_DEFAULT_PATH_REWRITE,
2402
- prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
2403
- port: BLOCKLET_DEFAULT_PORT_NAME,
2404
- protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
2405
- },
2406
- ],
2407
- specVersion: BLOCKLET_LATEST_SPEC_VERSION,
2408
- };
2409
- const meta = validateMeta(rawMeta);
2410
-
2411
- await states.blocklet.addBlocklet({ meta, source: BlockletSource.custom, externalSk: !!appSk });
2412
- await this._setConfigsFromMeta(did);
2413
- await this._setAppSk(did, appSk);
2414
-
2415
- // check duplicate appSk
2416
- await checkDuplicateAppSk({ did, states });
2417
-
2418
- // fake install bundle
2419
- const bundleDir = getBundleDir(this.installDir, meta);
2420
- fs.mkdirSync(bundleDir, { recursive: true });
2421
- updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
2422
-
2423
- return this._installBlocklet({ did, context });
2424
- }
2425
-
2426
- async _downloadFromUpload(file) {
2427
- // const { filename, mimetype, encoding, createReadStream } = await file;
2428
- const { filename, createReadStream } = await file;
2429
- const cwd = path.join(this.dataDirs.tmp, 'download');
2430
- const tarFile = path.join(cwd, `${path.basename(filename, path.extname(filename))}.tgz`);
2431
- await fs.ensureDir(cwd);
2432
- await new Promise((resolve, reject) => {
2433
- const readStream = createReadStream();
2434
- const writeStream = fs.createWriteStream(tarFile);
2435
- readStream
2436
- .pipe(new Throttle({ rate: 1024 * 1024 * 20 })) // 20MB
2437
- .pipe(writeStream);
2438
- readStream.on('error', (error) => {
2439
- logger.error('File upload read stream failed', { error });
2440
- writeStream.destroy(new Error(`File upload read stream failed, ${error.message}`));
2441
- });
2442
- writeStream.on('error', (error) => {
2443
- logger.error('File upload write stream failed', { error });
2444
- fs.removeSync(tarFile);
2445
- reject(error);
2446
- });
2447
- writeStream.on('finish', resolve);
2448
- });
2449
-
2450
- return { cwd, tarFile };
2451
- }
2452
-
2453
- async _installFromUpload({ file, did, diffVersion, deleteSet, appSk }, context) {
2454
- logger.info('install blocklet', { from: 'upload file' });
2455
- const { tarFile } = await this._downloadFromUpload(file);
2456
-
2457
- // diff deploy
2458
- if (did && diffVersion) {
2459
- const oldBlocklet = await this._getBlockletForInstallation(did);
2460
- if (!oldBlocklet) {
2461
- throw new Error(`Blocklet ${did} not found when diff deploying`);
2462
- }
2463
- if (oldBlocklet.meta.version !== diffVersion) {
2464
- logger.error('Diff deploy: Blocklet version changed', {
2465
- preVersion: diffVersion,
2466
- changedVersion: oldBlocklet.meta.version,
2467
- name: oldBlocklet.meta.name,
2468
- did: oldBlocklet.meta.did,
2469
- });
2470
- throw new Error('Blocklet version changed when diff deploying');
2471
- }
2472
- if (isInProgress(oldBlocklet.status)) {
2473
- logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
2474
- throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
2475
- }
2476
-
2477
- const { meta } = await resolveDiffDownload(tarFile, this.installDir, {
2478
- deleteSet,
2479
- meta: oldBlocklet.meta,
2480
- });
2481
- const newBlocklet = await states.blocklet.getBlocklet(did);
2482
- newBlocklet.meta = ensureMeta(meta);
2483
- newBlocklet.source = BlockletSource.upload;
2484
- newBlocklet.deployedFrom = `Upload by ${context.user.fullName}`;
2485
- newBlocklet.children = await this._getChildrenForInstallation(meta);
2486
- await validateBlocklet(newBlocklet);
2487
-
2488
- // backup rollback data
2489
- const action = 'upgrade';
2490
- await this._rollbackCache.backup({ did: newBlocklet.meta.did, action, oldBlocklet });
2491
-
2492
- await this.downloadAndInstall({
2493
- blocklet: newBlocklet,
2494
- oldBlocklet,
2495
- context: { ...context, forceStartProcessIds: [getComponentProcessId(newBlocklet)] },
2496
- throwOnError: true,
2497
- postAction: action,
2498
- skipCheckStatusBeforeDownload: true,
2499
- });
2500
- return this.getBlocklet(newBlocklet.meta.did);
2501
- }
2502
-
2503
- // full deploy
2504
- const { meta } = await resolveDownload(tarFile, this.installDir);
2505
- const oldBlocklet = await this._getBlockletForInstallation(meta.did);
2506
-
2507
- // full deploy - upgrade
2508
- if (oldBlocklet) {
2509
- if (isInProgress(oldBlocklet.status)) {
2510
- logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
2511
- throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
2512
- }
2513
-
2514
- const newBlocklet = await states.blocklet.getBlocklet(meta.did);
2515
- newBlocklet.meta = ensureMeta(meta);
2516
- newBlocklet.source = BlockletSource.upload;
2517
- newBlocklet.deployedFrom = `Upload by ${context.user.fullName}`;
2518
- newBlocklet.children = await this._getChildrenForInstallation(meta);
2519
-
2520
- // backup rollback data
2521
- const action = 'upgrade';
2522
- await this._rollbackCache.backup({ did: newBlocklet.meta.did, action, oldBlocklet });
2523
-
2524
- await validateBlocklet(newBlocklet);
2525
- await this.downloadAndInstall({
2526
- blocklet: newBlocklet,
2527
- oldBlocklet,
2528
- context: { ...context, forceStartProcessIds: [getComponentProcessId(newBlocklet)] },
2529
- throwOnError: true,
2530
- postAction: action,
2531
- skipCheckStatusBeforeDownload: true,
2532
- });
2533
- return this.getBlocklet(newBlocklet.meta.did);
2534
- }
2535
-
2536
- // full deploy - install
2537
-
2538
- const oldExtraState = await states.blockletExtras.findOne({ did: meta.did });
2539
- const children = await this._getChildrenForInstallation(meta);
2540
- const blocklet = await states.blocklet.addBlocklet({
2541
- meta,
2542
- source: BlockletSource.upload,
2543
- deployedFrom: `Upload by ${context.user.fullName}`,
2544
- children,
2545
- externalSk: !!appSk,
2546
- });
2547
-
2548
- const action = 'install';
2549
- const oldState = { extraState: oldExtraState };
2550
- try {
2551
- await this._setConfigsFromMeta(meta.did);
2552
- await this._setAppSk(appSk);
2553
- await validateBlocklet(blocklet);
2554
-
2555
- // check duplicate appSk
2556
- await checkDuplicateAppSk({ did: meta.did, states });
2557
- } catch (error) {
2558
- await this._rollback(action, meta.did, oldState);
2559
- throw error;
2560
- }
2561
-
2562
- // backup rollback data
2563
- await this._rollbackCache.backup({ did: meta.did, action, oldBlocklet: oldState });
2564
-
2565
- await this.downloadAndInstall({
2566
- blocklet,
2567
- oldBlocklet: oldState,
2568
- context,
2569
- throwOnError: true,
2570
- postAction: action,
2571
- skipCheckStatusBeforeDownload: true,
2572
- });
2573
- return this.getBlocklet(meta.did);
2574
- }
2575
-
2576
- async _installComponentFromUpload({
2577
- rootDid,
2578
- mountPoint,
2579
- file,
2580
- did,
2581
- diffVersion,
2582
- deleteSet,
2583
- skipNavigation,
2584
- context,
2585
- }) {
2586
- logger.info('install blocklet', { from: 'upload file' });
2587
- // download
2588
- const { tarFile } = await this._downloadFromUpload(file);
2589
-
2590
- const oldBlocklet = await this._getBlockletForInstallation(rootDid);
2591
- if (!oldBlocklet) {
2592
- throw new Error('Root blocklet does not exist');
2593
- }
2594
-
2595
- if (isInProgress(oldBlocklet.status)) {
2596
- logger.error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
2597
- throw new Error(`Can not deploy blocklet when it is ${fromBlockletStatus(oldBlocklet.status)}`);
2598
- }
2599
-
2600
- let meta;
2601
- // diff upload
2602
- if (did && diffVersion) {
2603
- const oldChild = oldBlocklet.children.find((x) => x.meta.did === did);
2604
- if (!oldChild) {
2605
- throw new Error(`Blocklet ${did} not found when diff deploying`);
2606
- }
2607
- if (oldChild.meta.version !== diffVersion) {
2608
- logger.error('Diff deploy: Blocklet version changed', {
2609
- preVersion: diffVersion,
2610
- changedVersion: oldChild.meta.version,
2611
- name: oldChild.meta.name,
2612
- did: oldChild.meta.did,
2613
- });
2614
- throw new Error('Blocklet version changed when diff deploying');
2615
- }
2616
-
2617
- meta = (await resolveDiffDownload(tarFile, this.installDir, { deleteSet, meta: oldChild.meta })).meta;
2618
- } else {
2619
- // full deploy
2620
- meta = (await resolveDownload(tarFile, this.installDir)).meta;
2621
- }
2622
-
2623
- if (meta.did === rootDid) {
2624
- // should not be here
2625
- throw new Error('Cannot add self as a component');
2626
- }
2627
-
2628
- const newBlocklet = await states.blocklet.getBlocklet(rootDid);
2629
-
2630
- if (newBlocklet.children.some((x) => !x.dynamic && x.meta.did === meta.did)) {
2631
- throw new Error('Cannot add duplicate component defined in app meta');
2632
- }
2633
-
2634
- const newChild = {
2635
- meta: ensureMeta(meta),
2636
- mountPoint,
2637
- source: BlockletSource.upload,
2638
- deployedFrom: `Upload by ${context.user.fullName}`,
2639
- bundleSource: null,
2640
- dynamic: true,
2641
- children: await parseChildrenFromMeta(meta, { ancestors: [{ mountPoint }] }),
2642
- };
2643
- const index = newBlocklet.children.findIndex((child) => child.meta.did === meta.did);
2644
- if (index >= 0) {
2645
- newBlocklet.children.splice(index, 1, newChild);
2646
- } else {
2647
- newBlocklet.children.push(newChild);
2648
- }
2649
-
2650
- checkDuplicateComponents(newBlocklet.children);
2651
-
2652
- await this._upsertDynamicNavigation(newBlocklet.meta.did, newChild, { skipNavigation });
2653
-
2654
- // backup rollback data
2655
- const action = 'upgrade';
2656
- await this._rollbackCache.backup({ did: newBlocklet.meta.did, action, oldBlocklet });
2657
-
2658
- await this.downloadAndInstall({
2659
- blocklet: newBlocklet,
2660
- oldBlocklet,
2661
- context: { ...context, forceStartProcessIds: [getComponentProcessId(newChild, [newBlocklet])] },
2662
- throwOnError: true,
2663
- postAction: action,
2664
- skipCheckStatusBeforeDownload: true,
2665
- selectedComponentDids: [newChild.meta.did],
2666
- });
2667
- return this.getBlocklet(newBlocklet.meta.did);
2668
- }
2669
-
2670
- async prune() {
2671
- const blocklets = await states.blocklet.getBlocklets();
2672
- const settings = await states.blockletExtras.listSettings();
2673
- await pruneBlockletBundle({
2674
- installDir: this.dataDirs.blocklets,
2675
- blocklets,
2676
- blockletSettings: settings
2677
- .filter((x) => x.settings.children && x.settings.children.length)
2678
- .map((x) => x.settings),
1670
+ ...getComponentSystemEnvironments(childWithEnv), // system env of child blocklet
1671
+ ...appSystemEnvironments, // system env of root blocklet
1672
+ });
1673
+ }
2679
1674
  });
2680
- }
2681
1675
 
2682
- async getLatestBlockletVersion({ did, version }) {
2683
- const blocklet = await states.blocklet.getBlocklet(did);
2684
- if (!blocklet) {
2685
- throw new Error('the blocklet is not installed');
2686
- }
1676
+ // put BLOCKLET_APP_ID at root level for indexing
1677
+ blocklet.appDid = appSystemEnvironments.BLOCKLET_APP_ID;
2687
1678
 
2688
- if (blocklet.source === BlockletSource.registry && blocklet.deployedFrom) {
2689
- return this._getLatestBlockletVersionFromStore({ blocklet, version });
1679
+ if (!Array.isArray(blocklet.migratedFrom)) {
1680
+ blocklet.migratedFrom = [];
2690
1681
  }
2691
-
2692
- if (blocklet.source === BlockletSource.url && blocklet.deployedFrom) {
2693
- return this._getLatestBlockletVersionFromUrl({ blocklet, version });
1682
+ // This can only be set once, can be used for indexing, will not change ever
1683
+ if (!blocklet.appPid) {
1684
+ blocklet.appPid = appSystemEnvironments.BLOCKLET_APP_PID;
2694
1685
  }
2695
1686
 
2696
- return null;
2697
- }
2698
-
2699
- getCrons() {
2700
- return [
2701
- {
2702
- name: 'sync-blocklet-status',
2703
- time: '*/60 * * * * *', // 60s
2704
- fn: this._syncBlockletStatus.bind(this),
2705
- },
2706
- {
2707
- name: 'sync-blocklet-list',
2708
- time: '*/60 * * * * *', // 60s
2709
- fn: this.refreshListCache.bind(this),
2710
- },
2711
- {
2712
- name: 'refresh-accessible-ip',
2713
- time: '0 */10 * * * *', // 10min
2714
- fn: async () => {
2715
- const nodeInfo = await states.node.read();
2716
- await refreshAccessibleExternalNodeIp(nodeInfo);
2717
- },
2718
- },
2719
- {
2720
- name: 'delete-expired-external-blocklet',
2721
- time: '0 */30 * * * *', // 30min
2722
- options: { runOnInit: false },
2723
- fn: () => this._deleteExpiredExternalBlocklet(),
2724
- },
2725
- {
2726
- name: 'clean-expired-blocklet-data',
2727
- time: '0 */20 0 * * *', // 每天凌晨 0 点的每 20 分钟
2728
- fn: () => this._cleanExpiredBlockletData(),
2729
- },
2730
- {
2731
- name: 'record-blocklet-runtime-history',
2732
- time: `*/${MONITOR_RECORD_INTERVAL_SEC} * * * * *`, // 10s
2733
- fn: () => this.runtimeMonitor.monitAll(),
2734
- },
2735
- ];
1687
+ // update state to db
1688
+ return states.blocklet.updateBlocklet(did, blocklet);
2736
1689
  }
2737
1690
 
2738
1691
  async _attachRuntimeInfo({ did, nodeInfo, diskInfo = true, context, cachedBlocklet }) {
@@ -2821,257 +1774,75 @@ class BlockletManager extends BaseBlockletManager {
2821
1774
  blocklets.forEach(run);
2822
1775
  }
2823
1776
 
2824
- async _getChildrenForInstallation(meta) {
2825
- const staticChildren = await parseChildrenFromMeta(meta);
2826
- const dynamicChildren = await this._getDynamicChildrenFromSettings(meta.did, { skipDeleted: true });
2827
- for (const dynamicChild of dynamicChildren) {
2828
- dynamicChild.children = await parseChildrenFromMeta(dynamicChild.meta, { ancestors: [dynamicChild] });
1777
+ async _getChildrenForInstallation(component) {
1778
+ if (!component) {
1779
+ return [];
2829
1780
  }
2830
- checkDuplicateComponents([...dynamicChildren, ...staticChildren]);
2831
-
2832
- return [...staticChildren, ...dynamicChildren];
2833
- }
2834
-
2835
- async _getDynamicChildrenFromSettings(did, { skipDeleted } = {}) {
2836
- const children = await states.blockletExtras.getSettings(did, 'children', []);
2837
- return children
2838
- .map((x) => ({ ...x, dynamic: true }))
2839
- .filter((x) => (skipDeleted ? x.status !== BlockletStatus.deleted : true));
2840
- }
2841
-
2842
- /**
2843
- *
2844
- *
2845
- * @param {{
2846
- * meta: {}; // blocklet meta
2847
- * source: number; // example: BlockletSource.registry,
2848
- * deployedFrom: string;
2849
- * appSk: string;
2850
- * context: {}
2851
- * sync: boolean = false;
2852
- * delay: number;
2853
- * controller: Controller;
2854
- * }} params
2855
- * @return {*}
2856
- * @memberof BlockletManager
2857
- */
2858
- async _install(params) {
2859
- const { meta, source, deployedFrom, context, sync, delay, controller, appSk } = params;
2860
1781
 
2861
- validateBlockletMeta(meta, { ensureDist: true });
2862
-
2863
- const info = await states.node.read();
2864
- if (info.mode === NODE_MODES.SERVERLESS) {
2865
- validateInServerless({ blockletMeta: meta });
1782
+ const { dynamicComponents } = await parseComponents(component);
1783
+ if (component.meta.group !== BlockletGroup.gateway) {
1784
+ dynamicComponents.unshift(component);
2866
1785
  }
2867
1786
 
2868
- const { name, did, version } = meta;
2869
-
2870
- const oldExtraState = await states.blockletExtras.findOne({ did: meta.did });
2871
-
2872
- const children = await this._getChildrenForInstallation(meta);
2873
- try {
2874
- // 创建一个blocklet到database
2875
- const blocklet = await states.blocklet.addBlocklet({
2876
- meta,
2877
- source,
2878
- deployedFrom,
2879
- children,
2880
- externalSk: !!appSk,
2881
- });
2882
-
2883
- await validateBlocklet(blocklet);
2884
-
2885
- await states.blockletExtras.addMeta({ did, meta: { did, name }, controller });
2886
-
2887
- await this._setConfigsFromMeta(did);
2888
- await this._setAppSk(did, appSk);
2889
-
2890
- // check duplicate appSk
2891
- await checkDuplicateAppSk({ did, states });
2892
-
2893
- logger.info('blocklet added to database', { did: meta.did });
2894
-
2895
- const blocklet1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
2896
- this.emit(BlockletEvents.added, blocklet1);
2897
-
2898
- const action = 'install';
2899
- const oldBlocklet = {
2900
- extraState: oldExtraState,
2901
- };
2902
-
2903
- /** @type {{
2904
- * blocklet: any;
2905
- * oldBlocklet: {
2906
- * children: any[];
2907
- * extraState: any;
2908
- * };
2909
- * context: {};
2910
- * postAction: string
2911
- * }} */
2912
- const downloadParams = {
2913
- blocklet: { ...blocklet1 },
2914
- context,
2915
- postAction: action,
2916
- oldBlocklet,
2917
- };
2918
-
2919
- // backup rollback data
2920
- await this._rollbackCache.backup({ did, action, oldBlocklet });
2921
-
2922
- if (sync) {
2923
- await this.downloadAndInstall({ ...downloadParams, throwOnError: true });
2924
- return states.blocklet.getBlocklet(did);
2925
- }
2926
-
2927
- setTimeout(() => {
2928
- const ticket = this.installQueue.push(
2929
- {
2930
- entity: 'blocklet',
2931
- action: 'download',
2932
- id: did,
2933
- ...downloadParams,
2934
- },
2935
- did
2936
- );
2937
- ticket.on('failed', async (err) => {
2938
- logger.error('failed to install blocklet', { name, did, version, error: err });
2939
- try {
2940
- await this._rollback('install', did, { extraState: oldExtraState });
2941
- } catch (e) {
2942
- logger.error('failed to remove blocklet on install error', { did: meta.did, error: e });
2943
- }
2944
-
2945
- this._createNotification(did, {
2946
- title: 'Blocklet Install Failed',
2947
- description: `Blocklet ${name}@${version} install failed with error: ${err.message || 'queue exception'}`,
2948
- entityType: 'blocklet',
2949
- entityId: did,
2950
- severity: 'error',
2951
- });
2952
- });
2953
- }, delay || 0);
2954
-
2955
- return blocklet1;
2956
- } catch (err) {
2957
- logger.error('failed to install blocklet', { name, did, version, error: err });
2958
- this._createNotification(did, {
2959
- title: 'Blocklet Install Failed',
2960
- description: `Blocklet ${name}@${version} install failed with error: ${err.message || 'queue exception'}`,
2961
- entityType: 'blocklet',
2962
- entityId: did,
2963
- severity: 'error',
2964
- });
2965
-
2966
- try {
2967
- await this._rollback('install', did, { extraState: oldExtraState });
2968
- } catch (e) {
2969
- logger.error('failed to remove blocklet on install error', { did: meta.did, error: e });
2970
- }
2971
-
2972
- throw err;
2973
- }
1787
+ const children = filterDuplicateComponents(dynamicComponents);
1788
+ checkVersionCompatibility(children);
1789
+ return children;
2974
1790
  }
2975
1791
 
2976
- /**
2977
- *
2978
- *
2979
- * @param {{}} { meta, source, deployedFrom, context, sync }
2980
- * @return {*}
2981
- * @memberof BlockletManager
2982
- */
2983
- async _upgrade({ meta, source, deployedFrom, context, sync }) {
2984
- validateBlockletMeta(meta, { ensureDist: meta.group !== BlockletGroup.gateway });
2985
-
2986
- const { name, version, did } = meta;
2987
-
2988
- const oldBlocklet = await this._getBlockletForInstallation(did);
2989
-
2990
- // NOTE: 目前的版本移除了降级通道,所以不需要考虑降级通道的情况
2991
- const action = semver.gt(oldBlocklet.meta.version, version) ? 'downgrade' : 'upgrade';
2992
- logger.info(`${action} blocklet`, { did, name, version });
2993
-
2994
- const newBlocklet = await states.blocklet.setBlockletStatus(did, BlockletStatus.waiting);
2995
-
2996
- newBlocklet.meta = ensureMeta(meta);
2997
- newBlocklet.source = source;
2998
- newBlocklet.deployedFrom = deployedFrom;
2999
- newBlocklet.children = await this._getChildrenForInstallation(meta);
3000
-
3001
- await validateBlocklet(newBlocklet);
3002
-
3003
- this.emit(BlockletEvents.statusChange, newBlocklet);
3004
-
3005
- // 如果是一个付费的blocklet,需要携带token才能下载成功
3006
- if (!isFreeBlocklet(meta)) {
3007
- const blocklet = await states.blocklet.getBlocklet(did);
3008
-
3009
- const info = await states.node.read();
3010
-
3011
- // eslint-disable-next-line no-param-reassign
3012
- context = {
3013
- ...context,
3014
- headers: {
3015
- 'x-server-did': info.did,
3016
- 'x-server-public-key': info.pk,
3017
- 'x-server-signature': sign(info.did, info.sk, {
3018
- exp: (Date.now() + 5 * 60 * 1000) / 1000,
3019
- }),
1792
+ async _addBlocklet({ component, mode = BLOCKLET_MODES.PRODUCTION, name, did, title, description }) {
1793
+ const meta = {
1794
+ name,
1795
+ did,
1796
+ title: title || component?.meta?.title || '',
1797
+ description: description || component?.meta?.description || '',
1798
+ version: BLOCKLET_DEFAULT_VERSION,
1799
+ group: BlockletGroup.gateway,
1800
+ interfaces: [
1801
+ {
1802
+ type: BLOCKLET_INTERFACE_TYPE_WEB,
1803
+ name: BLOCKLET_INTERFACE_PUBLIC,
1804
+ path: BLOCKLET_DEFAULT_PATH_REWRITE,
1805
+ prefix: BLOCKLET_DYNAMIC_PATH_PREFIX,
1806
+ port: BLOCKLET_DEFAULT_PORT_NAME,
1807
+ protocol: BLOCKLET_INTERFACE_PROTOCOL_HTTP,
3020
1808
  },
3021
- downloadTokenList: blocklet?.tokens?.downloadTokenList || [],
3022
- };
3023
- }
3024
-
3025
- // download
3026
- const downloadParams = {
3027
- oldBlocklet: { ...oldBlocklet },
3028
- blocklet: { ...newBlocklet },
3029
- version,
3030
- context,
3031
- postAction: action,
1809
+ ],
1810
+ specVersion: BLOCKLET_LATEST_SPEC_VERSION,
1811
+ environments: component?.meta?.environments || [],
1812
+ timeout: {
1813
+ start: process.env.NODE_ENV === 'test' ? 10 : 60,
1814
+ },
3032
1815
  };
3033
1816
 
3034
- // backup rollback data
3035
- await this._rollbackCache.backup({ did, action, oldBlocklet });
1817
+ const children = component ? await this._getChildrenForInstallation(component) : [];
3036
1818
 
3037
- if (sync) {
3038
- await this.downloadAndInstall({ ...downloadParams, throwOnError: true });
3039
- return states.blocklet.getBlocklet(did);
1819
+ if (children[0]?.meta?.logo) {
1820
+ meta.logo = children[0].meta.logo;
3040
1821
  }
3041
- const ticket = this.installQueue.push(
3042
- {
3043
- entity: 'blocklet',
3044
- action: 'download',
3045
- id: did,
3046
- ...downloadParams,
3047
- },
3048
- did
3049
- );
3050
1822
 
3051
- ticket.on('failed', async (err) => {
3052
- logger.error('queue failed', { entity: 'blocklet', action, did, version, name, error: err });
3053
- await this._rollback(action, did, oldBlocklet);
3054
- const eventNames = {
3055
- upgrade: BlockletEvents.upgradeFailed,
3056
- downgrade: BlockletEvents.downgradeFailed,
3057
- };
3058
- this.emit(eventNames[action], { blocklet: oldBlocklet, context });
3059
- this._createNotification(did, {
3060
- title: `Blocklet ${capitalize(action)} Failed`,
3061
- description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message || 'queue exception'}`,
3062
- entityType: 'blocklet',
3063
- entityId: did,
3064
- severity: 'error',
3065
- });
1823
+ await validateBlocklet({ meta, children });
1824
+
1825
+ // fake install bundle
1826
+ const bundleDir = getBundleDir(this.installDir, meta);
1827
+ fs.mkdirSync(bundleDir, { recursive: true });
1828
+ updateMetaFile(path.join(bundleDir, BLOCKLET_META_FILE), meta);
1829
+
1830
+ // add blocklet to db
1831
+ const blocklet = await states.blocklet.addBlocklet({
1832
+ meta,
1833
+ source: BlockletSource.custom,
1834
+ children,
1835
+ mode,
3066
1836
  });
3067
- return newBlocklet;
1837
+
1838
+ return blocklet;
3068
1839
  }
3069
1840
 
3070
1841
  /**
3071
1842
  * @param {string} opt.did
3072
1843
  * @param {object} opt.context
3073
1844
  */
3074
- async _installBlocklet({ did, oldBlocklet, context }) {
1845
+ async _installBlocklet({ did, oldBlocklet, context, createNotification = true }) {
3075
1846
  try {
3076
1847
  // should ensure blocklet integrity
3077
1848
  let blocklet = await this.ensureBlocklet(did);
@@ -3089,7 +1860,7 @@ class BlockletManager extends BaseBlockletManager {
3089
1860
  await this._runPreInstallHook(blocklet, context);
3090
1861
 
3091
1862
  // Add environments
3092
- await this.updateBlockletEnvironment(meta.did);
1863
+ await this._updateBlockletEnvironment(meta.did);
3093
1864
  blocklet = await this.getBlocklet(did);
3094
1865
 
3095
1866
  // post install
@@ -3099,15 +1870,27 @@ class BlockletManager extends BaseBlockletManager {
3099
1870
  blocklet = await this.getBlocklet(did);
3100
1871
  logger.info('blocklet installed', { source, did: meta.did });
3101
1872
 
1873
+ // logo
1874
+ if (blocklet.children[0]?.meta?.logo) {
1875
+ const fileName = blocklet.children[0].meta.logo;
1876
+ const src = path.join(getBundleDir(this.installDir, blocklet.children[0].meta), fileName);
1877
+ const dist = path.join(getBundleDir(this.installDir, blocklet.meta), fileName);
1878
+ await fs.copy(src, dist);
1879
+ }
1880
+
3102
1881
  await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createDidLogo(blocklet.meta.did));
3103
1882
 
3104
1883
  // Init db
3105
1884
  await this.teamManager.initTeam(blocklet.meta.did);
3106
1885
 
1886
+ // Update dependents
1887
+ await this._updateDependents(did);
1888
+ blocklet = await this.getBlocklet(did);
1889
+
3107
1890
  this.emit(BlockletEvents.installed, { blocklet, context });
3108
1891
 
3109
1892
  // Update dynamic component meta in blocklet settings
3110
- await this._ensureDynamicChildrenInSettings(blocklet);
1893
+ await this._ensureDeletedChildrenInSettings(blocklet);
3111
1894
 
3112
1895
  if (context?.downloadTokenList?.length) {
3113
1896
  await states.blocklet.updateBlocklet(did, {
@@ -3122,16 +1905,18 @@ class BlockletManager extends BaseBlockletManager {
3122
1905
  await consumeServerlessNFT({ nftId: blocklet.controller.nftId, nodeInfo, blocklet });
3123
1906
  }
3124
1907
 
3125
- this._createNotification(did, {
3126
- title: 'Blocklet Installed',
3127
- description: `Blocklet ${meta.name}@${meta.version} is installed successfully. (Source: ${
3128
- deployedFrom || fromBlockletSource(source)
3129
- })`,
3130
- action: `/blocklets/${did}/overview`,
3131
- entityType: 'blocklet',
3132
- entityId: did,
3133
- severity: 'success',
3134
- });
1908
+ if (createNotification) {
1909
+ this._createNotification(did, {
1910
+ title: 'Blocklet Installed',
1911
+ description: `Blocklet ${meta.name}@${meta.version} is installed successfully. (Source: ${
1912
+ deployedFrom || fromBlockletSource(source)
1913
+ })`,
1914
+ action: `/blocklets/${did}/overview`,
1915
+ entityType: 'blocklet',
1916
+ entityId: did,
1917
+ severity: 'success',
1918
+ });
1919
+ }
3135
1920
 
3136
1921
  await this._rollbackCache.remove({ did: blocklet.meta.did });
3137
1922
  return blocklet;
@@ -3169,7 +1954,6 @@ class BlockletManager extends BaseBlockletManager {
3169
1954
  // ids
3170
1955
  context.skippedProcessIds = getSkippedProcessIds({ newBlocklet, oldBlocklet, context });
3171
1956
 
3172
- const action = semver.gt(oldBlocklet.meta.version, version) ? 'downgrade' : 'upgrade';
3173
1957
  try {
3174
1958
  // delete old process
3175
1959
  try {
@@ -3190,7 +1974,7 @@ class BlockletManager extends BaseBlockletManager {
3190
1974
  await this._runPreInstallHook(blocklet, context);
3191
1975
 
3192
1976
  // Add environments
3193
- await this.updateBlockletEnvironment(did);
1977
+ await this._updateBlockletEnvironment(did);
3194
1978
  blocklet = await this.getBlocklet(did);
3195
1979
 
3196
1980
  // post install
@@ -3235,17 +2019,15 @@ class BlockletManager extends BaseBlockletManager {
3235
2019
 
3236
2020
  await fs.writeFile(path.join(blocklet.env.dataDir, 'logo.svg'), createDidLogo(blocklet.meta.did));
3237
2021
 
2022
+ await this._updateDependents(did);
2023
+
3238
2024
  this.refreshListCache();
3239
2025
 
3240
2026
  try {
3241
- const eventNames = {
3242
- upgrade: BlockletEvents.upgraded,
3243
- downgrade: BlockletEvents.downgraded,
3244
- };
3245
- this.emit(eventNames[action], { blocklet, context });
2027
+ this.emit(BlockletEvents.upgraded, { blocklet, context });
3246
2028
  this._createNotification(did, {
3247
- title: `Blocklet ${capitalize(action)} Success`,
3248
- description: `Blocklet ${name}@${version} ${action} successfully. (Source: ${
2029
+ title: 'Blocklet Upgrade Success',
2030
+ description: `Blocklet ${name}@${version} upgrade successfully. (Source: ${
3249
2031
  deployedFrom || fromBlockletSource(source)
3250
2032
  })`,
3251
2033
  action: `/blocklets/${did}/overview`,
@@ -3258,26 +2040,25 @@ class BlockletManager extends BaseBlockletManager {
3258
2040
  }
3259
2041
 
3260
2042
  // Update dynamic component meta in blocklet settings
3261
- await this._ensureDynamicChildrenInSettings(blocklet);
2043
+ await this._ensureDeletedChildrenInSettings(blocklet);
3262
2044
 
3263
2045
  await this._rollbackCache.remove({ did: blocklet.meta.did });
3264
2046
 
3265
2047
  return blocklet;
3266
2048
  } catch (err) {
3267
- const b = await this._rollback(action, did, oldBlocklet);
3268
- logger.error(`failed to ${action} blocklet`, { did, version, name, error: err });
2049
+ const b = await this._rollback('upgrade', did, oldBlocklet);
2050
+ logger.error('failed to upgrade blocklet', { did, version, name, error: err });
3269
2051
 
3270
2052
  this.emit(BlockletEvents.updated, b);
3271
2053
 
3272
- const eventNames = {
3273
- upgrade: BlockletEvents.upgradeFailed,
3274
- downgrade: BlockletEvents.downgradeFailed,
3275
- };
3276
- this.emit(eventNames[action], { blocklet: { ...oldBlocklet, error: { message: err.message } }, context });
2054
+ this.emit(BlockletEvents.upgradeFailed, {
2055
+ blocklet: { ...oldBlocklet, error: { message: err.message } },
2056
+ context,
2057
+ });
3277
2058
 
3278
2059
  this._createNotification(did, {
3279
- title: `Blocklet ${capitalize(action)} Failed`,
3280
- description: `Blocklet ${name}@${version} ${action} failed with error: ${err.message}`,
2060
+ title: 'Blocklet Upgrade Failed',
2061
+ description: `Blocklet ${name}@${version} upgrade failed with error: ${err.message}`,
3281
2062
  entityType: 'blocklet',
3282
2063
  entityId: did,
3283
2064
  severity: 'error',
@@ -3286,21 +2067,17 @@ class BlockletManager extends BaseBlockletManager {
3286
2067
  }
3287
2068
  }
3288
2069
 
3289
- // Update dynamic component meta in blocklet settings
3290
- async _ensureDynamicChildrenInSettings(blocklet) {
2070
+ // Refresh deleted component in blocklet settings
2071
+ async _ensureDeletedChildrenInSettings(blocklet) {
3291
2072
  const { did } = blocklet.meta;
3292
- const dynamicChildren = blocklet.children
3293
- .filter((x) => x.dynamic)
3294
- .map((x) => pick(x, ['meta', 'mountPoint', 'bundleSource', 'source']));
3295
2073
 
3296
- // deleted blocklet
3297
- let deletes = await states.blockletExtras.getSettings(did, 'children', []);
3298
- deletes = deletes.filter(
2074
+ // TODO 不从 settings 中取值, 直接存在 extra 中
2075
+ let deletedChildren = await states.blockletExtras.getSettings(did, 'children', []);
2076
+ deletedChildren = deletedChildren.filter(
3299
2077
  (x) => x.status === BlockletStatus.deleted && !blocklet.children.some((y) => y.meta.did === x.meta.did)
3300
2078
  );
3301
- dynamicChildren.push(...deletes);
3302
2079
 
3303
- await states.blockletExtras.setSettings(did, { children: dynamicChildren });
2080
+ await states.blockletExtras.setSettings(did, { children: deletedChildren });
3304
2081
  }
3305
2082
 
3306
2083
  /**
@@ -3483,6 +2260,7 @@ class BlockletManager extends BaseBlockletManager {
3483
2260
  }
3484
2261
  }
3485
2262
 
2263
+ // to be deleted
3486
2264
  async _setAppSk(did, appSk, context) {
3487
2265
  if (process.env.NODE_ENV === 'production' && !appSk) {
3488
2266
  throw new Error(`appSk for blocklet ${did} is required`);
@@ -3501,37 +2279,6 @@ class BlockletManager extends BaseBlockletManager {
3501
2279
  }
3502
2280
  }
3503
2281
 
3504
- async _findNextCustomBlockletName(leftTimes = 10) {
3505
- if (leftTimes <= 0) {
3506
- throw new Error('Generate custom blocklet did too many times');
3507
- }
3508
- const number = await states.node.increaseCustomBlockletNumber();
3509
- const name = `custom-${number}`;
3510
- // MEMO: 空壳 APP可以保留原有的 did 生成逻辑
3511
- const did = toBlockletDid(name);
3512
- const blocklet = await states.blocklet.getBlocklet(did);
3513
- if (blocklet) {
3514
- return this._findNextCustomBlockletName(leftTimes - 1);
3515
- }
3516
- return { did, name };
3517
- }
3518
-
3519
- async _upsertDynamicNavigation(did, child, { skipNavigation } = {}) {
3520
- const navigation = await states.blockletExtras.getSettings(did, 'navigation', []);
3521
- const item = { title: child.meta.title, child: child.meta.name };
3522
- const index = navigation.findIndex((x) => x.child === item.child);
3523
- if (index > -1) {
3524
- if (skipNavigation) {
3525
- navigation.splice(index, 1);
3526
- } else {
3527
- navigation.splice(index, 1, item);
3528
- }
3529
- } else if (!skipNavigation) {
3530
- navigation.push(item);
3531
- }
3532
- await states.blockletExtras.setSettings(did, { navigation });
3533
- }
3534
-
3535
2282
  async _getBlockletForInstallation(did) {
3536
2283
  const blocklet = await states.blocklet.getBlocklet(did, { decryptSk: false });
3537
2284
  if (!blocklet) {
@@ -3544,58 +2291,6 @@ class BlockletManager extends BaseBlockletManager {
3544
2291
  return blocklet;
3545
2292
  }
3546
2293
 
3547
- async _getLatestBlockletVersionFromStore({ blocklet, version }) {
3548
- const { deployedFrom: storeUrl } = blocklet;
3549
- const { did, bundleDid } = blocklet.meta;
3550
-
3551
- let versions = this.cachedBlockletVersions.get(did);
3552
-
3553
- if (!versions) {
3554
- const item = await StoreUtil.getBlockletMeta({ did: bundleDid, storeUrl });
3555
-
3556
- if (!item) {
3557
- return null;
3558
- }
3559
-
3560
- versions = [{ did, version: item.version }];
3561
-
3562
- this.cachedBlockletVersions.set(did, versions);
3563
- }
3564
-
3565
- versions = versions.filter((item) => item && semver.gt(item.version, version));
3566
-
3567
- if (versions.length === 0) {
3568
- return null;
3569
- }
3570
-
3571
- return versions[0];
3572
- }
3573
-
3574
- async _getLatestBlockletVersionFromUrl({ blocklet, version }) {
3575
- const { did } = blocklet.meta;
3576
-
3577
- let versions = this.cachedBlockletVersions.get(did);
3578
-
3579
- if (!versions) {
3580
- try {
3581
- const item = await getBlockletMetaFromUrl(blocklet.deployedFrom);
3582
- versions = [{ did, version: item.version }];
3583
- } catch (error) {
3584
- logger.error('get blocklet meta from url failed when checking latest version', { did, error });
3585
- versions = [];
3586
- }
3587
- }
3588
-
3589
- this.cachedBlockletVersions.set(did, versions);
3590
- versions = versions.filter((item) => item && semver.gt(item.version, version));
3591
-
3592
- if (versions.length === 0) {
3593
- return null;
3594
- }
3595
-
3596
- return versions[0];
3597
- }
3598
-
3599
2294
  async _runPreInstallHook(blocklet, context) {
3600
2295
  const nodeEnvironments = await states.node.getEnvironments();
3601
2296
 
@@ -3743,10 +2438,7 @@ class BlockletManager extends BaseBlockletManager {
3743
2438
  },
3744
2439
  nodeInfo.sk
3745
2440
  );
3746
- const didDomain = getDidDomainForBlocklet({
3747
- blockletAppDid: wallet.address,
3748
- didDomain: nodeInfo.didDomain,
3749
- });
2441
+ const didDomain = getDidDomainForBlocklet({ appPid: blocklet.appPid, didDomain: nodeInfo.didDomain });
3750
2442
 
3751
2443
  const domainAliases = (get(blocklet, 'site.domainAliases') || []).filter(
3752
2444
  (item) => !item.value.endsWith(nodeInfo.didDomain) && !item.value.endsWith('did.staging.arcblock.io') // did.staging.arcblock.io 是旧 did domain, 但主要存在于比较旧的节点中, 需要做兼容
@@ -3759,20 +2451,36 @@ class BlockletManager extends BaseBlockletManager {
3759
2451
 
3760
2452
  this.emit(BlockletEvents.appDidChanged, blocklet);
3761
2453
 
3762
- const blockletWithEnv = await this.getBlocklet(blocklet.meta.did);
3763
- const appSystemEnvironments = getAppOverwrittenEnvironments(blockletWithEnv, nodeInfo);
3764
-
3765
2454
  await didDocument.updateBlockletDocument({
3766
2455
  wallet,
3767
- blockletAppDid: appSystemEnvironments.BLOCKLET_APP_ID,
2456
+ appPid: blocklet.appPid,
2457
+ alsoKnownAs: getBlockletKnownAs(blocklet),
3768
2458
  daemonDidDomain: util.getServerDidDomain(nodeInfo),
3769
2459
  didRegistryUrl: nodeInfo.didRegistry,
3770
2460
  domain: nodeInfo.didDomain,
3771
2461
  });
3772
- logger.info('updated blocklet dns document', {
3773
- did: blocklet.meta.did,
3774
- currentAppDid: appSystemEnvironments.BLOCKLET_APP_ID,
2462
+ logger.info('updated blocklet dns document', { appPid: blocklet.appPid, appDid: blocklet.appDid });
2463
+ }
2464
+
2465
+ async _updateDependents(did) {
2466
+ const blocklet = await states.blocklet.getBlocklet(did);
2467
+ const map = {};
2468
+ for (const child of blocklet.children) {
2469
+ child.dependents = [];
2470
+ map[child.meta.did] = child;
2471
+ }
2472
+
2473
+ forEachBlockletSync(blocklet, (x, { id }) => {
2474
+ if (x.dependencies) {
2475
+ x.dependencies.forEach((y) => {
2476
+ if (map[y.did]) {
2477
+ map[y.did].dependents.push({ id, required: y.required });
2478
+ }
2479
+ });
2480
+ }
3775
2481
  });
2482
+
2483
+ await states.blocklet.updateBlocklet(blocklet.meta.did, { children: blocklet.children });
3776
2484
  }
3777
2485
  }
3778
2486