@abtnode/core 1.8.63 → 1.8.64-beta-0b5ede51
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/api/team.js +48 -7
- package/lib/blocklet/manager/disk.js +40 -5
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +63 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +105 -0
- package/lib/blocklet/storage/backup/blocklet.js +53 -0
- package/lib/blocklet/storage/backup/blocklets.js +80 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +23 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +189 -0
- package/lib/blocklet/storage/restore/base.js +55 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +84 -0
- package/lib/blocklet/storage/restore/blocklets.js +24 -0
- package/lib/blocklet/storage/restore/spaces.js +137 -0
- package/lib/event.js +5 -1
- package/lib/index.js +4 -1
- package/lib/router/helper.js +65 -34
- package/lib/states/blocklet-extras.js +4 -0
- package/lib/states/blocklet.js +0 -22
- package/lib/util/blocklet.js +23 -2
- package/lib/validators/util.js +1 -0
- package/package.json +30 -27
- package/lib/validators/blocklet.js +0 -5
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* did?: string
|
|
4
|
+
* }} SpaceBackupInput
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { isValid } = require('@arcblock/did');
|
|
8
|
+
const { BLOCKLET_CONFIGURABLE_KEY } = require('@blocklet/constant');
|
|
9
|
+
const { getBlockletInfo } = require('@blocklet/meta');
|
|
10
|
+
const { SpaceClient, SyncFolderPushCommand } = require('@did-space/client');
|
|
11
|
+
const { ensureDirSync, existsSync, rmdirSync, removeSync } = require('fs-extra');
|
|
12
|
+
const { isEmpty } = require('lodash');
|
|
13
|
+
const { join } = require('path');
|
|
14
|
+
const states = require('../../../states');
|
|
15
|
+
const { AuditLogBackup } = require('./audit-log');
|
|
16
|
+
const { BlockletBackup } = require('./blocklet');
|
|
17
|
+
const { BlockletExtrasBackup } = require('./blocklet-extras');
|
|
18
|
+
const { BlockletsBackup } = require('./blocklets');
|
|
19
|
+
const { DataBackup } = require('./data');
|
|
20
|
+
const { LogsBackup } = require('./logs');
|
|
21
|
+
const { RoutingRuleBackup } = require('./routing-rule');
|
|
22
|
+
|
|
23
|
+
class SpacesBackup {
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @type {SpaceBackupInput}
|
|
27
|
+
* @memberof SpacesBackup
|
|
28
|
+
*/
|
|
29
|
+
input;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @description blocklet state 对象
|
|
33
|
+
* @type {import('@abtnode/client').BlockletState}
|
|
34
|
+
* @memberof SpacesBackup
|
|
35
|
+
*/
|
|
36
|
+
blocklet;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @description 当前 blocklet 的数据目录
|
|
40
|
+
* @type {string}
|
|
41
|
+
* @memberof SpacesBackup
|
|
42
|
+
*/
|
|
43
|
+
blockletBackupDir;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @description server 的数据目录
|
|
48
|
+
* @type {string}
|
|
49
|
+
* @memberof SpacesBackup
|
|
50
|
+
*/
|
|
51
|
+
serverDataDir;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
*
|
|
55
|
+
* @description spaces 的 endpoint
|
|
56
|
+
* @type {string}
|
|
57
|
+
* @memberof SpacesBackup
|
|
58
|
+
*/
|
|
59
|
+
spacesEndpoint;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @description spaces 的 endpoint
|
|
64
|
+
* @type {import('@ocap/wallet').WalletObject}
|
|
65
|
+
* @memberof SpacesBackup
|
|
66
|
+
*/
|
|
67
|
+
blockletWallet;
|
|
68
|
+
|
|
69
|
+
storages;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {SpaceBackupInput} input
|
|
74
|
+
* @memberof SpacesBackup
|
|
75
|
+
*/
|
|
76
|
+
constructor(input) {
|
|
77
|
+
this.verify(input);
|
|
78
|
+
this.input = input;
|
|
79
|
+
this.storages = [
|
|
80
|
+
new AuditLogBackup(this.input),
|
|
81
|
+
new LogsBackup(this.input),
|
|
82
|
+
new BlockletBackup(this.input),
|
|
83
|
+
new BlockletsBackup(this.input),
|
|
84
|
+
new BlockletExtrasBackup(this.input),
|
|
85
|
+
new RoutingRuleBackup(this.input),
|
|
86
|
+
new DataBackup(this.input),
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {SpaceBackupInput} input
|
|
92
|
+
* @returns {void}
|
|
93
|
+
* @memberof SpacesBackup
|
|
94
|
+
*/
|
|
95
|
+
verify(input) {
|
|
96
|
+
if (isEmpty(input?.did) || !isValid(input?.did)) {
|
|
97
|
+
throw new Error(`input.did(${input?.did}) is not a valid did`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
* @memberof SpacesBackup
|
|
105
|
+
*/
|
|
106
|
+
async backup() {
|
|
107
|
+
await this.initialize();
|
|
108
|
+
await this.export();
|
|
109
|
+
await this.syncToSpaces();
|
|
110
|
+
await this.destroy();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async initialize() {
|
|
114
|
+
this.blocklet = await states.blocklet.findOne({
|
|
115
|
+
appDid: this.input.did,
|
|
116
|
+
});
|
|
117
|
+
if (isEmpty(this.blocklet)) {
|
|
118
|
+
throw new Error('blocklet cannot be empty');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
|
|
122
|
+
|
|
123
|
+
this.blockletBackupDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/backup', this.blocklet.meta.did);
|
|
124
|
+
if (existsSync(this.blockletBackupDir)) {
|
|
125
|
+
rmdirSync(this.blockletBackupDir, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
ensureDirSync(this.blockletBackupDir);
|
|
128
|
+
|
|
129
|
+
this.spacesEndpoint = this.blocklet.environments.find(
|
|
130
|
+
(e) => e.key === BLOCKLET_CONFIGURABLE_KEY.BLOCKLET_APP_SPACE_ENDPOINT
|
|
131
|
+
)?.value;
|
|
132
|
+
if (isEmpty(this.spacesEndpoint)) {
|
|
133
|
+
throw new Error('spacesEndpoint cannot be empty');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.blockletWallet = await this.getBlockletWallet();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async export() {
|
|
140
|
+
await Promise.all(
|
|
141
|
+
this.storages.map((storage) => {
|
|
142
|
+
storage.ensureParams(this);
|
|
143
|
+
return storage.export();
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async syncToSpaces() {
|
|
149
|
+
const wallet = await this.getBlockletWallet();
|
|
150
|
+
const spaceClient = new SpaceClient({
|
|
151
|
+
endpoint: this.spacesEndpoint,
|
|
152
|
+
wallet,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// FIXME: 在 Spaces 里面预览出 blocklet 的样式,需要规划一个好的数据结构
|
|
156
|
+
const { errorCount } = await spaceClient.send(
|
|
157
|
+
new SyncFolderPushCommand({
|
|
158
|
+
source: join(this.blockletBackupDir, '/'),
|
|
159
|
+
target: join('.did-objects', this.blocklet.appDid),
|
|
160
|
+
debug: true,
|
|
161
|
+
retryCount: 3,
|
|
162
|
+
filter: (object) => {
|
|
163
|
+
return object.name !== '.DS_Store';
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (errorCount !== 0) {
|
|
169
|
+
throw new Error(`Sync to spaces encountered ${errorCount} error`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async getBlockletWallet() {
|
|
174
|
+
const blockletInfo = await states.blocklet.getBlocklet(this.input.did);
|
|
175
|
+
const nodeInfo = await states.node.read();
|
|
176
|
+
const { wallet } = getBlockletInfo(blockletInfo, nodeInfo.sk);
|
|
177
|
+
return wallet;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async destroy() {
|
|
181
|
+
if (existsSync(this.blockletBackupDir)) {
|
|
182
|
+
removeSync(this.blockletBackupDir);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
SpacesBackup,
|
|
189
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class BaseRestore {
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @type {import('./spaces').SpaceRestoreInput}
|
|
5
|
+
* @memberof BaseRestore
|
|
6
|
+
*/
|
|
7
|
+
input;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @description 当前 blocklet 的数据目录
|
|
11
|
+
* @type {string}
|
|
12
|
+
* @memberof BaseRestore
|
|
13
|
+
*/
|
|
14
|
+
blockletRestoreDir;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @description spaces 的 endpoint
|
|
19
|
+
* @type {import('@ocap/wallet').WalletObject}
|
|
20
|
+
* @memberof BaseRestore
|
|
21
|
+
*/
|
|
22
|
+
blockletWallet;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @description server 的数据目录
|
|
27
|
+
* @type {string}
|
|
28
|
+
* @memberof BaseRestore
|
|
29
|
+
*/
|
|
30
|
+
serverDataDir;
|
|
31
|
+
|
|
32
|
+
constructor(input) {
|
|
33
|
+
this.input = input;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
*
|
|
39
|
+
* @param {import('./spaces').SpacesRestore} spaces
|
|
40
|
+
* @memberof BaseRestore
|
|
41
|
+
*/
|
|
42
|
+
ensureParams(spaces) {
|
|
43
|
+
this.blockletRestoreDir = spaces.blockletRestoreDir;
|
|
44
|
+
this.blockletWallet = spaces.blockletWallet;
|
|
45
|
+
this.serverDataDir = spaces.serverDataDir;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async import() {
|
|
49
|
+
throw new Error('not implemented');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
BaseRestore,
|
|
55
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const { removeSync, outputJsonSync, readJSONSync } = require('fs-extra');
|
|
2
|
+
const { cloneDeep } = require('lodash');
|
|
3
|
+
const { join } = require('path');
|
|
4
|
+
const security = require('@abtnode/util/lib/security');
|
|
5
|
+
const { BaseRestore } = require('./base');
|
|
6
|
+
|
|
7
|
+
class BlockletExtrasRestore extends BaseRestore {
|
|
8
|
+
filename = 'blocklet-extras.json';
|
|
9
|
+
|
|
10
|
+
async import() {
|
|
11
|
+
const blockletExtra = await this.getBlockletExtra();
|
|
12
|
+
removeSync(join(this.blockletRestoreDir, this.filename));
|
|
13
|
+
outputJsonSync(join(this.blockletRestoreDir, this.filename), blockletExtra);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* @description
|
|
19
|
+
* @return {Promise<import('@abtnode/client').BlockletState>}
|
|
20
|
+
* @memberof BlockletExtrasRestore
|
|
21
|
+
*/
|
|
22
|
+
async getBlockletExtra() {
|
|
23
|
+
/**
|
|
24
|
+
* @type {import('@abtnode/client').BlockletState}
|
|
25
|
+
*/
|
|
26
|
+
const blockletExtra = readJSONSync(join(this.blockletRestoreDir, this.filename));
|
|
27
|
+
|
|
28
|
+
return this.cleanData(blockletExtra);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @description 清理数据并加密
|
|
34
|
+
* @param {import('@abtnode/client').BlockletState} blockletExtraInput
|
|
35
|
+
* @return {Promise<void>}
|
|
36
|
+
* @memberof BlockletExtrasRestore
|
|
37
|
+
*/
|
|
38
|
+
async cleanData(blockletExtraInput) {
|
|
39
|
+
const blockletExtra = cloneDeep(blockletExtraInput);
|
|
40
|
+
|
|
41
|
+
const queue = [blockletExtra];
|
|
42
|
+
while (queue.length) {
|
|
43
|
+
const currentBlockletExtra = queue.pop();
|
|
44
|
+
|
|
45
|
+
// 加解密
|
|
46
|
+
this.useBlockletDecryptConfigs(currentBlockletExtra.configs);
|
|
47
|
+
|
|
48
|
+
if (currentBlockletExtra?.children) {
|
|
49
|
+
queue.push(...currentBlockletExtra.children);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return blockletExtra;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @description 清理数据并加密
|
|
59
|
+
* @param {import('@abtnode/client').ConfigEntry[]} configs
|
|
60
|
+
* @return {void}
|
|
61
|
+
* @memberof BlockletExtrasRestore
|
|
62
|
+
*/
|
|
63
|
+
useBlockletDecryptConfigs(configs) {
|
|
64
|
+
for (const config of configs) {
|
|
65
|
+
// secure 为 true 的配置才需要被加密保存上次到 did spaces
|
|
66
|
+
if (config.secure) {
|
|
67
|
+
const encryptByBlocklet = config.value;
|
|
68
|
+
|
|
69
|
+
// 再用 blocklet secret 加密,然后才可以上传到 spaces
|
|
70
|
+
const decryptByBlocklet = security.decrypt(
|
|
71
|
+
encryptByBlocklet,
|
|
72
|
+
this.blockletWallet.address,
|
|
73
|
+
Buffer.from(this.blockletWallet.secretKey)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
config.value = decryptByBlocklet;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return configs;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { BlockletExtrasRestore };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { existsSync, removeSync } = require('fs-extra');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const StreamZip = require('node-stream-zip');
|
|
4
|
+
const { BaseRestore } = require('./base');
|
|
5
|
+
|
|
6
|
+
class BlockletsRestore extends BaseRestore {
|
|
7
|
+
filename = 'blocklets.zip';
|
|
8
|
+
|
|
9
|
+
async import() {
|
|
10
|
+
const blockletZipPath = join(this.blockletRestoreDir, this.filename);
|
|
11
|
+
|
|
12
|
+
if (!existsSync(blockletZipPath)) {
|
|
13
|
+
throw new Error(`file not found: ${blockletZipPath}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line new-cap
|
|
17
|
+
const zip = new StreamZip.async({ file: blockletZipPath });
|
|
18
|
+
await zip.extract(null, join(this.blockletRestoreDir, 'blocklets'));
|
|
19
|
+
await zip.close();
|
|
20
|
+
removeSync(blockletZipPath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { BlockletsRestore };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* endpoint: string;
|
|
4
|
+
* blockletSecretKey: string;
|
|
5
|
+
* }} SpaceRestoreInput
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { SpaceClient, SyncFolderPullCommand } = require('@did-space/client');
|
|
9
|
+
const { types } = require('@ocap/mcrypto');
|
|
10
|
+
const { fromSecretKey, WalletType } = require('@ocap/wallet');
|
|
11
|
+
const { ensureDirSync, existsSync, rmdirSync } = require('fs-extra');
|
|
12
|
+
const { join } = require('path');
|
|
13
|
+
const validUrl = require('valid-url');
|
|
14
|
+
const { BlockletExtrasRestore } = require('./blocklet-extras');
|
|
15
|
+
const { BlockletsRestore } = require('./blocklets');
|
|
16
|
+
|
|
17
|
+
class SpacesRestore {
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @type {SpaceRestoreInput}
|
|
21
|
+
* @memberof SpacesRestore
|
|
22
|
+
*/
|
|
23
|
+
input;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @description 当前 blocklet 的数据目录
|
|
27
|
+
* @type {string}
|
|
28
|
+
* @memberof SpacesRestore
|
|
29
|
+
*/
|
|
30
|
+
blockletRestoreDir;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @description server 的数据目录
|
|
35
|
+
* @type {string}
|
|
36
|
+
* @memberof SpacesRestore
|
|
37
|
+
*/
|
|
38
|
+
serverDataDir;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @description spaces 的 endpoint
|
|
43
|
+
* @type {import('@ocap/wallet').WalletObject}
|
|
44
|
+
* @memberof SpacesRestore
|
|
45
|
+
*/
|
|
46
|
+
blockletWallet;
|
|
47
|
+
|
|
48
|
+
storages;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param {SpaceRestoreInput} input
|
|
53
|
+
* @memberof SpacesRestore
|
|
54
|
+
*/
|
|
55
|
+
constructor(input) {
|
|
56
|
+
this.verify(input);
|
|
57
|
+
this.input = input;
|
|
58
|
+
this.storages = [new BlockletExtrasRestore(this.input), new BlockletsRestore(this.input)];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @param {SpaceRestoreInput} input
|
|
64
|
+
* @returns {void}
|
|
65
|
+
* @memberof SpacesRestore
|
|
66
|
+
*/
|
|
67
|
+
verify(input) {
|
|
68
|
+
if (!validUrl.isWebUri(input.endpoint)) {
|
|
69
|
+
throw new Error(`endpoint(${input.endpoint}) must be a WebUri`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async initialize() {
|
|
74
|
+
this.serverDataDir = process.env.ABT_NODE_DATA_DIR;
|
|
75
|
+
this.blockletWallet = await this.getBlockletWallet();
|
|
76
|
+
|
|
77
|
+
this.blockletRestoreDir = join(process.env.ABT_NODE_DATA_DIR, 'tmp/restore', this.blockletWallet.address);
|
|
78
|
+
if (existsSync(this.blockletRestoreDir)) {
|
|
79
|
+
rmdirSync(this.blockletRestoreDir, { recursive: true });
|
|
80
|
+
}
|
|
81
|
+
ensureDirSync(this.blockletRestoreDir);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
* @memberof SpacesRestore
|
|
88
|
+
*/
|
|
89
|
+
async restore() {
|
|
90
|
+
await this.initialize();
|
|
91
|
+
await this.syncFromSpaces();
|
|
92
|
+
await this.import();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getBlockletWallet() {
|
|
96
|
+
// @FIXME: blocklet 钱包类型如何得知呢?
|
|
97
|
+
const wallet = fromSecretKey(this.input.blockletSecretKey, WalletType({ role: types.RoleType.ROLE_APPLICATION }));
|
|
98
|
+
|
|
99
|
+
return wallet;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async syncFromSpaces() {
|
|
103
|
+
const { endpoint } = this.input;
|
|
104
|
+
const wallet = await this.getBlockletWallet();
|
|
105
|
+
|
|
106
|
+
const spaceClient = new SpaceClient({
|
|
107
|
+
endpoint,
|
|
108
|
+
wallet,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const { errorCount } = await spaceClient.send(
|
|
112
|
+
new SyncFolderPullCommand({
|
|
113
|
+
source: join('.did-objects', this.blockletWallet.address, '/'),
|
|
114
|
+
target: this.blockletRestoreDir,
|
|
115
|
+
debug: true,
|
|
116
|
+
retryCount: 3,
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (errorCount !== 0) {
|
|
121
|
+
throw new Error(`Sync from spaces encountered ${errorCount} error`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async import() {
|
|
126
|
+
await Promise.all(
|
|
127
|
+
this.storages.map((storage) => {
|
|
128
|
+
storage.ensureParams(this);
|
|
129
|
+
return storage.import();
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
SpacesRestore,
|
|
137
|
+
};
|
package/lib/event.js
CHANGED
|
@@ -238,7 +238,11 @@ module.exports = ({
|
|
|
238
238
|
logger.info('take snapshot after updated blocklet app', { event: eventName, did: blocklet.meta.did, hash });
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
if (
|
|
241
|
+
if (
|
|
242
|
+
![BlockletEvents.removed, BlockletEvents.dataCleaned].includes(eventName) &&
|
|
243
|
+
blocklet.status &&
|
|
244
|
+
!isBeforeInstalled(blocklet.status)
|
|
245
|
+
) {
|
|
242
246
|
try {
|
|
243
247
|
await blockletManager.runtimeMonitor.monit(blocklet.meta.did);
|
|
244
248
|
} catch (error) {
|
package/lib/index.js
CHANGED
|
@@ -216,6 +216,8 @@ function ABTNode(options) {
|
|
|
216
216
|
updateWhoCanAccess: blockletManager.updateWhoCanAccess.bind(blockletManager),
|
|
217
217
|
updateComponentTitle: blockletManager.updateComponentTitle.bind(blockletManager),
|
|
218
218
|
updateComponentMountPoint: blockletManager.updateComponentMountPoint.bind(blockletManager),
|
|
219
|
+
backupToSpaces: blockletManager.backupToSpaces.bind(blockletManager),
|
|
220
|
+
restoreFromSpaces: blockletManager.restoreFromSpaces.bind(blockletManager),
|
|
219
221
|
|
|
220
222
|
// For diagnose purpose
|
|
221
223
|
syncBlockletStatus: blockletManager.status.bind(blockletManager),
|
|
@@ -259,8 +261,9 @@ function ABTNode(options) {
|
|
|
259
261
|
createTransferInvitation: teamAPI.createTransferInvitation.bind(teamAPI),
|
|
260
262
|
getInvitation: teamAPI.getInvitation.bind(teamAPI),
|
|
261
263
|
getInvitations: teamAPI.getInvitations.bind(teamAPI),
|
|
262
|
-
|
|
264
|
+
checkInvitation: teamAPI.checkInvitation.bind(teamAPI),
|
|
263
265
|
deleteInvitation: teamAPI.deleteInvitation.bind(teamAPI),
|
|
266
|
+
closeInvitation: teamAPI.closeInvitation.bind(teamAPI),
|
|
264
267
|
|
|
265
268
|
// Account
|
|
266
269
|
getUsers: teamAPI.getUsers.bind(teamAPI),
|
package/lib/router/helper.js
CHANGED
|
@@ -14,6 +14,7 @@ const getTmpDir = require('@abtnode/util/lib/get-tmp-directory');
|
|
|
14
14
|
const downloadFile = require('@abtnode/util/lib/download-file');
|
|
15
15
|
const { updateBlockletDocument } = require('@abtnode/util/lib/did-document');
|
|
16
16
|
const getBlockletInfo = require('@blocklet/meta/lib/info');
|
|
17
|
+
const { forEachBlocklet } = require('@blocklet/meta/lib/util');
|
|
17
18
|
const {
|
|
18
19
|
DOMAIN_FOR_DEFAULT_SITE,
|
|
19
20
|
DOMAIN_FOR_IP_SITE_REGEXP,
|
|
@@ -768,47 +769,77 @@ module.exports = function getRouterHelpers({ dataDirs, routingSnapshot, routerMa
|
|
|
768
769
|
return false;
|
|
769
770
|
}
|
|
770
771
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
772
|
+
/**
|
|
773
|
+
* 1. component blocklet 不允许有相同多的 wellknown
|
|
774
|
+
* 1. wellknown 可以访问的路由:
|
|
775
|
+
* /.well-known/xxx
|
|
776
|
+
* /{wellknown owner blocklet}/.well-known/xxx
|
|
777
|
+
*/
|
|
778
778
|
|
|
779
|
-
|
|
780
|
-
const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
|
|
779
|
+
const isWellknownInterface = (x) => x.type === BLOCKLET_INTERFACE_TYPE_WELLKNOWN;
|
|
781
780
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
788
|
-
interfaceName: x.name,
|
|
789
|
-
},
|
|
790
|
-
isProtected: true,
|
|
791
|
-
};
|
|
781
|
+
const handler = async (tmpBlocklet, tmpInterface, mountPoint) => {
|
|
782
|
+
let pathPrefix = normalizePathPrefix(tmpInterface.prefix);
|
|
783
|
+
if (!pathPrefix.startsWith(WELLKNOWN_PATH_PREFIX)) {
|
|
784
|
+
throw new Error(`Wellknown path prefix must start with: ${WELLKNOWN_PATH_PREFIX}`);
|
|
785
|
+
}
|
|
792
786
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
787
|
+
const tmpMountPoint = mountPoint || tmpBlocklet.mountPoint;
|
|
788
|
+
if (tmpMountPoint) {
|
|
789
|
+
pathPrefix = joinUrl(tmpMountPoint, pathPrefix);
|
|
790
|
+
}
|
|
797
791
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
existedRule.to = rule.to;
|
|
792
|
+
const port = findInterfacePortByName(tmpBlocklet, tmpInterface.name);
|
|
793
|
+
const existedRule = hasRuleByPrefix(wellknownSite, pathPrefix);
|
|
801
794
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
795
|
+
const rule = {
|
|
796
|
+
from: { pathPrefix },
|
|
797
|
+
to: {
|
|
798
|
+
did: tmpBlocklet.meta.did,
|
|
799
|
+
port,
|
|
800
|
+
type: ROUTING_RULE_TYPES.GENERAL_PROXY,
|
|
801
|
+
interfaceName: tmpInterface.name,
|
|
802
|
+
},
|
|
803
|
+
isProtected: true,
|
|
804
|
+
};
|
|
806
805
|
|
|
807
|
-
|
|
808
|
-
}
|
|
806
|
+
if (!existedRule) {
|
|
807
|
+
await routerManager.addRoutingRule({ id: wellknownSite.id, rule, skipCheckDynamicBlacklist: true }, context);
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
809
810
|
|
|
810
|
-
|
|
811
|
-
|
|
811
|
+
// 兼容代码,旧的 rule 没有 to.did 字段
|
|
812
|
+
if (port !== existedRule.to.port || existedRule.to.did !== tmpBlocklet.meta.did) {
|
|
813
|
+
existedRule.to = rule.to;
|
|
814
|
+
|
|
815
|
+
await routerManager.updateRoutingRule(
|
|
816
|
+
{ id: wellknownSite.id, rule: existedRule, skipProtectedRuleChecking: true },
|
|
817
|
+
context
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return false;
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const tasks = [];
|
|
827
|
+
|
|
828
|
+
forEachBlocklet(
|
|
829
|
+
blocklet,
|
|
830
|
+
(b) => {
|
|
831
|
+
(b.meta.interfaces || []).forEach((item) => {
|
|
832
|
+
if (isWellknownInterface(item)) {
|
|
833
|
+
tasks.push(handler(b, item));
|
|
834
|
+
|
|
835
|
+
if (!b.mountPoint || b.mountPoint !== '/') {
|
|
836
|
+
tasks.push(handler(b, item, '/')); // 在站点的根路由下挂载一个
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
},
|
|
841
|
+
{ sync: true }
|
|
842
|
+
);
|
|
812
843
|
|
|
813
844
|
const changes = await Promise.all(tasks);
|
|
814
845
|
return changes.some(Boolean);
|
|
@@ -199,6 +199,10 @@ class BlockletExtrasState extends BaseState {
|
|
|
199
199
|
return super.update({ did }, { $set: entity }, { upsert: true });
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
async getMeta(did) {
|
|
203
|
+
return super.findOne({ did }, { did: 1, controller: 1, meta: 1 });
|
|
204
|
+
}
|
|
205
|
+
|
|
202
206
|
async updateExpireInfo({ did, expiredAt } = {}) {
|
|
203
207
|
const entity = { did, expiredAt };
|
|
204
208
|
await validateExpiredInfo(entity);
|