@abtnode/core 1.16.7 → 1.16.8-beta-ca58a421

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.
@@ -128,6 +128,8 @@ const UpgradeComponents = require('./helper/upgrade-components');
128
128
  const BlockletDownloader = require('../downloader/blocklet-downloader');
129
129
  const RollbackCache = require('./helper/rollback-cache');
130
130
  const { migrateApplicationToStructV2 } = require('./helper/migrate-application-to-struct-v2');
131
+ const { getBackupFilesUrlFromEndpoint, getDIDSpaceBackupEndpoint } = require('../../util/spaces');
132
+ const { validateAddSpaceGateway, validateUpdateSpaceGateway } = require('../../validators/space-gateway');
131
133
 
132
134
  const { formatEnvironments, shouldUpdateBlockletStatus, getBlockletMeta, validateOwner } = util;
133
135
 
@@ -195,9 +197,19 @@ const MONITOR_HISTORY_LENGTH = 86400 / MONITOR_RECORD_INTERVAL_SEC;
195
197
 
196
198
  class BlockletManager extends BaseBlockletManager {
197
199
  /**
198
- * @param {*} dataDirs generate by ../../util:getDataDirs
200
+ * Creates an instance of BlockletManager.
201
+ * @param {{
202
+ * dataDirs: ReturnType<typeof import('../../util/index').getDataDirs>,
203
+ * startQueue: ReturnType<typeof import('../../util/queue')>,
204
+ * installQueue: ReturnType<typeof import('../../util/queue')>,
205
+ * backupQueue: ReturnType<typeof import('../../util/queue')>,
206
+ * restoreQueue: ReturnType<typeof import('../../util/queue')>,
207
+ * daemon: boolean
208
+ * teamManager: import('../../team/manager.js')
209
+ * }} { dataDirs, startQueue, installQueue, backupQueue, restoreQueue, daemon = false, teamManager }
210
+ * @memberof BlockletManager
199
211
  */
200
- constructor({ dataDirs, startQueue, installQueue, backupQueue, daemon = false, teamManager }) {
212
+ constructor({ dataDirs, startQueue, installQueue, backupQueue, restoreQueue, daemon = false, teamManager }) {
201
213
  super();
202
214
 
203
215
  this.dataDirs = dataDirs;
@@ -205,6 +217,7 @@ class BlockletManager extends BaseBlockletManager {
205
217
  this.startQueue = startQueue;
206
218
  this.installQueue = installQueue;
207
219
  this.backupQueue = backupQueue;
220
+ this.restoreQueue = restoreQueue;
208
221
  this.teamManager = teamManager;
209
222
 
210
223
  // cached installed blocklets for performance
@@ -607,7 +620,7 @@ class BlockletManager extends BaseBlockletManager {
607
620
  }
608
621
 
609
622
  if (to === 'spaces') {
610
- return this._backupToSpaces({ blocklet }, context);
623
+ return this._backupToSpaces({ blocklet, context });
611
624
  }
612
625
 
613
626
  if (to === 'disk') {
@@ -1097,7 +1110,7 @@ class BlockletManager extends BaseBlockletManager {
1097
1110
  }
1098
1111
 
1099
1112
  // Reload nginx to make sure did-space can embed content from this app
1100
- if (newConfigs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT)?.value) {
1113
+ if (newConfigs.find((x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT)?.value) {
1101
1114
  this.emit(BlockletEvents.spaceConnected, blocklet);
1102
1115
  }
1103
1116
 
@@ -1367,6 +1380,16 @@ class BlockletManager extends BaseBlockletManager {
1367
1380
  }
1368
1381
 
1369
1382
  try {
1383
+ if (
1384
+ blocklet.meta?.group === BlockletGroup.gateway &&
1385
+ !blocklet.children?.length &&
1386
+ isInProgress(blocklet.status)
1387
+ ) {
1388
+ const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
1389
+ this.emit(BlockletEvents.statusChange, res);
1390
+ return res;
1391
+ }
1392
+
1370
1393
  const status = await getBlockletStatusFromProcess(blocklet);
1371
1394
  if (blocklet.status !== status) {
1372
1395
  const res = await states.blocklet.setBlockletStatus(did, status);
@@ -1406,6 +1429,15 @@ class BlockletManager extends BaseBlockletManager {
1406
1429
  });
1407
1430
  }
1408
1431
 
1432
+ /**
1433
+ * @description
1434
+ * @param {{
1435
+ * entity: string;
1436
+ * action: string;
1437
+ * id: string;
1438
+ * }} job
1439
+ * @memberof BlockletManager
1440
+ */
1409
1441
  async onJob(job) {
1410
1442
  if (job.entity === 'blocklet') {
1411
1443
  if (job.action === 'download') {
@@ -1418,6 +1450,14 @@ class BlockletManager extends BaseBlockletManager {
1418
1450
  if (job.action === 'check_if_started') {
1419
1451
  await this._onCheckIfStarted(job);
1420
1452
  }
1453
+
1454
+ if (job.action === 'backupToSpaces') {
1455
+ await this._onBackupToSpaces(job);
1456
+ }
1457
+
1458
+ if (job.action === 'restoreFromSpaces') {
1459
+ await this._onRestoreFromSpaces(job);
1460
+ }
1421
1461
  }
1422
1462
  }
1423
1463
 
@@ -1461,6 +1501,100 @@ class BlockletManager extends BaseBlockletManager {
1461
1501
  ];
1462
1502
  }
1463
1503
 
1504
+ /**
1505
+ * @description
1506
+ * @param {{
1507
+ * did: import('@abtnode/client').RequestAddBlockletSpaceGatewayInput,
1508
+ * spaceGateway: import('@abtnode/client').SpaceGateway
1509
+ * }} { did, spaceGateway }
1510
+ * @return {Promise<void>}
1511
+ * @memberof BlockletManager
1512
+ */
1513
+ async addBlockletSpaceGateway({ did, spaceGateway }) {
1514
+ const spaceGateways = await this.getBlockletSpaceGateways({ did });
1515
+
1516
+ const { error, value } = validateAddSpaceGateway.validate(spaceGateway, {
1517
+ stripUnknown: true,
1518
+ allowUnknown: true,
1519
+ });
1520
+
1521
+ if (error) {
1522
+ throw error;
1523
+ }
1524
+
1525
+ spaceGateways.push(value);
1526
+
1527
+ await states.blockletExtras.setSettings(did, { spaceGateways });
1528
+ }
1529
+
1530
+ /**
1531
+ * @description
1532
+ * @param {import('@abtnode/client').RequestAddBlockletSpaceGatewayInput} { did, spaceGateway }
1533
+ * @return {Promise<void>}
1534
+ * @memberof BlockletManager
1535
+ */
1536
+ async deleteBlockletSpaceGateway({ did, url }) {
1537
+ const spaceGateways = await this.getBlockletSpaceGateways({ did });
1538
+
1539
+ const latestSpaceGateways = spaceGateways.filter((s) => s?.url !== url);
1540
+
1541
+ await states.blockletExtras.setSettings(did, { spaceGateways: latestSpaceGateways });
1542
+ }
1543
+
1544
+ /**
1545
+ * @description
1546
+ * @param {{
1547
+ * did: string,
1548
+ * where: import('@abtnode/client').SpaceGateway
1549
+ * spaceGateway: import('@abtnode/client').SpaceGateway
1550
+ * }} { did, spaceGateway }
1551
+ * @return {Promise<void>}
1552
+ * @memberof BlockletManager
1553
+ */
1554
+ async updateBlockletSpaceGateway({ did, where, spaceGateway }) {
1555
+ const { error, value } = validateUpdateSpaceGateway.validate(spaceGateway, {
1556
+ stripUnknown: true,
1557
+ allowUnknown: true,
1558
+ });
1559
+
1560
+ if (error) {
1561
+ throw error;
1562
+ }
1563
+
1564
+ const spaceGateways = await this.getBlockletSpaceGateways({ did });
1565
+
1566
+ for (const s of spaceGateways) {
1567
+ if (s.url === where?.url) {
1568
+ Object.assign(s, value);
1569
+ break;
1570
+ }
1571
+ }
1572
+
1573
+ await states.blockletExtras.setSettings(did, { spaceGateways });
1574
+ }
1575
+
1576
+ /**
1577
+ * @description
1578
+ * @param {{ did: string }} { did }
1579
+ * @return {Promise<Array<import('@abtnode/client').SpaceGateway>>}
1580
+ * @memberof BlockletManager
1581
+ */
1582
+ async getBlockletSpaceGateways({ did }) {
1583
+ const spaceGateways = await states.blockletExtras.getSettings(did, 'spaceGateways', []);
1584
+
1585
+ return spaceGateways;
1586
+ }
1587
+
1588
+ /**
1589
+ * @description
1590
+ * @param {{ did: string }} { did }
1591
+ * @return {Promise<Array<import('@abtnode/client').Backup>>}
1592
+ * @memberof BlockletManager
1593
+ */
1594
+ async getBlockletBackups({ did }) {
1595
+ return states.backup.getBlockletBackups({ did });
1596
+ }
1597
+
1464
1598
  // ============================================================================================
1465
1599
  // Private API that are used by self of helper function
1466
1600
  // ============================================================================================
@@ -1721,6 +1855,127 @@ class BlockletManager extends BaseBlockletManager {
1721
1855
  }
1722
1856
  }
1723
1857
 
1858
+ /**
1859
+ * FIXME: @wangshijun create audit log for this
1860
+ * @param {{
1861
+ * blocklet: import('@abtnode/client').BlockletState,
1862
+ * context: {
1863
+ * referrer: string;
1864
+ * user: {
1865
+ * did: string;
1866
+ * }
1867
+ * }
1868
+ * }} { blocklet, context }
1869
+ * @memberof BlockletManager
1870
+ */
1871
+ async _onBackupToSpaces({ blocklet, context, backup }) {
1872
+ const {
1873
+ referrer,
1874
+ user: { did: userDid },
1875
+ } = context;
1876
+ const {
1877
+ appDid,
1878
+ meta: { did: appPid },
1879
+ } = blocklet;
1880
+
1881
+ try {
1882
+ const spacesBackup = new SpacesBackup({ appDid, appPid, event: this, userDid, referrer, locale: 'en' });
1883
+ this.emit(BlockletEvents.backupProgress, {
1884
+ appDid,
1885
+ meta: { did: appPid },
1886
+ message: 'Start backup...',
1887
+ progress: 10,
1888
+ completed: false,
1889
+ });
1890
+ await spacesBackup.backup();
1891
+
1892
+ await states.backup.success(backup._id, {
1893
+ targetUrl: getBackupFilesUrlFromEndpoint(getDIDSpaceBackupEndpoint(blocklet?.environments)),
1894
+ });
1895
+
1896
+ // 备份成功了
1897
+ this.emit(BlockletEvents.backupProgress, { appDid, meta: { did: appPid }, completed: true, progress: 100 });
1898
+ } catch (error) {
1899
+ await states.backup.fail(backup._id, {
1900
+ message: error?.message,
1901
+ });
1902
+ this.emit(BlockletEvents.backupProgress, {
1903
+ appDid,
1904
+ meta: { did: appPid },
1905
+ completed: true,
1906
+ progress: -1,
1907
+ message: error?.message,
1908
+ });
1909
+ throw error;
1910
+ }
1911
+ }
1912
+
1913
+ /**
1914
+ * FIXME: @linchen support cancel
1915
+ * FIXME: @wangshijun create audit log for this
1916
+ * @param {{
1917
+ * input: import('@abtnode/client').RequestRestoreBlockletInput,
1918
+ * context: Record<string, string>,
1919
+ * }} {input, context}
1920
+ * @memberof BlockletManager
1921
+ */
1922
+ // eslint-disable-next-line no-unused-vars
1923
+ async _onRestoreFromSpaces({ input, context }) {
1924
+ if (input.delay) {
1925
+ await sleep(input.delay);
1926
+ }
1927
+
1928
+ const appPid = input.appDid;
1929
+
1930
+ this.emit(BlockletEvents.restoreProgress, {
1931
+ appDid: input.appDid,
1932
+ meta: { did: appPid },
1933
+ status: RESTORE_PROGRESS_STATUS.start,
1934
+ });
1935
+
1936
+ const userDid = context.user.did;
1937
+
1938
+ const spacesRestore = new SpacesRestore({ ...input, appPid, event: this, userDid, referrer: context.referrer });
1939
+ const params = await spacesRestore.restore();
1940
+
1941
+ const removeRestoreDir = () => {
1942
+ if (fs.existsSync(spacesRestore.restoreDir)) {
1943
+ fs.remove(spacesRestore.restoreDir).catch((err) => {
1944
+ logger.error('failed to remove restore dir', { error: err, dir: spacesRestore.restoreDir });
1945
+ });
1946
+ }
1947
+ };
1948
+
1949
+ this.emit(BlockletEvents.restoreProgress, {
1950
+ appDid: input.appDid,
1951
+ meta: { did: appPid },
1952
+ status: RESTORE_PROGRESS_STATUS.installing,
1953
+ });
1954
+
1955
+ try {
1956
+ await installApplicationFromBackup({
1957
+ url: `file://${spacesRestore.restoreDir}`,
1958
+ moveDir: true,
1959
+ ...merge(...params),
1960
+ manager: this,
1961
+ states,
1962
+ controller: input.controller,
1963
+ context: { ...context, startImmediately: true },
1964
+ });
1965
+
1966
+ removeRestoreDir();
1967
+ } catch (error) {
1968
+ removeRestoreDir();
1969
+ throw error;
1970
+ }
1971
+
1972
+ this.emit(BlockletEvents.restoreProgress, {
1973
+ appDid: input.appDid,
1974
+ meta: { did: appPid },
1975
+ status: RESTORE_PROGRESS_STATUS.completed,
1976
+ });
1977
+ }
1978
+
1724
1979
  async _updateBlockletEnvironment(did) {
1725
1980
  const blockletWithEnv = await this.getBlocklet(did);
1726
1981
  const blocklet = await states.blocklet.getBlocklet(did);
@@ -2641,28 +2896,45 @@ class BlockletManager extends BaseBlockletManager {
2641
2896
 
2642
2897
  /**
2643
2898
  * FIXME: @wangshijun create audit log for this
2644
- * @param {import('@abtnode/client').BlockletState} blocklet
2899
+ * @param {{
2900
+ * blocklet: import('@abtnode/client').BlockletState,
2901
+ * context: {
2902
+ * referrer: string;
2903
+ * user: {
2904
+ * did: string;
2905
+ * }
2906
+ * }
2907
+ * }} { blocklet, context }
2645
2908
  * @memberof BlockletManager
2646
2909
  */
2647
2910
  // eslint-disable-next-line no-unused-vars
2648
- async _backupToSpaces({ blocklet }, context) {
2649
- const userDid = context.user.did;
2650
- const { referrer } = context;
2911
+ async _backupToSpaces({ blocklet, context }) {
2912
+ const {
2913
+ user: { did: userDid },
2914
+ } = context;
2651
2915
  const {
2652
2916
  appDid,
2653
2917
  meta: { did: appPid },
2654
2918
  } = blocklet;
2655
2919
 
2656
- const spacesBackup = new SpacesBackup({ appDid, appPid, event: this, userDid, referrer });
2657
- this.emit(BlockletEvents.backupProgress, {
2658
- appDid,
2659
- meta: { did: appPid },
2660
- message: 'Start backup...',
2661
- progress: 10,
2662
- completed: false,
2920
+ const backup = await states.backup.start({
2921
+ appPid,
2922
+ userDid,
2923
+ strategy: 1,
2924
+ sourceUrl: path.join(this.dataDirs.tmp, 'backup', appDid),
2663
2925
  });
2664
- await spacesBackup.backup();
2665
- this.emit(BlockletEvents.backupProgress, { appDid, meta: { did: appPid }, completed: true, progress: 100 });
2926
+
2927
+ this.backupQueue.push(
2928
+ {
2929
+ entity: 'blocklet',
2930
+ action: 'backupToSpaces',
2931
+ id: appDid,
2932
+ blocklet,
2933
+ context,
2934
+ backup,
2935
+ },
2936
+ appDid
2937
+ );
2666
2938
  }
2667
2939
 
2668
2940
  /**
@@ -2690,45 +2962,16 @@ class BlockletManager extends BaseBlockletManager {
2690
2962
 
2691
2963
  const appPid = input.appDid;
2692
2964
 
2693
- this.emit(BlockletEvents.restoreProgress, {
2694
- appDid: input.appDid,
2695
- meta: { did: appPid },
2696
- status: RESTORE_PROGRESS_STATUS.start,
2697
- });
2698
-
2699
- const userDid = context.user.did;
2700
-
2701
- const spacesRestore = new SpacesRestore({ ...input, appPid, event: this, userDid, referrer: context.referrer });
2702
- const params = await spacesRestore.restore();
2703
-
2704
- const removeRestoreDir = () => {
2705
- if (fs.existsSync(spacesRestore.restoreDir)) {
2706
- fs.remove(spacesRestore.restoreDir).catch((err) => {
2707
- logger.error('failed to remove restore dir', { error: err, dir: spacesRestore.restoreDir });
2708
- });
2709
- }
2710
- };
2711
-
2712
- this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, status: RESTORE_PROGRESS_STATUS.installing });
2713
-
2714
- try {
2715
- await installApplicationFromBackup({
2716
- url: `file://${spacesRestore.restoreDir}`,
2717
- moveDir: true,
2718
- ...merge(...params),
2719
- manager: this,
2720
- states,
2721
- controller: input.controller,
2722
- context: { ...context, startImmediately: true },
2723
- });
2724
-
2725
- removeRestoreDir();
2726
- } catch (error) {
2727
- removeRestoreDir();
2728
- throw error;
2729
- }
2730
-
2731
- this.emit(BlockletEvents.restoreProgress, { appDid: input.appDid, status: RESTORE_PROGRESS_STATUS.completed });
2965
+ this.restoreQueue.push(
2966
+ {
2967
+ entity: 'blocklet',
2968
+ action: 'restoreFromSpaces',
2969
+ id: appPid,
2970
+ input,
2971
+ context,
2972
+ },
2973
+ appPid
2974
+ );
2732
2975
  }
2733
2976
 
2734
2977
  async _restoreFromDisk(input) {
@@ -5,6 +5,7 @@
5
5
  * event: import('events').EventEmitter,
6
6
  * userDid: string,
7
7
  * referrer: string,
8
+ * locale: 'zh' | 'en',
8
9
  * }} SpaceBackupInput
9
10
  *
10
11
  * @typedef {{
@@ -18,7 +19,7 @@
18
19
  const { isValid } = require('@arcblock/did');
19
20
  const { BLOCKLET_CONFIGURABLE_KEY, BlockletEvents } = require('@blocklet/constant');
20
21
  const { SpaceClient, BackupBlockletCommand } = require('@did-space/client');
21
- const { ensureDirSync } = require('fs-extra');
22
+ const { ensureDirSync, existsSync, remove } = require('fs-extra');
22
23
  const { isEmpty } = require('lodash');
23
24
  const { join, basename } = require('path');
24
25
  const { getAppName, getAppDescription } = require('@blocklet/meta/lib/util');
@@ -33,6 +34,7 @@ const { BlockletExtrasBackup } = require('./blocklet-extras');
33
34
  const { BlockletsBackup } = require('./blocklets');
34
35
  const { DataBackup } = require('./data');
35
36
  const { RoutingRuleBackup } = require('./routing-rule');
37
+ const { translate } = require('../../../locales');
36
38
 
37
39
  class SpacesBackup extends BaseBackup {
38
40
  /**
@@ -70,7 +72,7 @@ class SpacesBackup extends BaseBackup {
70
72
  * @type {string}
71
73
  * @memberof SpacesBackup
72
74
  */
73
- spaceEndpoint;
75
+ spaceBackupEndpoint;
74
76
 
75
77
  /**
76
78
  *
@@ -123,6 +125,7 @@ class SpacesBackup extends BaseBackup {
123
125
  await this.initialize();
124
126
  await this.export();
125
127
  await this.syncToSpaces();
128
+ await this.destroy();
126
129
  }
127
130
 
128
131
  async initialize() {
@@ -135,10 +138,10 @@ class SpacesBackup extends BaseBackup {
135
138
  this.backupDir = join(this.serverDir, 'tmp/backup', this.blocklet.appDid);
136
139
  ensureDirSync(this.backupDir);
137
140
 
138
- this.spaceEndpoint = this.blocklet.environments.find(
139
- (e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
141
+ this.spaceBackupEndpoint = this.blocklet.environments.find(
142
+ (e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT
140
143
  )?.value;
141
- if (isEmpty(this.spaceEndpoint)) {
144
+ if (isEmpty(this.spaceBackupEndpoint)) {
142
145
  throw new Error('spaceEndpoint cannot be empty');
143
146
  }
144
147
 
@@ -173,12 +176,12 @@ class SpacesBackup extends BaseBackup {
173
176
  const serverDid = node.did;
174
177
 
175
178
  const spaceClient = new SpaceClient({
176
- endpoint: this.spaceEndpoint,
179
+ endpoint: this.spaceBackupEndpoint,
177
180
  wallet: this.securityContext.signer,
178
181
  delegation: this.securityContext.delegation,
179
182
  });
180
183
 
181
- const { errorCount, message } = await spaceClient.send(
184
+ const { errorCount, message, statusCode } = await spaceClient.send(
182
185
  new BackupBlockletCommand({
183
186
  appDid: this.blocklet.appDid,
184
187
  appName: getAppName(this.blocklet),
@@ -204,7 +207,7 @@ class SpacesBackup extends BaseBackup {
204
207
  meta: { did: this.input.appDid },
205
208
  message: `Uploading file ${basename(data.key)} (${data.completed}/${data.total})`,
206
209
  // 0.8 是因为上传文件到 spaces 占进度的 80%,+ 20 是因为需要累加之前的进度
207
- progress: +Math.ceil(percent * 0.8).toFixed(2) + 20,
210
+ progress: +Math.floor(percent * 0.8).toFixed(2) + 20,
208
211
  completed: false,
209
212
  });
210
213
  },
@@ -212,7 +215,20 @@ class SpacesBackup extends BaseBackup {
212
215
  );
213
216
 
214
217
  if (errorCount !== 0) {
215
- throw new Error(`Sync to spaces encountered error: ${message}`);
218
+ const { locale } = this.input;
219
+ // @FIXME: get locale @jianchao
220
+ if (statusCode === 403) {
221
+ throw new Error(
222
+ `${translate(locale, 'backup.space.error.title')}: ${translate(locale, 'backup.space.error.forbidden')}`
223
+ );
224
+ }
225
+ throw new Error(`${translate(locale, 'backup.space.error.title')}: ${message}`);
226
+ }
227
+ }
228
+
229
+ async destroy() {
230
+ if (existsSync(this.backupDir)) {
231
+ await remove(this.backupDir);
216
232
  }
217
233
  }
218
234
  }
package/lib/event.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const get = require('lodash/get');
2
2
  const cloneDeep = require('lodash/cloneDeep');
3
3
  const { EventEmitter } = require('events');
4
- const { wipeSensitiveData, isBeforeInstalled } = require('@blocklet/meta/lib/util');
4
+ const { wipeSensitiveData } = require('@blocklet/meta/lib/util');
5
5
  const logger = require('@abtnode/logger')('@abtnode/core:event');
6
6
  const { BLOCKLET_MODES, BlockletStatus, BlockletSource, BlockletEvents } = require('@blocklet/constant');
7
7
  const { EVENTS } = require('@abtnode/constant');
@@ -262,15 +262,9 @@ module.exports = ({
262
262
  }
263
263
 
264
264
  if (
265
- [
266
- BlockletEvents.started,
267
- BlockletEvents.startFailed,
268
- BlockletEvents.stopped,
269
- BlockletEvents.reloaded,
270
- BlockletEvents.statusChange,
271
- ].includes(eventName) &&
272
- blocklet.status &&
273
- !isBeforeInstalled(blocklet.status)
265
+ [BlockletEvents.started, BlockletEvents.startFailed, BlockletEvents.stopped, BlockletEvents.reloaded].includes(
266
+ eventName
267
+ )
274
268
  ) {
275
269
  try {
276
270
  await blockletManager.runtimeMonitor.monit(blocklet.meta.did);
package/lib/index.js CHANGED
@@ -118,9 +118,29 @@ function ABTNode(options) {
118
118
  },
119
119
  options: {
120
120
  concurrency,
121
- maxRetries: 3,
121
+ // 备份自带重试机制
122
+ maxRetries: 0,
122
123
  retryDelay: 10000, // retry after 10 seconds
123
- maxTimeout: 60 * 1000 * 30, // throw timeout error after 30 minutes
124
+ maxTimeout: 60 * 1000 * 60, // throw timeout error after 60 minutes
125
+ id: (job) => (job ? md5(`${job.entity}-${job.action}-${job.id}`) : ''),
126
+ },
127
+ });
128
+
129
+ const restoreQueue = createQueue({
130
+ daemon: options.daemon,
131
+ name: 'restore_queue',
132
+ dataDir: dataDirs.core,
133
+ onJob: async (job) => {
134
+ if (typeof blockletManager.onJob === 'function') {
135
+ await blockletManager.onJob(job);
136
+ }
137
+ },
138
+ options: {
139
+ concurrency,
140
+ // 自带重试机制
141
+ maxRetries: 0,
142
+ retryDelay: 10000, // retry after 10 seconds
143
+ maxTimeout: 60 * 1000 * 60, // throw timeout error after 60 minutes
124
144
  id: (job) => (job ? md5(`${job.entity}-${job.action}-${job.id}`) : ''),
125
145
  },
126
146
  });
@@ -151,6 +171,7 @@ function ABTNode(options) {
151
171
  startQueue,
152
172
  installQueue,
153
173
  backupQueue,
174
+ restoreQueue,
154
175
  daemon: options.daemon,
155
176
  teamManager,
156
177
  });
@@ -255,6 +276,15 @@ function ABTNode(options) {
255
276
  updateBlockletOwner: blockletManager.updateOwner.bind(blockletManager),
256
277
  getBlockletRuntimeHistory: blockletManager.getRuntimeHistory.bind(blockletManager),
257
278
 
279
+ // blocklet spaces gateways
280
+ addBlockletSpaceGateway: blockletManager.addBlockletSpaceGateway.bind(blockletManager),
281
+ deleteBlockletSpaceGateway: blockletManager.deleteBlockletSpaceGateway.bind(blockletManager),
282
+ updateBlockletSpaceGateway: blockletManager.updateBlockletSpaceGateway.bind(blockletManager),
283
+ getBlockletSpaceGateways: blockletManager.getBlockletSpaceGateways.bind(blockletManager),
284
+
285
+ // blocklet backup record
286
+ getBlockletBackups: blockletManager.getBlockletBackups.bind(blockletManager),
287
+
258
288
  // Store
259
289
  getBlockletMeta: StoreUtil.getBlockletMeta,
260
290
  getStoreMeta: StoreUtil.getStoreMeta,
package/lib/locales/en.js CHANGED
@@ -4,4 +4,13 @@ module.exports = flat({
4
4
  registry: {
5
5
  getListError: 'Get Blocklet list from registry "{registryUrl}" failed.',
6
6
  },
7
+ backup: {
8
+ space: {
9
+ error: {
10
+ title: 'Backup to spaces encountered error',
11
+ forbidden:
12
+ 'You do not have permission to perform the backup, try restoring the application license on DID Spaces or reconnect to DID Spaces and try again',
13
+ },
14
+ },
15
+ },
7
16
  });
@@ -10,6 +10,13 @@ const replace = (template, data) =>
10
10
  template.replace(/{(\w*)}/g, (_, key) => (Object.prototype.hasOwnProperty.call(data, key) ? data[key] : ''));
11
11
 
12
12
  module.exports = {
13
+ /**
14
+ *
15
+ * @param {'zh' | 'en'} locale
16
+ * @param {string} key
17
+ * @param {Record<string, string>} data
18
+ * @returns {string}
19
+ */
13
20
  translate: (locale, key, data) => {
14
21
  if (tranlations.has(locale)) {
15
22
  return replace(tranlations.get(locale)[key], data);
package/lib/locales/zh.js CHANGED
@@ -4,4 +4,12 @@ module.exports = flat({
4
4
  registry: {
5
5
  getListError: '从 "{registryUrl}" 源获取 Blocklet 列表失败.',
6
6
  },
7
+ backup: {
8
+ space: {
9
+ error: {
10
+ title: '备份到 Spaces 发生错误',
11
+ forbidden: '你还没有权限执行备份操作,请尝试在 DID Spaces 上恢复应用授权或者重新连接 DID Spaces 后重试',
12
+ },
13
+ },
14
+ },
7
15
  });
@@ -91,7 +91,7 @@ class BlockletRuntimeMonitor extends EventEmitter {
91
91
  if (status !== BlockletStatus.running) {
92
92
  if (this.data[blockletDid]) {
93
93
  Object.keys(this.data[blockletDid]).forEach((key) => {
94
- delete this.data[blockletDid][key].runtimeInfo;
94
+ this.data[blockletDid][key].runtimeInfo = {};
95
95
  });
96
96
  }
97
97
  return;
@@ -117,6 +117,14 @@ const attachRuntimeDomainAliases = async ({ sites = [], context = {}, node }) =>
117
117
  });
118
118
  };
119
119
 
120
+ /**
121
+ * @description
122
+ * @param {{
123
+ * corsAllowedOrigins: Array<string>;
124
+ * }} site
125
+ * @param {string} rawUrl
126
+ * @return {void}
127
+ */
120
128
  const addCorsToSite = (site, rawUrl) => {
121
129
  if (!site || !rawUrl) {
122
130
  return;
@@ -374,12 +382,18 @@ const ensureCorsForWebWallet = async (sites) => {
374
382
  return sites;
375
383
  };
376
384
 
385
+ /**
386
+ * @description
387
+ * @param {Array<any>} [sites=[]]
388
+ * @param {Array<import('@abtnode/client').BlockletState>} blocklets
389
+ * @return {Promise<any>}
390
+ */
377
391
  const ensureCorsForDidSpace = async (sites = [], blocklets) => {
378
392
  return sites.map((site) => {
379
393
  const blocklet = blocklets.find((x) => x.meta.did === site.blockletDid);
380
394
  if (blocklet) {
381
395
  const endpoint = blocklet.environments.find(
382
- (x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
396
+ (x) => x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT
383
397
  );
384
398
  if (endpoint && isUrl(endpoint.value)) {
385
399
  addCorsToSite(site, endpoint.value);
@@ -272,6 +272,11 @@ const getLogContent = async (action, args, context, result, info, node) => {
272
272
  }
273
273
  };
274
274
 
275
+ /**
276
+ * @description 获取日志的种类
277
+ * @param {string} action
278
+ * @return {Promise<'blocklet' | 'team' | 'security' | 'integrations' | 'server' | 'certificates' | 'gateway' | ''>}
279
+ */
275
280
  const getLogCategory = (action) => {
276
281
  switch (action) {
277
282
  // blocklets
@@ -294,7 +299,7 @@ const getLogCategory = (action) => {
294
299
  case 'updateComponentMountPoint':
295
300
  return 'blocklet';
296
301
 
297
- // store
302
+ // store,此处应该返回 server
298
303
  case 'addBlockletStore':
299
304
  case 'deleteBlockletStore':
300
305
  case 'selectBlockletStore':
@@ -449,7 +454,7 @@ class AuditLogState extends BaseState {
449
454
  const data = await this.insert({
450
455
  scope: getScope(args) || info.did, // server or blocklet did
451
456
  action,
452
- category: await getLogCategory(action, args, context, result, info, node),
457
+ category: await getLogCategory(action),
453
458
  content: (await getLogContent(action, args, context, result, info, node)).trim(),
454
459
  actor: pick(user.actual || user, ['did', 'fullName', 'role']),
455
460
  extra: args,
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @typedef{{
3
+ appPid: string; // 此处建议使用 appPid
4
+ userDid: string;
5
+ strategy?: 0 | 1;
6
+
7
+ sourceUrl: string;
8
+
9
+ target?: "Spaces" | 'Local';
10
+ targetUrl?: string;
11
+
12
+ createdAt: string;
13
+ updatedAt?: string;
14
+ status?: 0 | 1;
15
+ message?: string;
16
+ * }} Backup
17
+ */
18
+
19
+ const { Joi } = require('@arcblock/validator');
20
+ const BaseState = require('./base');
21
+ const { validateBackupSuccess, validateBackupFail, validateBackupStart } = require('../validators/backup.js');
22
+
23
+ const validateBackup = Joi.object({
24
+ appPid: Joi.DID(),
25
+ userDid: Joi.DID(),
26
+ // 备份策略: 0 表示自动,1 表示手动,建议使用常量
27
+ strategy: Joi.number().valid(0, 1).default(0).optional(),
28
+
29
+ // 形如, /User/allen/blocklet-server/data/discuss-kit
30
+ sourceUrl: Joi.string().required(),
31
+
32
+ // 备份文件存储在哪?本地还是 spaces 的那个位置?
33
+ // 如果存储在 Spaces, 形如: https://bbqaw5mgxc6fnihrwqcejcxvukkdgkk4anwxwk5msvm.did.abtnet.io/app/space/zNKhe8jwgNZX2z7ZUfwNddNECxSe3wyg7VtS
34
+ // 如果存储在 Local,形如: /User/allen/discuss-kit
35
+ target: Joi.string().valid('Spaces', 'Local').optional().default('Spaces'),
36
+ targetUrl: Joi.string().optional().allow('').default(''),
37
+
38
+ createdAt: Joi.string()
39
+ .optional()
40
+ .default(() => new Date().toISOString()),
41
+ updatedAt: Joi.string().optional().default('').allow(''),
42
+
43
+ // 0 表示成功了,建议使用常量表示,默认是 1 表示错误的
44
+ status: Joi.number().optional().allow(null),
45
+ // 发生错误的时候可以用来存储错误下信息
46
+ message: Joi.string().optional().default('').allow(''),
47
+ });
48
+
49
+ /**
50
+ * @description
51
+ * @class BackupState
52
+ */
53
+ class BackupState extends BaseState {
54
+ constructor(baseDir, config = {}) {
55
+ super(baseDir, {
56
+ filename: 'backup.db',
57
+ ...config,
58
+ });
59
+ }
60
+
61
+ /**
62
+ * @description
63
+ * @param {Pick<Backup, 'appPid' | 'userDid' | 'strategy' | 'sourceUrl' | 'target'>} backup
64
+ * @return {Promise<Backup & {_id: string}>}
65
+ * @memberof BackupState
66
+ */
67
+ async start(backup) {
68
+ const { error, value } = validateBackupStart.validate(backup, {
69
+ stripUnknown: true,
70
+ allowUnknown: true,
71
+ });
72
+
73
+ if (error) {
74
+ throw error;
75
+ }
76
+
77
+ return this.create({
78
+ ...value,
79
+ });
80
+ }
81
+
82
+ /**
83
+ * @description
84
+ * @param {string} id
85
+ * @param {Pick<Backup, 'targetUrl'>} successBackupInfo
86
+ * @return {PromiseLike<import('@nedb/core').UpdateResult<Backup>>}
87
+ * @memberof BackupState
88
+ */
89
+ async success(id, successBackupInfo) {
90
+ const { error, value } = validateBackupSuccess.validate(successBackupInfo, {
91
+ stripUnknown: true,
92
+ allowUnknown: true,
93
+ });
94
+
95
+ if (error) {
96
+ throw error;
97
+ }
98
+
99
+ return this.update(
100
+ { _id: id },
101
+ {
102
+ $set: {
103
+ ...value,
104
+ status: 0,
105
+ updatedAt: new Date().toISOString(),
106
+ },
107
+ }
108
+ );
109
+ }
110
+
111
+ /**
112
+ * @description
113
+ * @param {string} id
114
+ * @param {Pick<Backup, 'message'>} errorBackupInfo
115
+ * @return {PromiseLike<import('@nedb/core').UpdateResult<Backup>>}
116
+ * @memberof BackupState
117
+ */
118
+ async fail(id, errorBackupInfo) {
119
+ const { error, value } = validateBackupFail.validate(errorBackupInfo, {
120
+ stripUnknown: true,
121
+ allowUnknown: true,
122
+ });
123
+
124
+ if (error) {
125
+ throw error;
126
+ }
127
+
128
+ return this.update(
129
+ { _id: id },
130
+ {
131
+ $set: {
132
+ ...value,
133
+ status: 1,
134
+ updatedAt: new Date().toISOString(),
135
+ },
136
+ }
137
+ );
138
+ }
139
+
140
+ /**
141
+ * @description
142
+ * @param {Backup} backup
143
+ * @return {Promise<Backup & {_id: string}>}
144
+ * @memberof BackupState
145
+ */
146
+ async create(backup) {
147
+ const { error, value } = validateBackup.validate(backup, {
148
+ allowUnknown: true,
149
+ stripUnknown: true,
150
+ });
151
+
152
+ if (error) {
153
+ throw new Error(error.message);
154
+ }
155
+
156
+ return this.insert(value);
157
+ }
158
+
159
+ /**
160
+ * @description
161
+ * @param {{ did: string }} { did }
162
+ * @return {Promise<Array<import('@abtnode/client').Backup>>}
163
+ */
164
+ async getBlockletBackups({ did }) {
165
+ const backups = await this.cursor({
166
+ appPid: did,
167
+ })
168
+ .sort({
169
+ updatedAt: -1,
170
+ })
171
+ .exec();
172
+
173
+ return backups ?? [];
174
+ }
175
+ }
176
+
177
+ module.exports = BackupState;
@@ -91,6 +91,12 @@ class BlockletExtrasState extends BaseState {
91
91
  this.generateExtraFns();
92
92
  }
93
93
 
94
+ /**
95
+ * @description
96
+ * @param {string} did
97
+ * @return {Promise<number>}
98
+ * @memberof BlockletExtrasState
99
+ */
94
100
  delete(did) {
95
101
  return this.remove({ did });
96
102
  }
@@ -11,6 +11,7 @@ const SessionState = require('./session');
11
11
  const ExtrasState = require('./blocklet-extras');
12
12
  const CacheState = require('./cache');
13
13
  const AuditLogState = require('./audit-log');
14
+ const BackupState = require('./backup');
14
15
 
15
16
  const init = (dataDirs, config) => {
16
17
  const notificationState = new NotificationState(dataDirs.core, config);
@@ -25,6 +26,7 @@ const init = (dataDirs, config) => {
25
26
  const extrasState = new ExtrasState(dataDirs.core, config);
26
27
  const cacheState = new CacheState(dataDirs.core, config);
27
28
  const auditLogState = new AuditLogState(dataDirs.core, config);
29
+ const backupState = new BackupState(dataDirs.core, config);
28
30
 
29
31
  return {
30
32
  node: nodeState,
@@ -39,6 +41,7 @@ const init = (dataDirs, config) => {
39
41
  blockletExtras: extrasState,
40
42
  cache: cacheState,
41
43
  auditLog: auditLogState,
44
+ backup: backupState,
42
45
  };
43
46
  };
44
47
 
@@ -7,7 +7,6 @@ const { isValid } = require('@arcblock/did');
7
7
  const { PASSPORT_STATUS } = require('@abtnode/constant');
8
8
  const { upsertToPassports } = require('@abtnode/auth/lib/passport');
9
9
  const { fromAppDid } = require('@arcblock/did-ext');
10
- const { types } = require('@arcblock/did');
11
10
 
12
11
  const BaseState = require('./base');
13
12
  const { validateOwner } = require('../util');
@@ -400,7 +399,7 @@ class User extends BaseState {
400
399
  sourceProvider = 'wallet';
401
400
  connectedAccounts.forEach((account) => {
402
401
  if (account.id && blockletSk) {
403
- const accountWallet = fromAppDid(account.id, blockletSk, types.RoleType.ROLE_ACCOUNT);
402
+ const accountWallet = fromAppDid(account.id, blockletSk);
404
403
  account.did = accountWallet.address;
405
404
  account.pk = accountWallet.publicKey;
406
405
  account.firstLoginAt = account.firstLoginAt || new Date().toISOString();
@@ -543,7 +543,8 @@ const startBlockletProcess = async (
543
543
  namespace: 'blocklets',
544
544
  name: processId,
545
545
  cwd: appCwd,
546
- time: true,
546
+ // FIXME @linchen [] does not work, so use () here
547
+ log_date_format: '(YYYY-MM-DD HH:mm:ss)',
547
548
  output: path.join(logsDir, 'output.log'),
548
549
  error: path.join(logsDir, 'error.log'),
549
550
  wait_ready: process.env.NODE_ENV !== 'test',
@@ -1699,6 +1700,16 @@ const validateAppConfig = async (config, states) => {
1699
1700
  throw new Error(`${x.key}(${x.value}) is not a valid http address`);
1700
1701
  }
1701
1702
  }
1703
+
1704
+ if (x.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT) {
1705
+ if (isEmpty(x.value)) {
1706
+ throw new Error(`${x.key} can not be empty`);
1707
+ }
1708
+
1709
+ if (!isUrl(x.value)) {
1710
+ throw new Error(`${x.key}(${x.value}) is not a valid http address`);
1711
+ }
1712
+ }
1702
1713
  };
1703
1714
 
1704
1715
  const checkDuplicateAppSk = async ({ sk, did, states }) => {
package/lib/util/queue.js CHANGED
@@ -1,6 +1,24 @@
1
1
  const path = require('path');
2
2
  const createQueue = require('@abtnode/queue');
3
3
 
4
+ /**
5
+ *
6
+ * @typedef {
7
+ * EventEmitter & {
8
+ * store: JobStore;
9
+ * push: (...args: {
10
+ * job: any;
11
+ * jobId: string;
12
+ * persist: boolean;
13
+ * delay: number;
14
+ * }) => EventEmitter;
15
+ * get: (id: string) => Promise<any>;
16
+ * cancel: (id: string) => Promise<...>;
17
+ * options: {}
18
+ * }
19
+ * } Queue
20
+ */
21
+
4
22
  /**
5
23
  *
6
24
  * @param {Object} Options
@@ -14,6 +32,7 @@ const createQueue = require('@abtnode/queue');
14
32
  * @param {number} Options.options.maxRetries [param=1] number of max retries, default 1
15
33
  * @param {number} Options.options.maxTimeout [param=86400000] max timeout, in ms, default 86400000ms(1d)
16
34
  * @param {number} Options.options.retryDelay [param=0] retry delay, in ms, default 0ms
35
+ * @returns {Queue}
17
36
  */
18
37
  module.exports = ({ name, dataDir, onJob, daemon = false, options = {} }) => {
19
38
  if (daemon) {
@@ -0,0 +1,39 @@
1
+ const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
2
+ const isEmpty = require('lodash/isEmpty');
3
+ const joinUrl = require('url-join');
4
+
5
+ /**
6
+ * @description
7
+ * @param {import('@abtnode/client').ConfigEntry[]} configs
8
+ * @return {string | null}
9
+ */
10
+ const getDIDSpaceBackupEndpoint = (configs) => {
11
+ return (
12
+ configs?.find((config) => config.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_BACKUP_ENDPOINT)?.value || null
13
+ );
14
+ };
15
+
16
+ /**
17
+ * @FIXME 希望之后放在 sdk 中 @jianchao
18
+ * @description
19
+ * @param {string} endpoint
20
+ * @returns {string}
21
+ */
22
+ function getBackupFilesUrlFromEndpoint(endpoint) {
23
+ if (isEmpty(endpoint)) {
24
+ throw new Error(`Endpoint(${endpoint}) cannot be empty`);
25
+ }
26
+
27
+ const prefix = endpoint.replace(/\/api\/space\/.+/, '');
28
+
29
+ const strArray = endpoint.replace(/\/$/, '').split('/');
30
+ const spaceDid = strArray.at(-4);
31
+ const appDid = strArray.at(-2);
32
+
33
+ return joinUrl(prefix, 'space', spaceDid, 'apps', appDid, 'explorer', `?key=/apps/${appDid}/.did-objects/${appDid}/`);
34
+ }
35
+
36
+ module.exports = {
37
+ getDIDSpaceBackupEndpoint,
38
+ getBackupFilesUrlFromEndpoint,
39
+ };
@@ -0,0 +1,24 @@
1
+ const { Joi } = require('@arcblock/validator');
2
+
3
+ const validateBackupStart = Joi.object({
4
+ appPid: Joi.DID().required(),
5
+ userDid: Joi.DID().required(),
6
+ strategy: Joi.number().valid(0, 1).optional().default(0),
7
+
8
+ sourceUrl: Joi.string().required(),
9
+ target: Joi.string().valid('Spaces', 'Local').optional().default('Spaces'),
10
+ });
11
+
12
+ const validateBackupSuccess = Joi.object({
13
+ targetUrl: Joi.string().required(),
14
+ });
15
+
16
+ const validateBackupFail = Joi.object({
17
+ message: Joi.string().required(),
18
+ });
19
+
20
+ module.exports = {
21
+ validateBackupStart,
22
+ validateBackupSuccess,
23
+ validateBackupFail,
24
+ };
@@ -0,0 +1,30 @@
1
+ const { Joi } = require('@arcblock/validator');
2
+
3
+ const validateAddSpaceGateway = Joi.object({
4
+ url: Joi.string()
5
+ .uri({ scheme: ['http', 'https'] })
6
+ .required(),
7
+ name: Joi.string().required(),
8
+ protected: Joi.boolean().optional().allow(null),
9
+ endpoint: Joi.string()
10
+ .uri({ scheme: ['http', 'https'] })
11
+ .optional()
12
+ .allow(''),
13
+ });
14
+
15
+ const validateUpdateSpaceGateway = Joi.object({
16
+ url: Joi.string()
17
+ .uri({ scheme: ['http', 'https'] })
18
+ .required(),
19
+ name: Joi.string().optional().allow(''),
20
+ protected: Joi.boolean().optional().allow(null),
21
+ endpoint: Joi.string()
22
+ .uri({ scheme: ['http', 'https'] })
23
+ .optional()
24
+ .allow(''),
25
+ });
26
+
27
+ module.exports = {
28
+ validateAddSpaceGateway,
29
+ validateUpdateSpaceGateway,
30
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.7",
6
+ "version": "1.16.8-beta-ca58a421",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,18 +19,18 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@abtnode/auth": "1.16.7",
23
- "@abtnode/certificate-manager": "1.16.7",
24
- "@abtnode/constant": "1.16.7",
25
- "@abtnode/cron": "1.16.7",
26
- "@abtnode/db": "1.16.7",
27
- "@abtnode/logger": "1.16.7",
28
- "@abtnode/queue": "1.16.7",
29
- "@abtnode/rbac": "1.16.7",
30
- "@abtnode/router-provider": "1.16.7",
31
- "@abtnode/static-server": "1.16.7",
32
- "@abtnode/timemachine": "1.16.7",
33
- "@abtnode/util": "1.16.7",
22
+ "@abtnode/auth": "1.16.8-beta-ca58a421",
23
+ "@abtnode/certificate-manager": "1.16.8-beta-ca58a421",
24
+ "@abtnode/constant": "1.16.8-beta-ca58a421",
25
+ "@abtnode/cron": "1.16.8-beta-ca58a421",
26
+ "@abtnode/db": "1.16.8-beta-ca58a421",
27
+ "@abtnode/logger": "1.16.8-beta-ca58a421",
28
+ "@abtnode/queue": "1.16.8-beta-ca58a421",
29
+ "@abtnode/rbac": "1.16.8-beta-ca58a421",
30
+ "@abtnode/router-provider": "1.16.8-beta-ca58a421",
31
+ "@abtnode/static-server": "1.16.8-beta-ca58a421",
32
+ "@abtnode/timemachine": "1.16.8-beta-ca58a421",
33
+ "@abtnode/util": "1.16.8-beta-ca58a421",
34
34
  "@arcblock/did": "1.18.78",
35
35
  "@arcblock/did-auth": "1.18.78",
36
36
  "@arcblock/did-ext": "^1.18.78",
@@ -39,10 +39,11 @@
39
39
  "@arcblock/event-hub": "1.18.78",
40
40
  "@arcblock/jwt": "^1.18.78",
41
41
  "@arcblock/pm2-events": "^0.0.5",
42
+ "@arcblock/validator": "^1.18.77",
42
43
  "@arcblock/vc": "1.18.78",
43
- "@blocklet/constant": "1.16.7",
44
- "@blocklet/meta": "1.16.7",
45
- "@blocklet/sdk": "1.16.7",
44
+ "@blocklet/constant": "1.16.8-beta-ca58a421",
45
+ "@blocklet/meta": "1.16.8-beta-ca58a421",
46
+ "@blocklet/sdk": "1.16.8-beta-ca58a421",
46
47
  "@did-space/client": "^0.2.90",
47
48
  "@fidm/x509": "^1.2.1",
48
49
  "@ocap/mcrypto": "1.18.78",
@@ -93,5 +94,5 @@
93
94
  "express": "^4.18.2",
94
95
  "jest": "^27.5.1"
95
96
  },
96
- "gitHead": "dadd7bfa319e2ee5144bdccb6e280ffe4b8d2679"
97
+ "gitHead": "d0f724c8082572a01b6e9287df6c3d0663ec9c57"
97
98
  }