@abtnode/core 1.16.14-beta-a898bfcb → 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,
@@ -1835,18 +1818,19 @@ class BlockletManager extends BaseBlockletManager {
1835
1818
  }
1836
1819
 
1837
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
+
1838
1828
  try {
1839
- const blockletForDownload = {
1840
- ...blocklet,
1841
- children: (blocklet.children || []).filter((x) => {
1842
- if (componentDids?.length) {
1843
- return componentDids.includes(x.meta.did);
1844
- }
1845
- return x;
1846
- }),
1847
- };
1848
1829
  const { isCancelled } = await this._downloadBlocklet(
1849
- blockletForDownload,
1830
+ {
1831
+ ...blocklet,
1832
+ children: childrenToDownload,
1833
+ },
1850
1834
  Object.assign({}, context, { skipCheckIntegrity })
1851
1835
  );
1852
1836
 
@@ -1876,8 +1860,10 @@ class BlockletManager extends BaseBlockletManager {
1876
1860
  },
1877
1861
  });
1878
1862
  this._createNotification(did, {
1879
- title: 'Blocklet Download Failed',
1880
- 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}`,
1881
1867
  entityType: 'blocklet',
1882
1868
  entityId: did,
1883
1869
  severity: 'error',
@@ -1910,12 +1896,18 @@ class BlockletManager extends BaseBlockletManager {
1910
1896
  throw new Error('blocklet status changed durning download');
1911
1897
  }
1912
1898
 
1913
- if (postAction === 'install') {
1899
+ if (postAction === INSTALL_ACTIONS.INSTALL) {
1914
1900
  const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.installing, { componentDids });
1915
1901
  this.emit(BlockletEvents.statusChange, state);
1916
1902
  }
1917
1903
 
1918
- 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
+ ) {
1919
1911
  const state = await states.blocklet.setBlockletStatus(did, BlockletStatus.upgrading, { componentDids });
1920
1912
  this.emit(BlockletEvents.statusChange, state);
1921
1913
  }
@@ -1929,14 +1921,32 @@ class BlockletManager extends BaseBlockletManager {
1929
1921
  // install
1930
1922
  try {
1931
1923
  // install blocklet
1932
- if (postAction === 'install') {
1924
+ if (postAction === INSTALL_ACTIONS.INSTALL) {
1933
1925
  await this._onInstall({ blocklet, componentDids, context, oldBlocklet });
1934
1926
  return;
1935
1927
  }
1936
1928
 
1937
1929
  // upgrade blocklet
1938
- if (postAction === 'upgrade') {
1939
- 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
+ }
1940
1950
  }
1941
1951
  } catch (error) {
1942
1952
  if (throwOnError) {
@@ -1974,22 +1984,6 @@ class BlockletManager extends BaseBlockletManager {
1974
1984
  }
1975
1985
  }
1976
1986
 
1977
- async _onUpgrade({ oldBlocklet, newBlocklet, componentDids, context }) {
1978
- const { version, did } = newBlocklet.meta;
1979
- logger.info('do upgrade blocklet', { did, version });
1980
-
1981
- try {
1982
- await this._upgradeBlocklet({
1983
- newBlocklet,
1984
- oldBlocklet,
1985
- componentDids,
1986
- context,
1987
- });
1988
- } catch (err) {
1989
- logger.error('blocklet onUpgrade error', { error: err });
1990
- }
1991
- }
1992
-
1993
1987
  async _onRestart({ did, componentDids, context }) {
1994
1988
  await this.stop({ did, componentDids, updateStatus: false }, context);
1995
1989
  await this.start({ did, componentDids }, context);
@@ -2018,7 +2012,7 @@ class BlockletManager extends BaseBlockletManager {
2018
2012
  });
2019
2013
 
2020
2014
  this.emit(BlockletEvents.statusChange, res);
2021
- this.emit(BlockletEvents.started, res);
2015
+ this.emit(BlockletEvents.started, { ...res, componentDids });
2022
2016
  logger.info('blocklet healthy', { did, name, time: Date.now() - startedAt });
2023
2017
  } catch (error) {
2024
2018
  const status = await states.blocklet.getBlockletStatus(did);
@@ -2032,15 +2026,19 @@ class BlockletManager extends BaseBlockletManager {
2032
2026
  await this.deleteProcess({ did, componentDids }, context);
2033
2027
  const doc = await states.blocklet.setBlockletStatus(did, BlockletStatus.error, { componentDids });
2034
2028
 
2029
+ const description = `${getComponentNamesWithVersion(blocklet, componentDids)} start failed for ${title}: ${
2030
+ error.message
2031
+ }`;
2032
+
2035
2033
  this._createNotification(did, {
2036
- title: 'Blocklet Start Failed',
2037
- description: `Blocklet ${title} start failed: ${error.message}`,
2034
+ title: '',
2035
+ description,
2038
2036
  entityType: 'blocklet',
2039
2037
  entityId: did,
2040
2038
  severity: 'error',
2041
2039
  });
2042
2040
 
2043
- this.emit(BlockletEvents.startFailed, { meta, error });
2041
+ this.emit(BlockletEvents.startFailed, { ...doc, componentDids, error: { message: error.message } });
2044
2042
  this.emit(BlockletEvents.statusChange, { ...doc, error });
2045
2043
 
2046
2044
  if (throwOnError) {
@@ -2516,7 +2514,7 @@ class BlockletManager extends BaseBlockletManager {
2516
2514
  const { name, version } = meta;
2517
2515
  logger.error('failed to install blocklet', { name, did, version, error: err });
2518
2516
  try {
2519
- await this._rollback('install', did, oldBlocklet);
2517
+ await this._rollback(INSTALL_ACTIONS.INSTALL, did, oldBlocklet);
2520
2518
  this.emit(BlockletEvents.installFailed, {
2521
2519
  meta: { did },
2522
2520
  error: {
@@ -2538,18 +2536,10 @@ class BlockletManager extends BaseBlockletManager {
2538
2536
  }
2539
2537
  }
2540
2538
 
2541
- async _upgradeBlocklet({ newBlocklet, oldBlocklet, componentDids, context = {} }) {
2539
+ async _upgradeBlocklet({ newBlocklet, oldBlocklet, componentDids, action, context = {} }) {
2542
2540
  const { meta, source, deployedFrom, children } = newBlocklet;
2543
2541
  const { did, version, name, title } = meta;
2544
2542
 
2545
- // ids
2546
- const { skippedProcessIds, installedComponentNames } = getComponentChangedInfoForUpgrade({
2547
- newBlocklet,
2548
- oldBlocklet,
2549
- context,
2550
- });
2551
- context.skippedProcessIds = skippedProcessIds;
2552
-
2553
2543
  try {
2554
2544
  // delete old process
2555
2545
  try {
@@ -2622,10 +2612,22 @@ class BlockletManager extends BaseBlockletManager {
2622
2612
  this.refreshListCache();
2623
2613
 
2624
2614
  try {
2625
- 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
+
2626
2625
  this._createNotification(did, {
2627
- title: 'Blocklet Upgrade Success',
2628
- description: `Blocklet ${title} upgrade successfully. (Component: ${installedComponentNames.join(', ')})`,
2626
+ title: '',
2627
+ description: `${getComponentNamesWithVersion(
2628
+ newBlocklet,
2629
+ componentDids
2630
+ )} is ${actionName} successfully for ${title}`,
2629
2631
  action: `/blocklets/${did}/overview`,
2630
2632
  entityType: 'blocklet',
2631
2633
  entityId: did,
@@ -2647,21 +2649,27 @@ class BlockletManager extends BaseBlockletManager {
2647
2649
 
2648
2650
  return blocklet;
2649
2651
  } catch (err) {
2650
- const b = await this._rollback('upgrade', did, oldBlocklet);
2652
+ const b = await this._rollback(action, did, oldBlocklet);
2651
2653
  logger.error('failed to upgrade blocklet', { did, version, name, error: err });
2652
2654
 
2653
2655
  this.emit(BlockletEvents.updated, b);
2654
2656
 
2655
- this.emit(BlockletEvents.upgradeFailed, {
2656
- 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 } },
2657
2665
  context,
2658
2666
  });
2659
2667
 
2660
2668
  this._createNotification(did, {
2661
- title: 'Blocklet Upgrade Failed',
2662
- description: `Blocklet ${title} upgrade failed with error: ${
2669
+ title: '',
2670
+ description: `${getComponentNamesWithVersion(newBlocklet, componentDids)} ${actionName} failed for ${title}: ${
2663
2671
  err.message
2664
- }. (Component: ${installedComponentNames.join(', ')})`,
2672
+ }.`,
2665
2673
  entityType: 'blocklet',
2666
2674
  entityId: did,
2667
2675
  severity: 'error',
@@ -2735,12 +2743,12 @@ class BlockletManager extends BaseBlockletManager {
2735
2743
  }
2736
2744
 
2737
2745
  /**
2738
- * @param {string} action install, upgrade, downgrade
2746
+ * @param {string} action install, upgrade, installComponent, upgradeComponent
2739
2747
  * @param {string} did
2740
2748
  * @param {object} oldBlocklet
2741
2749
  */
2742
2750
  async _rollback(action, did, oldBlocklet) {
2743
- if (action === 'install') {
2751
+ if (action === INSTALL_ACTIONS.INSTALL) {
2744
2752
  const extraState = oldBlocklet?.extraState;
2745
2753
 
2746
2754
  // rollback blocklet extra state
@@ -2754,7 +2762,13 @@ class BlockletManager extends BaseBlockletManager {
2754
2762
  return this._deleteBlocklet({ did, keepData: true });
2755
2763
  }
2756
2764
 
2757
- 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
+ ) {
2758
2772
  const { extraState, ...blocklet } = oldBlocklet;
2759
2773
  // rollback blocklet state
2760
2774
  const result = await states.blocklet.updateBlocklet(did, blocklet);
@@ -2945,7 +2959,19 @@ class BlockletManager extends BaseBlockletManager {
2945
2959
  return;
2946
2960
  }
2947
2961
 
2948
- 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 });
2949
2975
  } catch (error) {
2950
2976
  logger.error('create notification failed', { error });
2951
2977
  }
@@ -3259,7 +3285,7 @@ class BlockletManager extends BaseBlockletManager {
3259
3285
  );
3260
3286
  }
3261
3287
 
3262
- async _restoreFromDisk(input) {
3288
+ async _restoreFromDisk(input, context) {
3263
3289
  if (input.delay) {
3264
3290
  await sleep(input.delay);
3265
3291
  }
@@ -3283,6 +3309,7 @@ class BlockletManager extends BaseBlockletManager {
3283
3309
  states,
3284
3310
  move: true,
3285
3311
  sync: false, // use queue to download and install application
3312
+ context,
3286
3313
  });
3287
3314
  removeRestoreDir();
3288
3315
  } catch (error) {
@@ -3366,4 +3393,151 @@ class BlockletManager extends BaseBlockletManager {
3366
3393
  }
3367
3394
  }
3368
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
+
3369
3543
  module.exports = BlockletManager;