@abtnode/core 1.16.7 → 1.16.8-beta-0c0c5eb2

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
 
@@ -1222,6 +1235,7 @@ class BlockletManager extends BaseBlockletManager {
1222
1235
  return newState;
1223
1236
  }
1224
1237
 
1238
+ // TODO: this method can be removed if title is not changed anymore
1225
1239
  async updateComponentTitle({ did, rootDid: inputRootDid, title }) {
1226
1240
  await titleSchema.validateAsync(title);
1227
1241
 
@@ -1367,6 +1381,16 @@ class BlockletManager extends BaseBlockletManager {
1367
1381
  }
1368
1382
 
1369
1383
  try {
1384
+ if (
1385
+ blocklet.meta?.group === BlockletGroup.gateway &&
1386
+ !blocklet.children?.length &&
1387
+ isInProgress(blocklet.status)
1388
+ ) {
1389
+ const res = await states.blocklet.setBlockletStatus(did, BlockletStatus.stopped);
1390
+ this.emit(BlockletEvents.statusChange, res);
1391
+ return res;
1392
+ }
1393
+
1370
1394
  const status = await getBlockletStatusFromProcess(blocklet);
1371
1395
  if (blocklet.status !== status) {
1372
1396
  const res = await states.blocklet.setBlockletStatus(did, status);
@@ -1406,6 +1430,15 @@ class BlockletManager extends BaseBlockletManager {
1406
1430
  });
1407
1431
  }
1408
1432
 
1433
+ /**
1434
+ * @description
1435
+ * @param {{
1436
+ * entity: string;
1437
+ * action: string;
1438
+ * id: string;
1439
+ * }} job
1440
+ * @memberof BlockletManager
1441
+ */
1409
1442
  async onJob(job) {
1410
1443
  if (job.entity === 'blocklet') {
1411
1444
  if (job.action === 'download') {
@@ -1418,6 +1451,14 @@ class BlockletManager extends BaseBlockletManager {
1418
1451
  if (job.action === 'check_if_started') {
1419
1452
  await this._onCheckIfStarted(job);
1420
1453
  }
1454
+
1455
+ if (job.action === 'backupToSpaces') {
1456
+ await this._onBackupToSpaces(job);
1457
+ }
1458
+
1459
+ if (job.action === 'restoreFromSpaces') {
1460
+ await this._onRestoreFromSpaces(job);
1461
+ }
1421
1462
  }
1422
1463
  }
1423
1464
 
@@ -1461,6 +1502,100 @@ class BlockletManager extends BaseBlockletManager {
1461
1502
  ];
1462
1503
  }
1463
1504
 
1505
+ /**
1506
+ * @description
1507
+ * @param {{
1508
+ * did: import('@abtnode/client').RequestAddBlockletSpaceGatewayInput,
1509
+ * spaceGateway: import('@abtnode/client').SpaceGateway
1510
+ * }} { did, spaceGateway }
1511
+ * @return {Promise<void>}
1512
+ * @memberof BlockletManager
1513
+ */
1514
+ async addBlockletSpaceGateway({ did, spaceGateway }) {
1515
+ const spaceGateways = await this.getBlockletSpaceGateways({ did });
1516
+
1517
+ const { error, value } = validateAddSpaceGateway.validate(spaceGateway, {
1518
+ stripUnknown: true,
1519
+ allowUnknown: true,
1520
+ });
1521
+
1522
+ if (error) {
1523
+ throw error;
1524
+ }
1525
+
1526
+ spaceGateways.push(value);
1527
+
1528
+ await states.blockletExtras.setSettings(did, { spaceGateways });
1529
+ }
1530
+
1531
+ /**
1532
+ * @description
1533
+ * @param {import('@abtnode/client').RequestAddBlockletSpaceGatewayInput} { did, spaceGateway }
1534
+ * @return {Promise<void>}
1535
+ * @memberof BlockletManager
1536
+ */
1537
+ async deleteBlockletSpaceGateway({ did, url }) {
1538
+ const spaceGateways = await this.getBlockletSpaceGateways({ did });
1539
+
1540
+ const latestSpaceGateways = spaceGateways.filter((s) => s?.url !== url);
1541
+
1542
+ await states.blockletExtras.setSettings(did, { spaceGateways: latestSpaceGateways });
1543
+ }
1544
+
1545
+ /**
1546
+ * @description
1547
+ * @param {{
1548
+ * did: string,
1549
+ * where: import('@abtnode/client').SpaceGateway
1550
+ * spaceGateway: import('@abtnode/client').SpaceGateway
1551
+ * }} { did, spaceGateway }
1552
+ * @return {Promise<void>}
1553
+ * @memberof BlockletManager
1554
+ */
1555
+ async updateBlockletSpaceGateway({ did, where, spaceGateway }) {
1556
+ const { error, value } = validateUpdateSpaceGateway.validate(spaceGateway, {
1557
+ stripUnknown: true,
1558
+ allowUnknown: true,
1559
+ });
1560
+
1561
+ if (error) {
1562
+ throw error;
1563
+ }
1564
+
1565
+ const spaceGateways = await this.getBlockletSpaceGateways({ did });
1566
+
1567
+ for (const s of spaceGateways) {
1568
+ if (s.url === where?.url) {
1569
+ Object.assign(s, value);
1570
+ break;
1571
+ }
1572
+ }
1573
+
1574
+ await states.blockletExtras.setSettings(did, { spaceGateways });
1575
+ }
1576
+
1577
+ /**
1578
+ * @description
1579
+ * @param {{ did: string }} { did }
1580
+ * @return {Promise<Array<import('@abtnode/client').SpaceGateway>>}
1581
+ * @memberof BlockletManager
1582
+ */
1583
+ async getBlockletSpaceGateways({ did }) {
1584
+ const spaceGateways = await states.blockletExtras.getSettings(did, 'spaceGateways', []);
1585
+
1586
+ return spaceGateways;
1587
+ }
1588
+
1589
+ /**
1590
+ * @description
1591
+ * @param {{ did: string }} { did }
1592
+ * @return {Promise<Array<import('@abtnode/client').Backup>>}
1593
+ * @memberof BlockletManager
1594
+ */
1595
+ async getBlockletBackups({ did }) {
1596
+ return states.backup.getBlockletBackups({ did });
1597
+ }
1598
+
1464
1599
  // ============================================================================================
1465
1600
  // Private API that are used by self of helper function
1466
1601
  // ============================================================================================
@@ -1721,6 +1856,127 @@ class BlockletManager extends BaseBlockletManager {
1721
1856
  }
1722
1857
  }
1723
1858
 
1859
+ /**
1860
+ * FIXME: @wangshijun create audit log for this
1861
+ * @param {{
1862
+ * blocklet: import('@abtnode/client').BlockletState,
1863
+ * context: {
1864
+ * referrer: string;
1865
+ * user: {
1866
+ * did: string;
1867
+ * }
1868
+ * }
1869
+ * }} { blocklet, context }
1870
+ * @memberof BlockletManager
1871
+ */
1872
+ async _onBackupToSpaces({ blocklet, context, backup }) {
1873
+ const {
1874
+ referrer,
1875
+ user: { did: userDid },
1876
+ } = context;
1877
+ const {
1878
+ appDid,
1879
+ meta: { did: appPid },
1880
+ } = blocklet;
1881
+
1882
+ try {
1883
+ const spacesBackup = new SpacesBackup({ appDid, appPid, event: this, userDid, referrer, locale: 'en' });
1884
+ this.emit(BlockletEvents.backupProgress, {
1885
+ appDid,
1886
+ meta: { did: appPid },
1887
+ message: 'Start backup...',
1888
+ progress: 10,
1889
+ completed: false,
1890
+ });
1891
+ await spacesBackup.backup();
1892
+
1893
+ await states.backup.success(backup._id, {
1894
+ targetUrl: getBackupFilesUrlFromEndpoint(getDIDSpaceBackupEndpoint(blocklet?.environments)),
1895
+ });
1896
+
1897
+ // 备份成功了
1898
+ this.emit(BlockletEvents.backupProgress, { appDid, meta: { did: appPid }, completed: true, progress: 100 });
1899
+ } catch (error) {
1900
+ await states.backup.fail(backup._id, {
1901
+ message: error?.message,
1902
+ });
1903
+ this.emit(BlockletEvents.backupProgress, {
1904
+ appDid,
1905
+ meta: { did: appPid },
1906
+ completed: true,
1907
+ progress: -1,
1908
+ message: error?.message,
1909
+ });
1910
+ throw error;
1911
+ }
1912
+ }
1913
+
1914
+ /**
1915
+ * FIXME: @linchen support cancel
1916
+ * FIXME: @wangshijun create audit log for this
1917
+ * @param {{
1918
+ * input: import('@abtnode/client').RequestRestoreBlockletInput,
1919
+ * context: Record<string, string>,
1920
+ * }} {input, context}
1921
+ * @memberof BlockletManager
1922
+ */
1923
+ // eslint-disable-next-line no-unused-vars
1924
+ async _onRestoreFromSpaces({ input, context }) {
1925
+ if (input.delay) {
1926
+ await sleep(input.delay);
1927
+ }
1928
+
1929
+ const appPid = input.appDid;
1930
+
1931
+ this.emit(BlockletEvents.restoreProgress, {
1932
+ appDid: input.appDid,
1933
+ meta: { did: appPid },
1934
+ status: RESTORE_PROGRESS_STATUS.start,
1935
+ });
1936
+
1937
+ const userDid = context.user.did;
1938
+
1939
+ const spacesRestore = new SpacesRestore({ ...input, appPid, event: this, userDid, referrer: context.referrer });
1940
+ const params = await spacesRestore.restore();
1941
+
1942
+ const removeRestoreDir = () => {
1943
+ if (fs.existsSync(spacesRestore.restoreDir)) {
1944
+ fs.remove(spacesRestore.restoreDir).catch((err) => {
1945
+ logger.error('failed to remove restore dir', { error: err, dir: spacesRestore.restoreDir });
1946
+ });
1947
+ }
1948
+ };
1949
+
1950
+ this.emit(BlockletEvents.restoreProgress, {
1951
+ appDid: input.appDid,
1952
+ meta: { did: appPid },
1953
+ status: RESTORE_PROGRESS_STATUS.installing,
1954
+ });
1955
+
1956
+ try {
1957
+ await installApplicationFromBackup({
1958
+ url: `file://${spacesRestore.restoreDir}`,
1959
+ moveDir: true,
1960
+ ...merge(...params),
1961
+ manager: this,
1962
+ states,
1963
+ controller: input.controller,
1964
+ context: { ...context, startImmediately: true },
1965
+ });
1966
+
1967
+ removeRestoreDir();
1968
+ } catch (error) {
1969
+ removeRestoreDir();
1970
+ throw error;
1971
+ }
1972
+
1973
+ this.emit(BlockletEvents.restoreProgress, {
1974
+ appDid: input.appDid,
1975
+ meta: { did: appPid },
1976
+ status: RESTORE_PROGRESS_STATUS.completed,
1977
+ });
1978
+ }
1979
+
1724
1980
  async _updateBlockletEnvironment(did) {
1725
1981
  const blockletWithEnv = await this.getBlocklet(did);
1726
1982
  const blocklet = await states.blocklet.getBlocklet(did);
@@ -2641,28 +2897,45 @@ class BlockletManager extends BaseBlockletManager {
2641
2897
 
2642
2898
  /**
2643
2899
  * FIXME: @wangshijun create audit log for this
2644
- * @param {import('@abtnode/client').BlockletState} blocklet
2900
+ * @param {{
2901
+ * blocklet: import('@abtnode/client').BlockletState,
2902
+ * context: {
2903
+ * referrer: string;
2904
+ * user: {
2905
+ * did: string;
2906
+ * }
2907
+ * }
2908
+ * }} { blocklet, context }
2645
2909
  * @memberof BlockletManager
2646
2910
  */
2647
2911
  // eslint-disable-next-line no-unused-vars
2648
- async _backupToSpaces({ blocklet }, context) {
2649
- const userDid = context.user.did;
2650
- const { referrer } = context;
2912
+ async _backupToSpaces({ blocklet, context }) {
2913
+ const {
2914
+ user: { did: userDid },
2915
+ } = context;
2651
2916
  const {
2652
2917
  appDid,
2653
2918
  meta: { did: appPid },
2654
2919
  } = blocklet;
2655
2920
 
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,
2921
+ const backup = await states.backup.start({
2922
+ appPid,
2923
+ userDid,
2924
+ strategy: 1,
2925
+ sourceUrl: path.join(this.dataDirs.tmp, 'backup', appDid),
2663
2926
  });
2664
- await spacesBackup.backup();
2665
- this.emit(BlockletEvents.backupProgress, { appDid, meta: { did: appPid }, completed: true, progress: 100 });
2927
+
2928
+ this.backupQueue.push(
2929
+ {
2930
+ entity: 'blocklet',
2931
+ action: 'backupToSpaces',
2932
+ id: appDid,
2933
+ blocklet,
2934
+ context,
2935
+ backup,
2936
+ },
2937
+ appDid
2938
+ );
2666
2939
  }
2667
2940
 
2668
2941
  /**
@@ -2690,45 +2963,16 @@ class BlockletManager extends BaseBlockletManager {
2690
2963
 
2691
2964
  const appPid = input.appDid;
2692
2965
 
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 });
2966
+ this.restoreQueue.push(
2967
+ {
2968
+ entity: 'blocklet',
2969
+ action: 'restoreFromSpaces',
2970
+ id: appPid,
2971
+ input,
2972
+ context,
2973
+ },
2974
+ appPid
2975
+ );
2732
2976
  }
2733
2977
 
2734
2978
  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
  });
@@ -0,0 +1,66 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ /* eslint-disable no-continue */
3
+
4
+ const blocklets = {
5
+ z8ia4e5vAeDsQEE2P26bQqz9oWR1Lxg9qUMaV: 'Static Demo',
6
+ z8iZyVVn6XsvcuiYhtdw3GoasMbtqR9BjvJz3: 'Blockchain Explorer',
7
+ z8iZqkCjLP6TZpR12tT3jESWxB8SGzNsx8nZa: 'NFT Store',
8
+ z8ia1WEiBZ7hxURf6LwH21Wpg99vophFwSJdu: 'Discuss Kit',
9
+ z8ia5AUWNBoc5Jw6Zf2ru97W1y6PZVFiFa7h9: 'Coming Soon Page',
10
+ z8iZiDFg3vkkrPwsiba1TLXy3H9XHzFERsP8o: 'Pages Kit',
11
+ z8iZscGk6ohCejHEiX16C7apdFC7JrPYD1J4Z: 'Virtual Gift Card',
12
+ z8ia2birZzhjbXqKnxPUUivmqErdsf3724tr6: 'NFT Maker',
13
+ z8ia1ieY5KhEC4LMRETzS5nUwD7PvAND8qkfX: 'NFT Blender',
14
+ z8iZqeUACK955YaBWqEd8aKg3tTki1GpvE2Wu: 'ArcBridge Node',
15
+ z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ: 'Blocklet Store',
16
+ z8ia2KGe3icfgRcVc9C1qCbWTBbpP2TrfPu7T: 'FS Chain Manager',
17
+ z8ia3xzq2tMq8CRHfaXj1BTYJyYnEcHbqP8cJ: 'AI Kit',
18
+ z8iZvmERrWxqReWe1HZmkAaZvFeRpkXutfKDk: 'NFT Marketplace',
19
+ z8iZpnScvjjeeyYZQoHSdXm4GQTqcfTTGkyPP: 'DID Wallet',
20
+ z8iZrihfHTTBCBpDqCzrjFer5jop383b5hdPh: 'DID Spaces Enterprise',
21
+ z8ia1mAXo8ZE7ytGF36L5uBf9kD2kenhqFGp9: 'Image Bin',
22
+ z8iZu6GDcVFaSsT7LjrBJC9uAfM6HKyQaCD9U: 'Tower Blocks',
23
+ z8iZyhourKXqn8JKHbFcQDqWoAMsR6ZEi5nCW: 'Mine Sweeper',
24
+ z8ia48jeqzdNhr9smse1tQCmt72G5PnSZaTax: 'MultiSig Vault',
25
+ z8iZngXotuUXxsm6imc7naUUy1G5ycVs7A34H: 'Uptime Kuma',
26
+ z8iZvMrKPa7qy2nxfrKremuSm8bE9Wb9Tu2NA: 'AI Assistant',
27
+ z8ia2kJi2hdqASNBZzRiWQaZ8vshaxgQS67EW: 'DID Spaces Personal',
28
+ z8iZpog7mcgcgBZzTiXJCWESvmnRrQmnd3XBB: 'AI Studio',
29
+ z8iZxVUfZZBPpLhVov5YqsaorNX9F2vKAKeMc: 'Excalidraw',
30
+ z8iZoLRKRXHzqdJ2vFZEi4H5UXT9ADsurxZRK: 'Tweet Token',
31
+ z8ia5gwZog5Ut4TfUJP4k82fXKQN8iWZp2bfG: 'Token Prize Pool',
32
+ z8iZorY6mvb5tZrxXTqhBmwu89xjEEazrgT3t: 'Meilisearch',
33
+ z8iZy4P83i6AgnNdNUexsh2kBcsDHoqcwPavn: 'DID Pay',
34
+ z8iZqTiD6tFwEub6t685e3dj18Ekbo8xvqBSV: 'Vote',
35
+ z8iZkFBbrVQxZHvcWWB3Sa2TrfGmSeFz9MSU7: 'Server Launcher',
36
+ z8iZhW61syFGfgMGDm7ttbDATUf4zbNrzxfJG: 'Blocklet Launcher',
37
+ z8iZqxnmW2i3AbgmjuFki1J6KE8e5i5zBWB9k: 'Nostr Verifier',
38
+ z8iZwyBfqwNcGbLCiUnFAQLEzT8sJd2TSjbM2: 'Static Demo',
39
+ z8iZva6oERHPw7qveUwTBKcY8DqUUtcXheBX8: 'Form Builder',
40
+ z8iZrdP3XNxaqzcHqTRewE3BdJiCfeMfNLzTc: 'AD Kit',
41
+ z8ia2XJkmoZDwRBYzrvLqeZAHWz38Ptrz51xf: 'Tweet Assistant',
42
+ z8ia2YJVK83HuwqykTVVe61mtNWEWeR6kVERi: 'aistro',
43
+ z8ia5nxBkFetpK1BzaumvDStQiKyAuHdymnoh: 'Wait Genie',
44
+ };
45
+
46
+ module.exports = async ({ states, printInfo }) => {
47
+ printInfo('Try to update component title...');
48
+
49
+ const apps = await states.blocklet.find({});
50
+
51
+ for (const app of apps || []) {
52
+ let shouldUpdate = false;
53
+ for (const component of app.children || []) {
54
+ const title = blocklets[component?.meta?.bundleDid];
55
+ if (component?.meta && title && title !== component.meta.title) {
56
+ component.meta.title = title;
57
+ shouldUpdate = true;
58
+ }
59
+ }
60
+
61
+ if (shouldUpdate) {
62
+ await states.blocklet.update({ _id: app._id }, { $set: { children: app.children } });
63
+ printInfo(`Blocklet in blocklet.db updated: ${app.meta?.title}`);
64
+ }
65
+ }
66
+ };
@@ -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-0c0c5eb2",
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-0c0c5eb2",
23
+ "@abtnode/certificate-manager": "1.16.8-beta-0c0c5eb2",
24
+ "@abtnode/constant": "1.16.8-beta-0c0c5eb2",
25
+ "@abtnode/cron": "1.16.8-beta-0c0c5eb2",
26
+ "@abtnode/db": "1.16.8-beta-0c0c5eb2",
27
+ "@abtnode/logger": "1.16.8-beta-0c0c5eb2",
28
+ "@abtnode/queue": "1.16.8-beta-0c0c5eb2",
29
+ "@abtnode/rbac": "1.16.8-beta-0c0c5eb2",
30
+ "@abtnode/router-provider": "1.16.8-beta-0c0c5eb2",
31
+ "@abtnode/static-server": "1.16.8-beta-0c0c5eb2",
32
+ "@abtnode/timemachine": "1.16.8-beta-0c0c5eb2",
33
+ "@abtnode/util": "1.16.8-beta-0c0c5eb2",
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-0c0c5eb2",
45
+ "@blocklet/meta": "1.16.8-beta-0c0c5eb2",
46
+ "@blocklet/sdk": "1.16.8-beta-0c0c5eb2",
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": "0d8ec3154caf670770284dbdabc61a5f4e8b205f"
97
98
  }