@abtnode/core 1.16.13 → 1.16.14-beta-0c29907f

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.
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-classes-per-file */
1
2
  /* eslint-disable no-underscore-dangle */
2
3
  /* eslint-disable no-await-in-loop */
3
4
  const fs = require('fs-extra');
@@ -25,6 +26,7 @@ const {
25
26
  APP_STRUCT_VERSION,
26
27
  BLOCKLET_CACHE_TTL,
27
28
  MONITOR_RECORD_INTERVAL_SEC,
29
+ INSTALL_ACTIONS,
28
30
  } = require('@abtnode/constant');
29
31
 
30
32
  const getBlockletEngine = require('@blocklet/meta/lib/engine');
@@ -45,7 +47,6 @@ const {
45
47
  forEachComponentV2Sync,
46
48
  findComponentByIdV2,
47
49
  } = require('@blocklet/meta/lib/util');
48
- const getComponentProcessId = require('@blocklet/meta/lib/get-component-process-id');
49
50
  const { update: updateMetaFile } = require('@blocklet/meta/lib/file');
50
51
  const { titleSchema, updateMountPointSchema, environmentNameSchema } = require('@blocklet/meta/lib/schema');
51
52
  const { emailConfigSchema } = require('@blocklet/sdk/lib/validators/email');
@@ -76,6 +77,12 @@ const {
76
77
  SUSPENDED_REASON,
77
78
  } = require('@blocklet/constant');
78
79
  const isUndefined = require('lodash/isUndefined');
80
+ const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
81
+ const { signV2 } = require('@arcblock/jwt');
82
+ const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
83
+ const pLimit = require('p-limit');
84
+ const pRetry = require('p-retry');
85
+
79
86
  const { consumeServerlessNFT, consumeLauncherSession } = require('../../util/launcher');
80
87
  const util = require('../../util');
81
88
  const {
@@ -118,7 +125,9 @@ const {
118
125
  shouldSkipComponent,
119
126
  exceedRedemptionPeriod,
120
127
  ensureAppPortsNotOccupied,
128
+ getComponentNamesWithVersion,
121
129
  } = require('../../util/blocklet');
130
+ const { getDidDomainForBlocklet } = require('../../util/get-domain-for-blocklet');
122
131
  const states = require('../../states');
123
132
  const BaseBlockletManager = require('./base');
124
133
  const { get: getEngine } = require('./engine');
@@ -147,9 +156,12 @@ const { getBackupFilesUrlFromEndpoint, getBackupEndpoint, getSpaceNameByEndpoint
147
156
  const { validateAddSpaceGateway, validateUpdateSpaceGateway } = require('../../validators/space-gateway');
148
157
  const { sessionConfigSchema } = require('../../validators/util');
149
158
 
159
+ const request = require('../../util/request');
160
+
150
161
  const { formatEnvironments, getBlockletMeta, validateOwner } = util;
151
162
 
152
163
  const statusLock = new Lock('blocklet-status-lock');
164
+ const limitSync = pLimit(1);
153
165
 
154
166
  const getHooksOutputFiles = (blocklet) => ({
155
167
  output: path.join(blocklet.env.logsDir, 'output.log'),
@@ -168,57 +180,7 @@ const pm2StatusMap = {
168
180
  */
169
181
  const getBlockletEngineNameByPlatform = (blockletMeta) => getBlockletEngine(blockletMeta).interpreter;
170
182
 
171
- /**
172
- * @param {{
173
- * newBlocklet,
174
- * oldBlocklet,
175
- * context: { forceStartProcessIds?: string[] },
176
- * }}
177
- * @returns {{ installedComponentNames: string[], skippedProcessIds: string[] }}
178
- */
179
- const getComponentChangedInfoForUpgrade = ({ newBlocklet, oldBlocklet, context = {} }) => {
180
- const { forceStartProcessIds = [] } = context;
181
- const idMap = {};
182
- const skippedProcessIds = [];
183
- const installedComponentNames = [];
184
-
185
- forEachBlockletSync(oldBlocklet, (b, { ancestors }) => {
186
- if (b.meta.dist?.integrity) {
187
- idMap[getComponentProcessId(b, ancestors)] = b.meta.dist?.integrity;
188
- }
189
- });
190
-
191
- forEachBlockletSync(newBlocklet, (b, { ancestors }) => {
192
- const id = getComponentProcessId(b, ancestors);
193
- if (forceStartProcessIds.includes(id)) {
194
- installedComponentNames.push(b.meta.title);
195
- return;
196
- }
197
-
198
- if (!b.meta.dist?.integrity || b.meta.dist.integrity === idMap[id]) {
199
- skippedProcessIds.push(id);
200
- } else {
201
- installedComponentNames.push(b.meta.title);
202
- }
203
- });
204
-
205
- return { skippedProcessIds, installedComponentNames };
206
- };
207
-
208
- class BlockletManager extends BaseBlockletManager {
209
- /**
210
- * Creates an instance of BlockletManager.
211
- * @param {{
212
- * dataDirs: ReturnType<typeof import('../../util/index').getDataDirs>,
213
- * startQueue: ReturnType<typeof import('../../util/queue')>,
214
- * installQueue: ReturnType<typeof import('../../util/queue')>,
215
- * backupQueue: ReturnType<typeof import('../../util/queue')>,
216
- * restoreQueue: ReturnType<typeof import('../../util/queue')>,
217
- * daemon: boolean
218
- * teamManager: import('../../team/manager.js')
219
- * }} { dataDirs, startQueue, installQueue, backupQueue, restoreQueue, daemon = false, teamManager }
220
- * @memberof BlockletManager
221
- */
183
+ class DiskBlockletManager extends BaseBlockletManager {
222
184
  constructor({ dataDirs, startQueue, installQueue, backupQueue, restoreQueue, daemon = false, teamManager }) {
223
185
  super();
224
186
 
@@ -469,7 +431,7 @@ class BlockletManager extends BaseBlockletManager {
469
431
  const componentDids = inputComponentDids?.length ? inputComponentDids : blocklet.children.map((x) => x.meta.did);
470
432
 
471
433
  const tasks = componentDids.map((componentDid) =>
472
- this._start({ did, throwOnError, checkHealthImmediately, e2eMode, componentDids: [componentDid] }, context)
434
+ this._start({ blocklet, throwOnError, checkHealthImmediately, e2eMode, componentDids: [componentDid] }, context)
473
435
  );
474
436
 
475
437
  return Promise.any(tasks).catch((err) => {
@@ -485,6 +447,8 @@ class BlockletManager extends BaseBlockletManager {
485
447
  // should check blocklet integrity
486
448
  const blocklet1 = inputBlocklet || (await this.ensureBlocklet(did, { e2eMode }));
487
449
 
450
+ did = blocklet1.meta.did; // eslint-disable-line no-param-reassign
451
+
488
452
  await this.checkControllerStatus(blocklet1, 'start');
489
453
 
490
454
  // validate requirement and engine
@@ -507,7 +471,9 @@ class BlockletManager extends BaseBlockletManager {
507
471
  // blocklet may be manually stopped durning starting
508
472
  // so error message would not be sent if blocklet is stopped
509
473
  // so we need update status first
510
- const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, { componentDids });
474
+ const doc1 = await states.blocklet.setBlockletStatus(did, BlockletStatus.starting, {
475
+ componentDids,
476
+ });
511
477
  blocklet1.status = BlockletStatus.starting;
512
478
  this.emit(BlockletEvents.statusChange, doc1);
513
479
 
@@ -579,9 +545,11 @@ class BlockletManager extends BaseBlockletManager {
579
545
 
580
546
  const error = Array.isArray(err) ? err[0] : err;
581
547
  logger.error('Failed to start blocklet', { error, did, title: blocklet1.meta.title });
582
- const description = `Start blocklet ${blocklet1.meta.title} failed with error: ${error.message}`;
548
+ const description = `${getComponentNamesWithVersion(blocklet1, componentDids)} start failed for ${
549
+ blocklet1.meta.title
550
+ }: ${error.message}`;
583
551
  this._createNotification(did, {
584
- title: 'Start Blocklet Failed',
552
+ title: '',
585
553
  description,
586
554
  entityType: 'blocklet',
587
555
  entityId: did,
@@ -590,7 +558,7 @@ class BlockletManager extends BaseBlockletManager {
590
558
 
591
559
  await this.deleteProcess({ did, componentDids });
592
560
  const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
593
- this.emit(BlockletEvents.startFailed, { ...res, error: { message: error.message } });
561
+ this.emit(BlockletEvents.startFailed, { ...res, componentDids, error: { message: error.message } });
594
562
  this.emit(BlockletEvents.statusChange, { ...res, error: { message: error.message } });
595
563
 
596
564
  if (throwOnError) {
@@ -647,7 +615,7 @@ class BlockletManager extends BaseBlockletManager {
647
615
  this.emit(BlockletEvents.statusChange, res);
648
616
 
649
617
  // send notification to wallet
650
- this.emit(BlockletEvents.stopped, res);
618
+ this.emit(BlockletEvents.stopped, { ...res, componentDids });
651
619
 
652
620
  this.emit(BlockletInternalEvents.componentsUpdated, {
653
621
  appDid: blocklet.appDid,
@@ -739,9 +707,12 @@ class BlockletManager extends BaseBlockletManager {
739
707
  const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped, { componentDids });
740
708
  this.emit(BlockletEvents.statusChange, state);
741
709
 
710
+ const description = `${getComponentNamesWithVersion(result, componentDids)} restart failed for ${
711
+ result.meta.title
712
+ }: ${err.message || 'queue exception'}`;
742
713
  this._createNotification(did, {
743
- title: 'Blocklet Restart Failed',
744
- description: `Blocklet ${result.meta.title} restart failed with error: ${err.message || 'queue exception'}`,
714
+ title: '',
715
+ description,
745
716
  entityType: 'blocklet',
746
717
  entityId: did,
747
718
  severity: 'error',
@@ -941,23 +912,33 @@ class BlockletManager extends BaseBlockletManager {
941
912
 
942
913
  await this._updateDependents(rootDid);
943
914
 
915
+ // support edge case
916
+ if (newBlocklet.children.length === 0) {
917
+ await states.blocklet.setBlockletStatus(newBlocklet.meta.did, BlockletStatus.stopped);
918
+ }
919
+
944
920
  this.emit(BlockletEvents.upgraded, { blocklet: newBlocklet, context: { ...context, createAuditLog: false } }); // trigger router refresh
945
921
 
946
922
  this._createNotification(newBlocklet.meta.did, {
947
- title: 'Component Deleted',
948
- description: `Component ${child.meta.title} of ${newBlocklet.meta.title} is successfully deleted.`,
923
+ title: '',
924
+ description: `${child.meta.title} is successfully deleted for ${newBlocklet.meta.title}.`,
949
925
  entityType: 'blocklet',
950
926
  entityId: newBlocklet.meta.did,
951
927
  severity: 'success',
952
928
  action: `/blocklets/${newBlocklet.meta.did}/components`,
953
929
  });
954
930
 
931
+ this.emit(BlockletEvents.componentRemoved, {
932
+ ...blocklet,
933
+ componentDids: [child.meta.did],
934
+ });
935
+
955
936
  this.emit(BlockletInternalEvents.componentsUpdated, {
956
937
  appDid: blocklet.appDid,
957
938
  components: getComponentsInternalInfo(newBlocklet),
958
939
  });
959
940
 
960
- return newBlocklet;
941
+ return { ...newBlocklet, deletedComponent: child };
961
942
  }
962
943
 
963
944
  async cancelDownload({ did: inputDid }) {
@@ -1346,6 +1327,8 @@ class BlockletManager extends BaseBlockletManager {
1346
1327
  return newState;
1347
1328
  }
1348
1329
 
1330
+ // -------------------------------------
1331
+
1349
1332
  // TODO: this method can be removed if title is not changed anymore
1350
1333
  async updateComponentTitle({ did, rootDid: inputRootDid, title }) {
1351
1334
  await titleSchema.validateAsync(title);
@@ -1784,7 +1767,7 @@ class BlockletManager extends BaseBlockletManager {
1784
1767
  * @param {{
1785
1768
  * blocklet: {},
1786
1769
  * context: {},
1787
- * postAction: 'install' | 'upgrade',
1770
+ * postAction: 'install' | 'upgradeComponent' | 'installComponent',
1788
1771
  * oldBlocklet: {},
1789
1772
  * throwOnError: Error,
1790
1773
  * skipCheckStatusBeforeDownload: boolean,
@@ -1804,6 +1787,7 @@ class BlockletManager extends BaseBlockletManager {
1804
1787
  componentDids,
1805
1788
  skipCheckIntegrity,
1806
1789
  } = params;
1790
+ logger.info('_downloadAndInstall', { did: blocklet?.meta?.did });
1807
1791
  const { meta } = blocklet;
1808
1792
  const { title, name, did, version } = meta;
1809
1793
 
@@ -1834,18 +1818,19 @@ class BlockletManager extends BaseBlockletManager {
1834
1818
  }
1835
1819
 
1836
1820
  // download bundle
1821
+ const childrenToDownload = (blocklet.children || []).filter((x) => {
1822
+ if (componentDids?.length) {
1823
+ return componentDids.includes(x.meta.did);
1824
+ }
1825
+ return x;
1826
+ });
1827
+
1837
1828
  try {
1838
- const blockletForDownload = {
1839
- ...blocklet,
1840
- children: (blocklet.children || []).filter((x) => {
1841
- if (componentDids?.length) {
1842
- return componentDids.includes(x.meta.did);
1843
- }
1844
- return x;
1845
- }),
1846
- };
1847
1829
  const { isCancelled } = await this._downloadBlocklet(
1848
- blockletForDownload,
1830
+ {
1831
+ ...blocklet,
1832
+ children: childrenToDownload,
1833
+ },
1849
1834
  Object.assign({}, context, { skipCheckIntegrity })
1850
1835
  );
1851
1836
 
@@ -1875,8 +1860,10 @@ class BlockletManager extends BaseBlockletManager {
1875
1860
  },
1876
1861
  });
1877
1862
  this._createNotification(did, {
1878
- title: 'Blocklet Download Failed',
1879
- description: `Blocklet ${title} download failed with error: ${err.message}`,
1863
+ title: '',
1864
+ description: `${childrenToDownload
1865
+ .map((x) => `${x.meta.title}@${x.meta.version}`)
1866
+ .join(', ')} download failed for ${title}: ${err.message}`,
1880
1867
  entityType: 'blocklet',
1881
1868
  entityId: did,
1882
1869
  severity: 'error',
@@ -1909,12 +1896,18 @@ class BlockletManager extends BaseBlockletManager {
1909
1896
  throw new Error('blocklet status changed durning download');
1910
1897
  }
1911
1898
 
1912
- if (postAction === 'install') {
1899
+ if (postAction === INSTALL_ACTIONS.INSTALL) {
1913
1900
  const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.installing, { componentDids });
1914
1901
  this.emit(BlockletEvents.statusChange, state);
1915
1902
  }
1916
1903
 
1917
- if (postAction === 'upgrade') {
1904
+ if (
1905
+ [
1906
+ INSTALL_ACTIONS.INSTALL_COMPONENT,
1907
+ INSTALL_ACTIONS.UPGRADE_COMPONENT,
1908
+ 'upgrade', // for backward compatibility
1909
+ ].includes(postAction)
1910
+ ) {
1918
1911
  const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading, { componentDids });
1919
1912
  this.emit(BlockletEvents.statusChange, state);
1920
1913
  }
@@ -1928,14 +1921,32 @@ class BlockletManager extends BaseBlockletManager {
1928
1921
  // install
1929
1922
  try {
1930
1923
  // install blocklet
1931
- if (postAction === 'install') {
1924
+ if (postAction === INSTALL_ACTIONS.INSTALL) {
1932
1925
  await this._onInstall({ blocklet, componentDids, context, oldBlocklet });
1933
1926
  return;
1934
1927
  }
1935
1928
 
1936
1929
  // upgrade blocklet
1937
- if (postAction === 'upgrade') {
1938
- await this._onUpgrade({ oldBlocklet, componentDids, newBlocklet: blocklet, context });
1930
+ if (
1931
+ [
1932
+ INSTALL_ACTIONS.INSTALL_COMPONENT,
1933
+ INSTALL_ACTIONS.UPGRADE_COMPONENT,
1934
+ 'upgrade', // for backward compatibility
1935
+ ].includes(postAction)
1936
+ ) {
1937
+ logger.info('do upgrade blocklet', { did, version, postAction, componentDids });
1938
+
1939
+ try {
1940
+ await this._upgradeBlocklet({
1941
+ newBlocklet: blocklet,
1942
+ oldBlocklet,
1943
+ componentDids,
1944
+ context,
1945
+ action: postAction,
1946
+ });
1947
+ } catch (err) {
1948
+ logger.error('blocklet onUpgrade error', { error: err });
1949
+ }
1939
1950
  }
1940
1951
  } catch (error) {
1941
1952
  if (throwOnError) {
@@ -1973,22 +1984,6 @@ class BlockletManager extends BaseBlockletManager {
1973
1984
  }
1974
1985
  }
1975
1986
 
1976
- async _onUpgrade({ oldBlocklet, newBlocklet, componentDids, context }) {
1977
- const { version, did } = newBlocklet.meta;
1978
- logger.info('do upgrade blocklet', { did, version });
1979
-
1980
- try {
1981
- await this._upgradeBlocklet({
1982
- newBlocklet,
1983
- oldBlocklet,
1984
- componentDids,
1985
- context,
1986
- });
1987
- } catch (err) {
1988
- logger.error('blocklet onUpgrade error', { error: err });
1989
- }
1990
- }
1991
-
1992
1987
  async _onRestart({ did, componentDids, context }) {
1993
1988
  await this.stop({ did, componentDids, updateStatus: false }, context);
1994
1989
  await this.start({ did, componentDids }, context);
@@ -2017,7 +2012,7 @@ class BlockletManager extends BaseBlockletManager {
2017
2012
  });
2018
2013
 
2019
2014
  this.emit(BlockletEvents.statusChange, res);
2020
- this.emit(BlockletEvents.started, res);
2015
+ this.emit(BlockletEvents.started, { ...res, componentDids });
2021
2016
  logger.info('blocklet healthy', { did, name, time: Date.now() - startedAt });
2022
2017
  } catch (error) {
2023
2018
  const status = await states.blocklet.getBlockletStatus(did);
@@ -2031,15 +2026,19 @@ class BlockletManager extends BaseBlockletManager {
2031
2026
  await this.deleteProcess({ did, componentDids }, context);
2032
2027
  const doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
2033
2028
 
2029
+ const description = `${getComponentNamesWithVersion(blocklet, componentDids)} start failed for ${title}: ${
2030
+ error.message
2031
+ }`;
2032
+
2034
2033
  this._createNotification(did, {
2035
- title: 'Blocklet Start Failed',
2036
- description: `Blocklet ${title} start failed: ${error.message}`,
2034
+ title: '',
2035
+ description,
2037
2036
  entityType: 'blocklet',
2038
2037
  entityId: did,
2039
2038
  severity: 'error',
2040
2039
  });
2041
2040
 
2042
- this.emit(BlockletEvents.startFailed, { meta, error });
2041
+ this.emit(BlockletEvents.startFailed, { ...doc, componentDids, error: { message: error.message } });
2043
2042
  this.emit(BlockletEvents.statusChange, { ...doc, error });
2044
2043
 
2045
2044
  if (throwOnError) {
@@ -2515,7 +2514,7 @@ class BlockletManager extends BaseBlockletManager {
2515
2514
  const { name, version } = meta;
2516
2515
  logger.error('failed to install blocklet', { name, did, version, error: err });
2517
2516
  try {
2518
- await this._rollback('install', did, oldBlocklet);
2517
+ await this._rollback(INSTALL_ACTIONS.INSTALL, did, oldBlocklet);
2519
2518
  this.emit(BlockletEvents.installFailed, {
2520
2519
  meta: { did },
2521
2520
  error: {
@@ -2537,18 +2536,10 @@ class BlockletManager extends BaseBlockletManager {
2537
2536
  }
2538
2537
  }
2539
2538
 
2540
- async _upgradeBlocklet({ newBlocklet, oldBlocklet, componentDids, context = {} }) {
2539
+ async _upgradeBlocklet({ newBlocklet, oldBlocklet, componentDids, action, context = {} }) {
2541
2540
  const { meta, source, deployedFrom, children } = newBlocklet;
2542
2541
  const { did, version, name, title } = meta;
2543
2542
 
2544
- // ids
2545
- const { skippedProcessIds, installedComponentNames } = getComponentChangedInfoForUpgrade({
2546
- newBlocklet,
2547
- oldBlocklet,
2548
- context,
2549
- });
2550
- context.skippedProcessIds = skippedProcessIds;
2551
-
2552
2543
  try {
2553
2544
  // delete old process
2554
2545
  try {
@@ -2605,7 +2596,7 @@ class BlockletManager extends BaseBlockletManager {
2605
2596
  if (oldBlocklet.status === BlockletStatus.running) {
2606
2597
  // start new process
2607
2598
  await this.start({ did, componentDids }, context);
2608
- logger.info('started blocklet for upgrading', { did, version });
2599
+ logger.info('started blocklet for upgrading', { did, version, componentDids });
2609
2600
  } else {
2610
2601
  const status =
2611
2602
  oldBlocklet.status === BlockletStatus.installed ? BlockletStatus.installed : BlockletStatus.stopped;
@@ -2621,10 +2612,22 @@ class BlockletManager extends BaseBlockletManager {
2621
2612
  this.refreshListCache();
2622
2613
 
2623
2614
  try {
2624
- this.emit(BlockletEvents.upgraded, { blocklet, context });
2615
+ this.emit(BlockletEvents.upgraded, { blocklet, context }); // trigger router refresh
2616
+
2617
+ const notificationEvent =
2618
+ action === INSTALL_ACTIONS.INSTALL_COMPONENT
2619
+ ? BlockletEvents.componentInstalled
2620
+ : BlockletEvents.componentUpgraded;
2621
+ const actionName = action === INSTALL_ACTIONS.INSTALL_COMPONENT ? 'installed' : 'upgraded';
2622
+
2623
+ this.emit(notificationEvent, { ...blocklet, componentDids, context });
2624
+
2625
2625
  this._createNotification(did, {
2626
- title: 'Blocklet Upgrade Success',
2627
- description: `Blocklet ${title} upgrade successfully. (Component: ${installedComponentNames.join(', ')})`,
2626
+ title: '',
2627
+ description: `${getComponentNamesWithVersion(
2628
+ newBlocklet,
2629
+ componentDids
2630
+ )} is ${actionName} successfully for ${title}`,
2628
2631
  action: `/blocklets/${did}/overview`,
2629
2632
  entityType: 'blocklet',
2630
2633
  entityId: did,
@@ -2646,21 +2649,27 @@ class BlockletManager extends BaseBlockletManager {
2646
2649
 
2647
2650
  return blocklet;
2648
2651
  } catch (err) {
2649
- const b = await this._rollback('upgrade', did, oldBlocklet);
2652
+ const b = await this._rollback(action, did, oldBlocklet);
2650
2653
  logger.error('failed to upgrade blocklet', { did, version, name, error: err });
2651
2654
 
2652
2655
  this.emit(BlockletEvents.updated, b);
2653
2656
 
2654
- this.emit(BlockletEvents.upgradeFailed, {
2655
- blocklet: { ...oldBlocklet, error: { message: err.message } },
2657
+ const actionName = action === INSTALL_ACTIONS.INSTALL_COMPONENT ? 'install' : 'upgrade';
2658
+ const notificationEvent =
2659
+ action === INSTALL_ACTIONS.INSTALL_COMPONENT
2660
+ ? BlockletEvents.componentInstallFailed
2661
+ : BlockletEvents.componentUpgradeFailed;
2662
+
2663
+ this.emit(notificationEvent, {
2664
+ blocklet: { ...oldBlocklet, componentDids, error: { message: err.message } },
2656
2665
  context,
2657
2666
  });
2658
2667
 
2659
2668
  this._createNotification(did, {
2660
- title: 'Blocklet Upgrade Failed',
2661
- description: `Blocklet ${title} upgrade failed with error: ${
2669
+ title: '',
2670
+ description: `${getComponentNamesWithVersion(newBlocklet, componentDids)} ${actionName} failed for ${title}: ${
2662
2671
  err.message
2663
- }. (Component: ${installedComponentNames.join(', ')})`,
2672
+ }.`,
2664
2673
  entityType: 'blocklet',
2665
2674
  entityId: did,
2666
2675
  severity: 'error',
@@ -2734,12 +2743,12 @@ class BlockletManager extends BaseBlockletManager {
2734
2743
  }
2735
2744
 
2736
2745
  /**
2737
- * @param {string} action install, upgrade, downgrade
2746
+ * @param {string} action install, upgrade, installComponent, upgradeComponent
2738
2747
  * @param {string} did
2739
2748
  * @param {object} oldBlocklet
2740
2749
  */
2741
2750
  async _rollback(action, did, oldBlocklet) {
2742
- if (action === 'install') {
2751
+ if (action === INSTALL_ACTIONS.INSTALL) {
2743
2752
  const extraState = oldBlocklet?.extraState;
2744
2753
 
2745
2754
  // rollback blocklet extra state
@@ -2753,7 +2762,13 @@ class BlockletManager extends BaseBlockletManager {
2753
2762
  return this._deleteBlocklet({ did, keepData: true });
2754
2763
  }
2755
2764
 
2756
- if (['upgrade', 'downgrade'].includes(action)) {
2765
+ if (
2766
+ [
2767
+ INSTALL_ACTIONS.INSTALL_COMPONENT,
2768
+ INSTALL_ACTIONS.UPGRADE_COMPONENT,
2769
+ 'upgrade', // for backward compatibility
2770
+ ].includes(action)
2771
+ ) {
2757
2772
  const { extraState, ...blocklet } = oldBlocklet;
2758
2773
  // rollback blocklet state
2759
2774
  const result = await states.blocklet.updateBlocklet(did, blocklet);
@@ -2944,7 +2959,19 @@ class BlockletManager extends BaseBlockletManager {
2944
2959
  return;
2945
2960
  }
2946
2961
 
2947
- await states.notification.create(notification);
2962
+ let blockletUrl;
2963
+ try {
2964
+ const blocklet = await this.getBlocklet(did);
2965
+ if (blocklet) {
2966
+ const urls = blocklet.site?.domainAliases || [];
2967
+ const customUrl = urls.find((x) => !x.isProtected)?.value;
2968
+ blockletUrl = `http://${customUrl || getDidDomainForBlocklet({ appPid: blocklet.appPid })}`;
2969
+ }
2970
+ } catch (error) {
2971
+ logger.error('[_createNotification] get blocklet url failed', { error });
2972
+ }
2973
+
2974
+ await states.notification.create({ ...notification, blockletUrl });
2948
2975
  } catch (error) {
2949
2976
  logger.error('create notification failed', { error });
2950
2977
  }
@@ -3258,7 +3285,7 @@ class BlockletManager extends BaseBlockletManager {
3258
3285
  );
3259
3286
  }
3260
3287
 
3261
- async _restoreFromDisk(input) {
3288
+ async _restoreFromDisk(input, context) {
3262
3289
  if (input.delay) {
3263
3290
  await sleep(input.delay);
3264
3291
  }
@@ -3282,6 +3309,7 @@ class BlockletManager extends BaseBlockletManager {
3282
3309
  states,
3283
3310
  move: true,
3284
3311
  sync: false, // use queue to download and install application
3312
+ context,
3285
3313
  });
3286
3314
  removeRestoreDir();
3287
3315
  } catch (error) {
@@ -3365,4 +3393,151 @@ class BlockletManager extends BaseBlockletManager {
3365
3393
  }
3366
3394
  }
3367
3395
 
3396
+ class FederatedBlockletManager extends DiskBlockletManager {
3397
+ /**
3398
+ * Joins federated login.
3399
+ * @param {{appUrl: string, did: string;}} options - The federated login appUrl.
3400
+ * @returns {Promise<any>} The data received from the appUrl.
3401
+ */
3402
+ async joinFederatedLogin({ appUrl, did }) {
3403
+ const url = new URL(appUrl);
3404
+ url.pathname = `${WELLKNOWN_SERVICE_PATH_PREFIX}/api/federated/join`;
3405
+
3406
+ const blocklet = await this.getBlocklet(did);
3407
+ const nodeInfo = await states.node.read();
3408
+ const blockletInfo = getBlockletInfo(blocklet, nodeInfo.sk);
3409
+ const { permanentWallet, wallet } = blockletInfo;
3410
+ const memberSite = {
3411
+ appId: wallet.address,
3412
+ appPid: permanentWallet.address,
3413
+ migratedFrom: blocklet.migratedFrom || [],
3414
+ appName: blockletInfo.name,
3415
+ appDescription: blockletInfo.description,
3416
+ appUrl: blockletInfo.appUrl,
3417
+ appLogo:
3418
+ blocklet.environmentObj.BLOCKLET_APP_LOGO ||
3419
+ normalizePathPrefix(`${WELLKNOWN_SERVICE_PATH_PREFIX}/blocklet/logo`) ||
3420
+ '/',
3421
+ appLogoRect: blocklet.environmentObj.BLOCKLET_APP_LOGO_RECT,
3422
+ did: blockletInfo.did,
3423
+ pk: permanentWallet.publicKey,
3424
+ serverId: nodeInfo.did,
3425
+ serverVersion: nodeInfo.version,
3426
+ version: blockletInfo.version,
3427
+ };
3428
+
3429
+ const { data } = await request.post(url.href, {
3430
+ // 初次申请时,member 不在站点群中,不需要对数据进行加密
3431
+ site: memberSite,
3432
+ });
3433
+ await states.blockletExtras.setSettings(blocklet.meta.did, {
3434
+ federated: {
3435
+ config: {
3436
+ appId: blocklet.appDid,
3437
+ appPid: blocklet.appPid || blocklet.appDid,
3438
+ isMaster: false,
3439
+ },
3440
+ sites: data.sites,
3441
+ },
3442
+ });
3443
+
3444
+ const newState = await this.getBlocklet(did);
3445
+ this.emit(BlockletEvents.updated, newState);
3446
+ return newState;
3447
+ }
3448
+
3449
+ async setFederated({ did, config }) {
3450
+ await states.blockletExtras.setSettings(did, { federated: config });
3451
+
3452
+ const newState = await this.getBlocklet(did);
3453
+ this.emit(BlockletEvents.updated, newState);
3454
+ return newState;
3455
+ }
3456
+
3457
+ async configFederated({ did, autoLogin = false }) {
3458
+ const blocklet = await this.getBlocklet(did);
3459
+ const federated = cloneDeep(blocklet.settings.federated || {});
3460
+ federated.config.autoLogin = autoLogin;
3461
+ await states.blockletExtras.setSettings(did, { federated });
3462
+
3463
+ const newState = await this.getBlocklet(did);
3464
+ this.emit(BlockletEvents.updated, newState);
3465
+ return newState;
3466
+ }
3467
+
3468
+ async auditFederatedLogin({ appId, did, status }) {
3469
+ const blocklet = await this.getBlocklet(did);
3470
+
3471
+ const federated = cloneDeep(blocklet.settings.federated || {});
3472
+ const memberSite = federated.sites.find((item) => item.appId === appId);
3473
+ memberSite.status = status;
3474
+ if ([null, undefined].includes(federated.config.isMaster)) {
3475
+ const masterSite = federated.sites.find((item) => item.appId === blocklet.meta.did);
3476
+
3477
+ masterSite.isMaster = true;
3478
+ federated.config.isMaster = true;
3479
+ }
3480
+ // 有审批操作的一方,自动成为 master
3481
+ const newState = await this.setFederated({
3482
+ did: blocklet.meta.did,
3483
+ config: federated,
3484
+ });
3485
+ const nodeInfo = await states.node.read();
3486
+ const { permanentWallet } = getBlockletInfo(blocklet, nodeInfo.sk);
3487
+ let delegation;
3488
+ let roles = [];
3489
+ if (status === 'approved') {
3490
+ delegation = signV2(permanentWallet.address, permanentWallet.secretKey, {
3491
+ agentDid: `did:abt:${memberSite.appPid}`,
3492
+ permissions: [
3493
+ {
3494
+ role: 'DIDConnectAgent',
3495
+ claims: [
3496
+ 'authPrincipal',
3497
+ 'profile',
3498
+ 'signature',
3499
+ 'prepareTx',
3500
+ 'agreement',
3501
+ 'verifiableCredential',
3502
+ 'asset',
3503
+ // 'keyPair',
3504
+ // 'encryptionKey',
3505
+ ],
3506
+ },
3507
+ ],
3508
+ exp: Math.floor(new Date().getTime() / 1000) + 86400 * 365 * 100, // valid for 100 year
3509
+ });
3510
+ roles = await this.teamManager.getRoles(blocklet.meta.did);
3511
+ }
3512
+
3513
+ await request.post(`${memberSite.appUrl}/${WELLKNOWN_SERVICE_PATH_PREFIX}/api/federated/audit-res`, {
3514
+ signer: permanentWallet.address,
3515
+ data: signV2(permanentWallet.address, permanentWallet.secretKey, {
3516
+ appId: blocklet.meta.did,
3517
+ status,
3518
+ delegation,
3519
+ roles: roles.map((item) => pick(item, ['name', 'title', 'description'])),
3520
+ }),
3521
+ });
3522
+ const waitingList = federated.sites
3523
+ .filter((item) => item.appId !== federated.config.appId)
3524
+ .map((item) => {
3525
+ return limitSync(() =>
3526
+ pRetry(
3527
+ () =>
3528
+ request.post(`${item.appUrl}/${WELLKNOWN_SERVICE_PATH_PREFIX}/api/federated/sync`, {
3529
+ signer: permanentWallet.address,
3530
+ data: signV2(permanentWallet.address, permanentWallet.secretKey, { sites: federated.sites }),
3531
+ }),
3532
+ { retries: 3 }
3533
+ )
3534
+ );
3535
+ });
3536
+ await Promise.all(waitingList);
3537
+ return newState;
3538
+ }
3539
+ }
3540
+
3541
+ class BlockletManager extends FederatedBlockletManager {}
3542
+
3368
3543
  module.exports = BlockletManager;