@abtnode/core 1.16.7-beta-1e0b7b89 → 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.
- package/lib/blocklet/manager/disk.js +299 -56
- package/lib/blocklet/storage/backup/spaces.js +25 -9
- package/lib/event.js +4 -10
- package/lib/index.js +32 -2
- package/lib/locales/en.js +9 -0
- package/lib/locales/index.js +7 -0
- package/lib/locales/zh.js +8 -0
- package/lib/monitor/blocklet-runtime-monitor.js +1 -1
- package/lib/router/helper.js +15 -1
- package/lib/states/audit-log.js +7 -2
- package/lib/states/backup.js +177 -0
- package/lib/states/blocklet-extras.js +6 -0
- package/lib/states/index.js +3 -0
- package/lib/states/user.js +1 -2
- package/lib/util/blocklet.js +12 -1
- package/lib/util/queue.js +19 -0
- package/lib/util/spaces.js +39 -0
- package/lib/validators/backup.js +24 -0
- package/lib/validators/space-gateway.js +30 -0
- package/package.json +18 -17
|
@@ -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
|
-
*
|
|
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
|
|
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.
|
|
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 {
|
|
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
|
|
2649
|
-
const
|
|
2650
|
-
|
|
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
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
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
|
-
|
|
2665
|
-
this.
|
|
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.
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
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
|
-
|
|
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.
|
|
139
|
-
(e) => e.key === BLOCKLET_CONFIGURABLE_KEY.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
267
|
-
|
|
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
|
-
|
|
121
|
+
// 备份自带重试机制
|
|
122
|
+
maxRetries: 0,
|
|
122
123
|
retryDelay: 10000, // retry after 10 seconds
|
|
123
|
-
maxTimeout: 60 * 1000 *
|
|
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
|
});
|
package/lib/locales/index.js
CHANGED
|
@@ -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
|
-
|
|
94
|
+
this.data[blockletDid][key].runtimeInfo = {};
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
return;
|
package/lib/router/helper.js
CHANGED
|
@@ -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.
|
|
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);
|
package/lib/states/audit-log.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/lib/states/index.js
CHANGED
|
@@ -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
|
|
package/lib/states/user.js
CHANGED
|
@@ -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
|
|
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();
|
package/lib/util/blocklet.js
CHANGED
|
@@ -543,7 +543,8 @@ const startBlockletProcess = async (
|
|
|
543
543
|
namespace: 'blocklets',
|
|
544
544
|
name: processId,
|
|
545
545
|
cwd: appCwd,
|
|
546
|
-
|
|
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.
|
|
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.
|
|
23
|
-
"@abtnode/certificate-manager": "1.16.
|
|
24
|
-
"@abtnode/constant": "1.16.
|
|
25
|
-
"@abtnode/cron": "1.16.
|
|
26
|
-
"@abtnode/db": "1.16.
|
|
27
|
-
"@abtnode/logger": "1.16.
|
|
28
|
-
"@abtnode/queue": "1.16.
|
|
29
|
-
"@abtnode/rbac": "1.16.
|
|
30
|
-
"@abtnode/router-provider": "1.16.
|
|
31
|
-
"@abtnode/static-server": "1.16.
|
|
32
|
-
"@abtnode/timemachine": "1.16.
|
|
33
|
-
"@abtnode/util": "1.16.
|
|
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.
|
|
44
|
-
"@blocklet/meta": "1.16.
|
|
45
|
-
"@blocklet/sdk": "1.16.
|
|
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": "
|
|
97
|
+
"gitHead": "d0f724c8082572a01b6e9287df6c3d0663ec9c57"
|
|
97
98
|
}
|