@abtnode/core 1.15.17 → 1.16.0-beta-8ee536d7
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/node.js +67 -69
- package/lib/api/team.js +386 -55
- package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
- package/lib/blocklet/downloader/bundle-downloader.js +272 -0
- package/lib/blocklet/downloader/constants.js +3 -0
- package/lib/blocklet/downloader/resolve-download.js +199 -0
- package/lib/blocklet/extras.js +83 -26
- package/lib/blocklet/hooks.js +18 -65
- package/lib/blocklet/manager/base.js +10 -16
- package/lib/blocklet/manager/disk.js +1680 -1566
- package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
- package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
- package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
- package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
- package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
- package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
- package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
- package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
- package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
- package/lib/blocklet/migration.js +30 -52
- package/lib/blocklet/storage/backup/audit-log.js +27 -0
- package/lib/blocklet/storage/backup/base.js +62 -0
- package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
- package/lib/blocklet/storage/backup/blocklet.js +70 -0
- package/lib/blocklet/storage/backup/blocklets.js +74 -0
- package/lib/blocklet/storage/backup/data.js +19 -0
- package/lib/blocklet/storage/backup/logs.js +24 -0
- package/lib/blocklet/storage/backup/routing-rule.js +19 -0
- package/lib/blocklet/storage/backup/spaces.js +240 -0
- package/lib/blocklet/storage/restore/base.js +67 -0
- package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
- package/lib/blocklet/storage/restore/blocklet.js +56 -0
- package/lib/blocklet/storage/restore/blocklets.js +43 -0
- package/lib/blocklet/storage/restore/logs.js +21 -0
- package/lib/blocklet/storage/restore/spaces.js +156 -0
- package/lib/blocklet/storage/utils/hash.js +51 -0
- package/lib/blocklet/storage/utils/zip.js +43 -0
- package/lib/cert.js +206 -0
- package/lib/event.js +237 -64
- package/lib/index.js +191 -83
- package/lib/migrations/1.0.21-update-config.js +1 -1
- package/lib/migrations/1.0.22-max-memory.js +1 -1
- package/lib/migrations/1.0.25.js +1 -1
- package/lib/migrations/1.0.32-update-config.js +1 -1
- package/lib/migrations/1.0.33-blocklets.js +1 -1
- package/lib/migrations/1.5.20-registry.js +15 -0
- package/lib/migrations/1.6.17-blocklet-children.js +48 -0
- package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
- package/lib/migrations/1.6.4-security.js +59 -0
- package/lib/migrations/1.6.5-security.js +60 -0
- package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
- package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
- package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
- package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
- package/lib/migrations/1.7.20-blocklet-component.js +41 -0
- package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
- package/lib/migrations/README.md +1 -1
- package/lib/migrations/index.js +6 -2
- package/lib/monitor/blocklet-runtime-monitor.js +200 -0
- package/lib/monitor/get-history-list.js +37 -0
- package/lib/monitor/node-runtime-monitor.js +228 -0
- package/lib/router/helper.js +576 -500
- package/lib/router/index.js +85 -21
- package/lib/router/manager.js +146 -187
- package/lib/states/README.md +36 -1
- package/lib/states/access-key.js +39 -17
- package/lib/states/audit-log.js +462 -0
- package/lib/states/base.js +4 -213
- package/lib/states/blocklet-extras.js +195 -138
- package/lib/states/blocklet.js +371 -110
- package/lib/states/cache.js +8 -6
- package/lib/states/challenge.js +5 -5
- package/lib/states/index.js +19 -36
- package/lib/states/migration.js +4 -4
- package/lib/states/node.js +135 -46
- package/lib/states/notification.js +22 -35
- package/lib/states/session.js +17 -9
- package/lib/states/site.js +50 -25
- package/lib/states/user.js +74 -20
- package/lib/states/webhook.js +10 -6
- package/lib/team/manager.js +124 -7
- package/lib/util/blocklet.js +1223 -246
- package/lib/util/chain.js +1 -1
- package/lib/util/default-node-config.js +5 -23
- package/lib/util/disk-monitor.js +13 -10
- package/lib/util/domain-status.js +84 -15
- package/lib/util/get-accessible-external-node-ip.js +2 -2
- package/lib/util/get-domain-for-blocklet.js +13 -0
- package/lib/util/get-meta-from-url.js +33 -0
- package/lib/util/index.js +207 -272
- package/lib/util/ip.js +6 -0
- package/lib/util/maintain.js +233 -0
- package/lib/util/public-to-store.js +85 -0
- package/lib/util/ready.js +1 -1
- package/lib/util/requirement.js +28 -9
- package/lib/util/reset-node.js +22 -7
- package/lib/util/router.js +13 -0
- package/lib/util/rpc.js +16 -0
- package/lib/util/store.js +179 -0
- package/lib/util/sysinfo.js +44 -0
- package/lib/util/ua.js +54 -0
- package/lib/validators/blocklet-extra.js +24 -0
- package/lib/validators/node.js +25 -12
- package/lib/validators/permission.js +16 -1
- package/lib/validators/role.js +17 -3
- package/lib/validators/router.js +40 -20
- package/lib/validators/trusted-passport.js +1 -0
- package/lib/validators/util.js +22 -5
- package/lib/webhook/index.js +45 -35
- package/lib/webhook/sender/index.js +5 -0
- package/lib/webhook/sender/slack/index.js +1 -1
- package/lib/webhook/sender/wallet/index.js +48 -0
- package/package.json +54 -36
- package/lib/blocklet/registry.js +0 -205
- package/lib/states/https-cert.js +0 -67
- package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
- package/lib/util/service.js +0 -66
- package/lib/util/upgrade.js +0 -178
- /package/lib/{queue.js → util/queue.js} +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
const { EventEmitter } = require('events');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const get = require('lodash/get');
|
|
5
|
+
const { toBase58 } = require('@ocap/util');
|
|
6
|
+
|
|
7
|
+
const defaultLogger = require('@abtnode/logger')('@abtnode/core:blocklet-downloader');
|
|
8
|
+
|
|
9
|
+
const { forEachBlockletSync, getComponentBundleId } = require('@blocklet/meta/lib/util');
|
|
10
|
+
const validateBlockletEntry = require('@blocklet/meta/lib/entry');
|
|
11
|
+
|
|
12
|
+
const { BlockletSource, BLOCKLET_META_FILE, BLOCKLET_META_FILE_ALT, BLOCKLET_MODES } = require('@blocklet/constant');
|
|
13
|
+
|
|
14
|
+
const { getBundleDir } = require('../../util/blocklet');
|
|
15
|
+
const BundleDownloader = require('./bundle-downloader');
|
|
16
|
+
const { CACHE_KEY } = require('./constants');
|
|
17
|
+
|
|
18
|
+
const isSourceAccessible = (blocklet) =>
|
|
19
|
+
![BlockletSource.upload, BlockletSource.local, BlockletSource.custom].includes(blocklet.source);
|
|
20
|
+
|
|
21
|
+
const isMetaFileExist = (dir) => {
|
|
22
|
+
const blockletMetaFile = path.join(dir, BLOCKLET_META_FILE);
|
|
23
|
+
const blockletMetaFileAlt = path.join(dir, BLOCKLET_META_FILE_ALT);
|
|
24
|
+
|
|
25
|
+
return fs.existsSync(blockletMetaFile) || fs.existsSync(blockletMetaFileAlt);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {blocklet} component
|
|
30
|
+
* @param {{
|
|
31
|
+
* cachedBundles: Array<{bundleId, integrity}>;
|
|
32
|
+
* }}
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
const needDownload = (
|
|
36
|
+
component,
|
|
37
|
+
{ installDir, logger = defaultLogger, cachedBundles = [], skipCheckIntegrity } = {}
|
|
38
|
+
) => {
|
|
39
|
+
if (component.mode === BLOCKLET_MODES.DEVELOPMENT) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (component.source === BlockletSource.local) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const bundleId = getComponentBundleId(component);
|
|
48
|
+
const bundleDir = getBundleDir(installDir, component.meta);
|
|
49
|
+
|
|
50
|
+
// check bundle exist
|
|
51
|
+
|
|
52
|
+
if (!isMetaFileExist(bundleDir)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// check bundle broken
|
|
57
|
+
try {
|
|
58
|
+
validateBlockletEntry(bundleDir, component.meta);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
logger.error('bundle is broken', { bundleId, msg: err.message });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!isSourceAccessible(component)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (skipCheckIntegrity) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// check integrity
|
|
73
|
+
|
|
74
|
+
const cachedBundle = cachedBundles.find((x) => x.bundleId === bundleId);
|
|
75
|
+
|
|
76
|
+
// FIXME: 不是新安装的组件不应该 check, 不应该 block 安装成功 https://github.com/blocklet/launcher/actions/runs/4184577090/jobs/7250416272
|
|
77
|
+
if (!cachedBundle) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const integrity = get(component, 'meta.dist.integrity');
|
|
82
|
+
if (toBase58(integrity) === cachedBundle.integrity) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
logger.error(`find duplicate bundle with different integrity when downloading ${component.meta.title}`, {
|
|
87
|
+
bundleId,
|
|
88
|
+
});
|
|
89
|
+
return true;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
class BlockletDownloader extends EventEmitter {
|
|
93
|
+
/**
|
|
94
|
+
* @param {{
|
|
95
|
+
* installDir: string,
|
|
96
|
+
* downloadDir: string,
|
|
97
|
+
* cache: {
|
|
98
|
+
* set: (key: string, value: any) => Promise
|
|
99
|
+
* get: (key: string) => Promise<any>
|
|
100
|
+
* },
|
|
101
|
+
* }}
|
|
102
|
+
*/
|
|
103
|
+
constructor({ installDir, downloadDir, cache, logger = defaultLogger }) {
|
|
104
|
+
super();
|
|
105
|
+
|
|
106
|
+
this.installDir = installDir;
|
|
107
|
+
this.cache = cache;
|
|
108
|
+
this.logger = logger;
|
|
109
|
+
|
|
110
|
+
this.bundleDownloader = new BundleDownloader({
|
|
111
|
+
installDir,
|
|
112
|
+
downloadDir,
|
|
113
|
+
cache,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {{}} blocklet
|
|
119
|
+
* @param {{
|
|
120
|
+
* preDownload: ({ downloadList: Array<meta>, downloadComponentIds: Array<string> }) => any
|
|
121
|
+
* postDownload: ({ downloadList: Array<meta>, downloadComponentIds: Array<string>, isCancelled: boolean }) => any
|
|
122
|
+
* skipCheckIntegrity: boolean
|
|
123
|
+
* }} [options={}]
|
|
124
|
+
* @return {*}
|
|
125
|
+
*/
|
|
126
|
+
async download(blocklet, options = {}) {
|
|
127
|
+
const {
|
|
128
|
+
meta: { name, did },
|
|
129
|
+
} = blocklet;
|
|
130
|
+
|
|
131
|
+
this.logger.info('Download Blocklet', { name, did });
|
|
132
|
+
|
|
133
|
+
const { preDownload = () => {}, postDownload = () => {}, skipCheckIntegrity } = options;
|
|
134
|
+
|
|
135
|
+
const { downloadComponentIds, downloadList } = await this.getDownloadList({
|
|
136
|
+
blocklet,
|
|
137
|
+
skipCheckIntegrity,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await preDownload({ downloadList, downloadComponentIds });
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
this.logger.info('Start Download Blocklet', {
|
|
144
|
+
name,
|
|
145
|
+
did,
|
|
146
|
+
bundles: downloadList.map((x) => get(x, 'dist.tarball')),
|
|
147
|
+
});
|
|
148
|
+
const tasks = [];
|
|
149
|
+
for (const meta of downloadList) {
|
|
150
|
+
const url = meta.dist.tarball;
|
|
151
|
+
tasks.push(this.bundleDownloader.download(meta, did, url, options));
|
|
152
|
+
}
|
|
153
|
+
const results = await Promise.all(tasks);
|
|
154
|
+
|
|
155
|
+
const isCancelled = results.some((x) => x.isCancelled);
|
|
156
|
+
|
|
157
|
+
await postDownload({ downloadList, downloadComponentIds, isCancelled });
|
|
158
|
+
|
|
159
|
+
if (isCancelled) {
|
|
160
|
+
return { isCancelled: true };
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
this.logger.error('Download blocklet failed', { did, name, error });
|
|
164
|
+
await this.bundleDownloader.cancelDownload(blocklet.meta.did);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { isCancelled: false };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async cancelDownload(did) {
|
|
172
|
+
return this.bundleDownloader.cancelDownload(did);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {{
|
|
177
|
+
* blocklet;
|
|
178
|
+
* }}
|
|
179
|
+
* @returns {{
|
|
180
|
+
* downloadList: Array<import('@abtnode/client').BlockletMeta>;
|
|
181
|
+
* downloadComponentIds: Array<string>; // like: "z8ia1i2yq67x39vruqQTbkVcwCnqRGx8zSPsJ/z8iZwubkNdA1BfEUwc5NJpY2Jnfm7yEbyvmKS"
|
|
182
|
+
* }}
|
|
183
|
+
*/
|
|
184
|
+
async getDownloadList({ blocklet, skipCheckIntegrity }) {
|
|
185
|
+
const downloadComponentIds = [];
|
|
186
|
+
const metas = {};
|
|
187
|
+
|
|
188
|
+
const cachedBundles = (await this.cache.get(CACHE_KEY)) || [];
|
|
189
|
+
|
|
190
|
+
forEachBlockletSync(blocklet, (component, { id: componentId }) => {
|
|
191
|
+
const bundleId = getComponentBundleId(component);
|
|
192
|
+
|
|
193
|
+
if (metas[bundleId]) {
|
|
194
|
+
this.logger.info(`skip download duplicate bundle ${bundleId}`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const needComponentDownload = needDownload(component, {
|
|
199
|
+
installDir: this.installDir,
|
|
200
|
+
cachedBundles,
|
|
201
|
+
skipCheckIntegrity,
|
|
202
|
+
});
|
|
203
|
+
if (!needComponentDownload) {
|
|
204
|
+
this.logger.info(`skip download existed bundle ${bundleId}`, { source: component.source });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!isSourceAccessible(component)) {
|
|
209
|
+
// should not throw error
|
|
210
|
+
// broken bundle should not block blocklet installing
|
|
211
|
+
// broken bundle should only block blocklet starting
|
|
212
|
+
this.logger.error(`Component bundle is broken and can not be recovered: ${bundleId}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
metas[bundleId] = component.meta;
|
|
217
|
+
downloadComponentIds.push(componentId);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const downloadList = Object.values(metas);
|
|
221
|
+
|
|
222
|
+
return { downloadList, downloadComponentIds };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = BlockletDownloader;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
const { EventEmitter } = require('events');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const { fileURLToPath } = require('url');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const pick = require('lodash/pick');
|
|
6
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
7
|
+
const { toBase58 } = require('@ocap/util');
|
|
8
|
+
|
|
9
|
+
const defaultLogger = require('@abtnode/logger')('@abtnode/core:bundle-downloader');
|
|
10
|
+
const downloadFile = require('@abtnode/util/lib/download-file');
|
|
11
|
+
const Lock = require('@abtnode/util/lib/lock');
|
|
12
|
+
|
|
13
|
+
const { getComponentBundleId } = require('@blocklet/meta/lib/util');
|
|
14
|
+
|
|
15
|
+
const { verifyIntegrity } = require('../../util/blocklet');
|
|
16
|
+
const { resolveDownload } = require('./resolve-download');
|
|
17
|
+
const { CACHE_KEY } = require('./constants');
|
|
18
|
+
|
|
19
|
+
class BundleDownloader extends EventEmitter {
|
|
20
|
+
/**
|
|
21
|
+
* @param {{
|
|
22
|
+
* cache: {
|
|
23
|
+
* set: (key: string, value: any) => Promise
|
|
24
|
+
* get: (key: string) => Promise<any>
|
|
25
|
+
* }
|
|
26
|
+
* }}
|
|
27
|
+
*/
|
|
28
|
+
constructor({ installDir, downloadDir, cache, logger = defaultLogger }) {
|
|
29
|
+
super();
|
|
30
|
+
|
|
31
|
+
this.installDir = installDir;
|
|
32
|
+
this.downloadDir = downloadDir;
|
|
33
|
+
this.cache = cache;
|
|
34
|
+
this.logger = logger;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* { did: Map({ <childDid>: <downloadFile.cancelCtrl> }) }
|
|
38
|
+
*/
|
|
39
|
+
this.downloadCtrls = {};
|
|
40
|
+
/**
|
|
41
|
+
* { [download-did-version]: Lock }
|
|
42
|
+
*/
|
|
43
|
+
this.downloadLocks = {};
|
|
44
|
+
|
|
45
|
+
this.cacheTarBallLock = new Lock();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
*
|
|
51
|
+
* @param {*} meta
|
|
52
|
+
* @param {*} rootDid
|
|
53
|
+
* @param {*} url
|
|
54
|
+
* @param {{}} [context={}]
|
|
55
|
+
* @return {*}
|
|
56
|
+
* @memberof BlockletManager
|
|
57
|
+
*/
|
|
58
|
+
async download(meta, rootDid, url, context = {}) {
|
|
59
|
+
const { bundleName: name, bundleDid: did, version, dist = {} } = meta;
|
|
60
|
+
const { tarball, integrity } = dist;
|
|
61
|
+
|
|
62
|
+
const lockName = `download-${did}-${version}`;
|
|
63
|
+
let lock = this.downloadLocks[lockName];
|
|
64
|
+
if (!lock) {
|
|
65
|
+
lock = new Lock(lockName);
|
|
66
|
+
this.downloadLocks[lockName] = lock;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await lock.acquire();
|
|
71
|
+
this.logger.info('downloaded blocklet for installing', { name, version, tarball, integrity });
|
|
72
|
+
const cwd = path.join(this.downloadDir, 'download', name);
|
|
73
|
+
await fs.ensureDir(cwd);
|
|
74
|
+
this.logger.info('start download blocklet', { name, version, cwd, tarball, integrity });
|
|
75
|
+
const tarballPath = await this._downloadTarball({
|
|
76
|
+
name,
|
|
77
|
+
did,
|
|
78
|
+
version,
|
|
79
|
+
cwd,
|
|
80
|
+
tarball,
|
|
81
|
+
integrity,
|
|
82
|
+
verify: true,
|
|
83
|
+
ctrlStore: this.downloadCtrls,
|
|
84
|
+
rootDid,
|
|
85
|
+
url,
|
|
86
|
+
context,
|
|
87
|
+
});
|
|
88
|
+
this.logger.info('downloaded blocklet tar file', { name, version, tarballPath });
|
|
89
|
+
if (tarballPath === downloadFile.CANCEL) {
|
|
90
|
+
lock.release();
|
|
91
|
+
return { isCancelled: true };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// resolve tarball and mv tarball to cache after resolved
|
|
95
|
+
await resolveDownload(tarballPath, this.installDir, { removeTarFile: false });
|
|
96
|
+
await this._addCacheTarFile(tarballPath, integrity, getComponentBundleId({ meta }));
|
|
97
|
+
|
|
98
|
+
this.logger.info('resolved blocklet tar file to install dir', { name, version });
|
|
99
|
+
lock.release();
|
|
100
|
+
return { isCancelled: false };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
lock.release();
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// eslint-disable-next-line no-unused-vars
|
|
108
|
+
async cancelDownload(rootDid) {
|
|
109
|
+
if (this.downloadCtrls[rootDid]) {
|
|
110
|
+
for (const cancelCtrl of this.downloadCtrls[rootDid].values()) {
|
|
111
|
+
cancelCtrl.cancel();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
*
|
|
118
|
+
*
|
|
119
|
+
* @param {{
|
|
120
|
+
* url: string,
|
|
121
|
+
* cwd: string,
|
|
122
|
+
* tarball: string,
|
|
123
|
+
* did: string,
|
|
124
|
+
* integrity: string,
|
|
125
|
+
* verify: boolean = true,
|
|
126
|
+
* ctrlStore: {},
|
|
127
|
+
* rootDid: string,
|
|
128
|
+
* context: {} = {},
|
|
129
|
+
* }} { url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid, context = {} }
|
|
130
|
+
* @return {*}
|
|
131
|
+
* @memberof BlockletManager
|
|
132
|
+
*/
|
|
133
|
+
async _downloadTarball({ url, cwd, tarball, did, integrity, verify = true, ctrlStore = {}, rootDid, context = {} }) {
|
|
134
|
+
fs.mkdirSync(cwd, { recursive: true });
|
|
135
|
+
|
|
136
|
+
const tarballName = url.split('/').slice(-1)[0];
|
|
137
|
+
|
|
138
|
+
const tarballPath = path.join(cwd, tarballName);
|
|
139
|
+
|
|
140
|
+
const { protocol } = new URL(url);
|
|
141
|
+
|
|
142
|
+
const cachedTarFile = await this._getCachedTarFile(integrity);
|
|
143
|
+
if (cachedTarFile) {
|
|
144
|
+
this.logger.info('found cache tarFile', { did, tarballName, integrity });
|
|
145
|
+
|
|
146
|
+
await this.cacheTarBallLock.acquire();
|
|
147
|
+
try {
|
|
148
|
+
await fs.move(cachedTarFile, tarballPath, { overwrite: true });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
await this.cacheTarBallLock.release();
|
|
151
|
+
this.logger.error('move cache tar file failed', { cachedTarFile, tarballPath, error });
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
await this.cacheTarBallLock.release();
|
|
155
|
+
} else if (protocol.startsWith('file')) {
|
|
156
|
+
await fs.copy(decodeURIComponent(fileURLToPath(url)), tarballPath);
|
|
157
|
+
} else {
|
|
158
|
+
const cancelCtrl = new downloadFile.CancelCtrl();
|
|
159
|
+
|
|
160
|
+
if (!ctrlStore[rootDid]) {
|
|
161
|
+
ctrlStore[rootDid] = new Map();
|
|
162
|
+
}
|
|
163
|
+
ctrlStore[rootDid].set(did, cancelCtrl);
|
|
164
|
+
|
|
165
|
+
const headers = pick(context.headers ? cloneDeep(context.headers) : {}, [
|
|
166
|
+
'x-server-did',
|
|
167
|
+
'x-server-public-key',
|
|
168
|
+
'x-server-signature',
|
|
169
|
+
]);
|
|
170
|
+
const exist = (context.downloadTokenList || []).find((x) => x.did === did);
|
|
171
|
+
if (exist) {
|
|
172
|
+
headers['x-download-token'] = exist.token;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await downloadFile(url, path.join(cwd, tarballName), { cancelCtrl }, { ...context, headers });
|
|
176
|
+
|
|
177
|
+
if (ctrlStore[rootDid]) {
|
|
178
|
+
ctrlStore[rootDid].delete(did);
|
|
179
|
+
if (!ctrlStore[rootDid].size) {
|
|
180
|
+
delete ctrlStore[rootDid];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (cancelCtrl.isCancelled) {
|
|
185
|
+
return downloadFile.CANCEL;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (verify) {
|
|
190
|
+
try {
|
|
191
|
+
await verifyIntegrity({ file: tarballPath, integrity });
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.logger.error('verify integrity error', { error, tarball, url });
|
|
194
|
+
throw new Error(`${tarball} integrity check failed.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return tarballPath;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* use LRU algorithm
|
|
203
|
+
*/
|
|
204
|
+
async _addCacheTarFile(tarballPath, integrity, bundleId) {
|
|
205
|
+
// eslint-disable-next-line no-param-reassign
|
|
206
|
+
integrity = toBase58(integrity);
|
|
207
|
+
|
|
208
|
+
// move tarball to cache dir
|
|
209
|
+
const cwd = path.join(this.downloadDir, 'download-cache');
|
|
210
|
+
const cachePath = path.join(cwd, `${integrity}.tar.gz`);
|
|
211
|
+
await fs.ensureDir(cwd);
|
|
212
|
+
await fs.move(tarballPath, cachePath, { overwrite: true });
|
|
213
|
+
|
|
214
|
+
const cacheList = (await this.cache.get(CACHE_KEY)) || [];
|
|
215
|
+
const exist = cacheList.find((x) => x.bundleId === bundleId && x.integrity === integrity);
|
|
216
|
+
|
|
217
|
+
// update
|
|
218
|
+
if (exist) {
|
|
219
|
+
this.logger.info('update cache tarFile', { base58: integrity });
|
|
220
|
+
exist.accessAt = Date.now();
|
|
221
|
+
await this.cache.set(CACHE_KEY, cacheList);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// add
|
|
226
|
+
cacheList.push({ integrity, accessAt: Date.now(), bundleId });
|
|
227
|
+
if (cacheList.length > 50) {
|
|
228
|
+
// find and remove
|
|
229
|
+
let minIndex = 0;
|
|
230
|
+
let min = cacheList[0];
|
|
231
|
+
cacheList.forEach((x, i) => {
|
|
232
|
+
if (x.accessAt < min.accessAt) {
|
|
233
|
+
minIndex = i;
|
|
234
|
+
min = x;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
cacheList.splice(minIndex, 1);
|
|
239
|
+
|
|
240
|
+
const removeFile = path.join(cwd, `${min.integrity}.tar.gz`);
|
|
241
|
+
await this.cacheTarBallLock.acquire();
|
|
242
|
+
try {
|
|
243
|
+
await fs.remove(removeFile);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
this.logger.error('remove cache tar file failed', { file: removeFile, error });
|
|
246
|
+
}
|
|
247
|
+
await this.cacheTarBallLock.release();
|
|
248
|
+
|
|
249
|
+
this.logger.info('remove cache tarFile', { base58: min.integrity });
|
|
250
|
+
}
|
|
251
|
+
this.logger.info('add cache tarFile', { base58: integrity });
|
|
252
|
+
|
|
253
|
+
// update
|
|
254
|
+
await this.cache.set(CACHE_KEY, cacheList);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async _getCachedTarFile(integrity) {
|
|
258
|
+
// eslint-disable-next-line no-param-reassign
|
|
259
|
+
integrity = toBase58(integrity);
|
|
260
|
+
|
|
261
|
+
const cwd = path.join(this.downloadDir, 'download-cache');
|
|
262
|
+
const cachePath = path.join(cwd, `${integrity}.tar.gz`);
|
|
263
|
+
|
|
264
|
+
if (fs.existsSync(cachePath)) {
|
|
265
|
+
return cachePath;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = BundleDownloader;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const { Throttle } = require('stream-throttle');
|
|
4
|
+
|
|
5
|
+
const validateBlockletEntry = require('@blocklet/meta/lib/entry');
|
|
6
|
+
const { BLOCKLET_BUNDLE_FOLDER } = require('@blocklet/constant');
|
|
7
|
+
|
|
8
|
+
const defaultLogger = require('@abtnode/logger')('@abtnode/core:resolve-download');
|
|
9
|
+
|
|
10
|
+
const { getBlockletMeta } = require('../../util');
|
|
11
|
+
|
|
12
|
+
const { ensureBlockletExpanded, expandTarball, getBundleDir } = require('../../util/blocklet');
|
|
13
|
+
|
|
14
|
+
const asyncFs = fs.promises;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* decompress file, format dir and move to installDir
|
|
18
|
+
* @param {string} src file
|
|
19
|
+
* @param {{
|
|
20
|
+
* cwd: string;
|
|
21
|
+
* removeTarFile: boolean;
|
|
22
|
+
* originalMeta; // for verification
|
|
23
|
+
* logger;
|
|
24
|
+
* }} option
|
|
25
|
+
*/
|
|
26
|
+
const resolveDownload = async (
|
|
27
|
+
tarFile,
|
|
28
|
+
dist,
|
|
29
|
+
{ cwd = '/', removeTarFile = true, logger = defaultLogger, originalMeta } = {}
|
|
30
|
+
) => {
|
|
31
|
+
// eslint-disable-next-line no-param-reassign
|
|
32
|
+
tarFile = path.join(cwd, tarFile);
|
|
33
|
+
// eslint-disable-next-line no-param-reassign
|
|
34
|
+
dist = path.join(cwd, dist);
|
|
35
|
+
|
|
36
|
+
const downloadDir = path.join(path.dirname(tarFile), path.basename(tarFile, path.extname(tarFile)));
|
|
37
|
+
const tmp = `${downloadDir}-tmp`;
|
|
38
|
+
try {
|
|
39
|
+
await expandTarball({ source: tarFile, dest: tmp, strip: 0 });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
logger.error('expand blocklet tar file error', { error });
|
|
42
|
+
throw error;
|
|
43
|
+
} finally {
|
|
44
|
+
if (removeTarFile) {
|
|
45
|
+
fs.removeSync(tarFile);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let installDir;
|
|
49
|
+
let meta;
|
|
50
|
+
try {
|
|
51
|
+
// resolve dir
|
|
52
|
+
let dir = tmp;
|
|
53
|
+
const files = await asyncFs.readdir(dir);
|
|
54
|
+
if (files.includes('package')) {
|
|
55
|
+
dir = path.join(tmp, 'package');
|
|
56
|
+
} else if (files.length === 1) {
|
|
57
|
+
const d = path.join(dir, files[0]);
|
|
58
|
+
if ((await asyncFs.stat(d)).isDirectory()) {
|
|
59
|
+
dir = d;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (fs.existsSync(path.join(dir, BLOCKLET_BUNDLE_FOLDER))) {
|
|
64
|
+
dir = path.join(dir, BLOCKLET_BUNDLE_FOLDER);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
logger.info('Move downloadDir to installDir', { downloadDir });
|
|
68
|
+
await fs.move(dir, downloadDir, { overwrite: true });
|
|
69
|
+
fs.removeSync(tmp);
|
|
70
|
+
|
|
71
|
+
meta = getBlockletMeta(downloadDir, { ensureMain: true });
|
|
72
|
+
const { did, name, version } = meta;
|
|
73
|
+
|
|
74
|
+
// validate
|
|
75
|
+
if (originalMeta && (originalMeta.did !== did || originalMeta.name !== name || originalMeta.version !== version)) {
|
|
76
|
+
logger.error('Meta has differences', { originalMeta, meta });
|
|
77
|
+
throw new Error('There are differences between the meta from tarball file and the original meta');
|
|
78
|
+
}
|
|
79
|
+
await validateBlockletEntry(downloadDir, meta);
|
|
80
|
+
|
|
81
|
+
await ensureBlockletExpanded(meta, downloadDir);
|
|
82
|
+
|
|
83
|
+
installDir = getBundleDir(dist, meta);
|
|
84
|
+
if (fs.existsSync(installDir)) {
|
|
85
|
+
fs.removeSync(installDir);
|
|
86
|
+
logger.info('cleanup blocklet upgrade dir', { name, version, installDir });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
await fs.move(downloadDir, installDir, { overwrite: true });
|
|
92
|
+
} catch (error) {
|
|
93
|
+
fs.removeSync(downloadDir);
|
|
94
|
+
fs.removeSync(tmp);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { meta, installDir };
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const resolveDiffDownload = async (
|
|
102
|
+
tarFile,
|
|
103
|
+
dist,
|
|
104
|
+
{ meta: oldMeta, deleteSet, cwd = '/', logger = defaultLogger } = {}
|
|
105
|
+
) => {
|
|
106
|
+
// eslint-disable-next-line no-param-reassign
|
|
107
|
+
tarFile = path.join(cwd, tarFile);
|
|
108
|
+
// eslint-disable-next-line no-param-reassign
|
|
109
|
+
dist = path.join(cwd, dist);
|
|
110
|
+
|
|
111
|
+
logger.info('Resolve diff download', { tarFile, cwd });
|
|
112
|
+
const downloadDir = path.join(path.dirname(tarFile), path.basename(tarFile, path.extname(tarFile)));
|
|
113
|
+
const diffDir = `${downloadDir}-diff`;
|
|
114
|
+
try {
|
|
115
|
+
await expandTarball({ source: tarFile, dest: diffDir, strip: 0 });
|
|
116
|
+
fs.removeSync(tarFile);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
fs.removeSync(tarFile);
|
|
119
|
+
logger.error('expand blocklet tar file error', { error });
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
logger.info('Copy installDir to downloadDir', { installDir: dist, downloadDir });
|
|
123
|
+
await fs.copy(getBundleDir(dist, oldMeta), downloadDir);
|
|
124
|
+
try {
|
|
125
|
+
// delete
|
|
126
|
+
logger.info('Delete files from downloadDir', { fileNum: deleteSet.length });
|
|
127
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
128
|
+
for (const file of deleteSet) {
|
|
129
|
+
/* eslint-disable no-await-in-loop */
|
|
130
|
+
await fs.remove(path.join(downloadDir, file));
|
|
131
|
+
}
|
|
132
|
+
// walk & cover
|
|
133
|
+
logger.info('Move files from diffDir to downloadDir', { diffDir, downloadDir });
|
|
134
|
+
const walkDiff = async (dir) => {
|
|
135
|
+
const files = await asyncFs.readdir(dir);
|
|
136
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
137
|
+
for (const file of files) {
|
|
138
|
+
const p = path.join(dir, file);
|
|
139
|
+
const stat = await asyncFs.stat(p);
|
|
140
|
+
if (stat.isDirectory()) {
|
|
141
|
+
await walkDiff(p);
|
|
142
|
+
} else if (stat.isFile()) {
|
|
143
|
+
await fs.move(p, path.join(downloadDir, path.relative(diffDir, p)), { overwrite: true });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
await walkDiff(diffDir);
|
|
148
|
+
fs.removeSync(diffDir);
|
|
149
|
+
const meta = getBlockletMeta(downloadDir, { ensureMain: true });
|
|
150
|
+
|
|
151
|
+
await ensureBlockletExpanded(meta, downloadDir);
|
|
152
|
+
|
|
153
|
+
// move to installDir
|
|
154
|
+
const bundleDir = getBundleDir(dist, meta);
|
|
155
|
+
logger.info('Move downloadDir to installDir', { downloadDir, bundleDir });
|
|
156
|
+
await fs.move(downloadDir, bundleDir, { overwrite: true });
|
|
157
|
+
|
|
158
|
+
return { meta, installDir: bundleDir };
|
|
159
|
+
} catch (error) {
|
|
160
|
+
fs.removeSync(downloadDir);
|
|
161
|
+
fs.removeSync(diffDir);
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* const { filename, mimetype, encoding, createReadStream } = await uploader;
|
|
168
|
+
*/
|
|
169
|
+
const downloadFromUpload = async (uploader, { downloadDir, logger = defaultLogger } = {}) => {
|
|
170
|
+
const cwd = downloadDir;
|
|
171
|
+
const { filename, createReadStream } = await uploader;
|
|
172
|
+
const tarFile = path.join(cwd, `${path.basename(filename, path.extname(filename))}.tgz`);
|
|
173
|
+
await fs.ensureDir(cwd);
|
|
174
|
+
await new Promise((resolve, reject) => {
|
|
175
|
+
const readStream = createReadStream();
|
|
176
|
+
const writeStream = fs.createWriteStream(tarFile);
|
|
177
|
+
readStream
|
|
178
|
+
.pipe(new Throttle({ rate: 1024 * 1024 * 20 })) // 20MB
|
|
179
|
+
.pipe(writeStream);
|
|
180
|
+
readStream.on('error', (error) => {
|
|
181
|
+
logger.error('File upload read stream failed', { error });
|
|
182
|
+
writeStream.destroy(new Error(`File upload read stream failed, ${error.message}`));
|
|
183
|
+
});
|
|
184
|
+
writeStream.on('error', (error) => {
|
|
185
|
+
logger.error('File upload write stream failed', { error });
|
|
186
|
+
fs.removeSync(tarFile);
|
|
187
|
+
reject(error);
|
|
188
|
+
});
|
|
189
|
+
writeStream.on('finish', resolve);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return { tarFile };
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
resolveDownload,
|
|
197
|
+
resolveDiffDownload,
|
|
198
|
+
downloadFromUpload,
|
|
199
|
+
};
|