@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.
Files changed (119) hide show
  1. package/lib/api/node.js +67 -69
  2. package/lib/api/team.js +386 -55
  3. package/lib/blocklet/downloader/blocklet-downloader.js +226 -0
  4. package/lib/blocklet/downloader/bundle-downloader.js +272 -0
  5. package/lib/blocklet/downloader/constants.js +3 -0
  6. package/lib/blocklet/downloader/resolve-download.js +199 -0
  7. package/lib/blocklet/extras.js +83 -26
  8. package/lib/blocklet/hooks.js +18 -65
  9. package/lib/blocklet/manager/base.js +10 -16
  10. package/lib/blocklet/manager/disk.js +1680 -1566
  11. package/lib/blocklet/manager/helper/install-application-from-backup.js +177 -0
  12. package/lib/blocklet/manager/helper/install-application-from-dev.js +94 -0
  13. package/lib/blocklet/manager/helper/install-application-from-general.js +188 -0
  14. package/lib/blocklet/manager/helper/install-component-from-dev.js +84 -0
  15. package/lib/blocklet/manager/helper/install-component-from-upload.js +181 -0
  16. package/lib/blocklet/manager/helper/install-component-from-url.js +173 -0
  17. package/lib/blocklet/manager/helper/migrate-application-to-struct-v2.js +450 -0
  18. package/lib/blocklet/manager/helper/rollback-cache.js +41 -0
  19. package/lib/blocklet/manager/helper/upgrade-components.js +152 -0
  20. package/lib/blocklet/migration.js +30 -52
  21. package/lib/blocklet/storage/backup/audit-log.js +27 -0
  22. package/lib/blocklet/storage/backup/base.js +62 -0
  23. package/lib/blocklet/storage/backup/blocklet-extras.js +92 -0
  24. package/lib/blocklet/storage/backup/blocklet.js +70 -0
  25. package/lib/blocklet/storage/backup/blocklets.js +74 -0
  26. package/lib/blocklet/storage/backup/data.js +19 -0
  27. package/lib/blocklet/storage/backup/logs.js +24 -0
  28. package/lib/blocklet/storage/backup/routing-rule.js +19 -0
  29. package/lib/blocklet/storage/backup/spaces.js +240 -0
  30. package/lib/blocklet/storage/restore/base.js +67 -0
  31. package/lib/blocklet/storage/restore/blocklet-extras.js +86 -0
  32. package/lib/blocklet/storage/restore/blocklet.js +56 -0
  33. package/lib/blocklet/storage/restore/blocklets.js +43 -0
  34. package/lib/blocklet/storage/restore/logs.js +21 -0
  35. package/lib/blocklet/storage/restore/spaces.js +156 -0
  36. package/lib/blocklet/storage/utils/hash.js +51 -0
  37. package/lib/blocklet/storage/utils/zip.js +43 -0
  38. package/lib/cert.js +206 -0
  39. package/lib/event.js +237 -64
  40. package/lib/index.js +191 -83
  41. package/lib/migrations/1.0.21-update-config.js +1 -1
  42. package/lib/migrations/1.0.22-max-memory.js +1 -1
  43. package/lib/migrations/1.0.25.js +1 -1
  44. package/lib/migrations/1.0.32-update-config.js +1 -1
  45. package/lib/migrations/1.0.33-blocklets.js +1 -1
  46. package/lib/migrations/1.5.20-registry.js +15 -0
  47. package/lib/migrations/1.6.17-blocklet-children.js +48 -0
  48. package/lib/migrations/1.6.21-rename-ip-echo-domain.js +35 -0
  49. package/lib/migrations/1.6.4-security.js +59 -0
  50. package/lib/migrations/1.6.5-security.js +60 -0
  51. package/lib/migrations/1.6.9-update-node-info-and-certificate.js +38 -0
  52. package/lib/migrations/1.7.1-blocklet-setup.js +18 -0
  53. package/lib/migrations/1.7.12-blocklet-meta.js +51 -0
  54. package/lib/migrations/1.7.15-blocklet-bundle-source.js +42 -0
  55. package/lib/migrations/1.7.20-blocklet-component.js +41 -0
  56. package/lib/migrations/1.8.33-blocklet-mem-limit.js +20 -0
  57. package/lib/migrations/README.md +1 -1
  58. package/lib/migrations/index.js +6 -2
  59. package/lib/monitor/blocklet-runtime-monitor.js +200 -0
  60. package/lib/monitor/get-history-list.js +37 -0
  61. package/lib/monitor/node-runtime-monitor.js +228 -0
  62. package/lib/router/helper.js +576 -500
  63. package/lib/router/index.js +85 -21
  64. package/lib/router/manager.js +146 -187
  65. package/lib/states/README.md +36 -1
  66. package/lib/states/access-key.js +39 -17
  67. package/lib/states/audit-log.js +462 -0
  68. package/lib/states/base.js +4 -213
  69. package/lib/states/blocklet-extras.js +195 -138
  70. package/lib/states/blocklet.js +371 -110
  71. package/lib/states/cache.js +8 -6
  72. package/lib/states/challenge.js +5 -5
  73. package/lib/states/index.js +19 -36
  74. package/lib/states/migration.js +4 -4
  75. package/lib/states/node.js +135 -46
  76. package/lib/states/notification.js +22 -35
  77. package/lib/states/session.js +17 -9
  78. package/lib/states/site.js +50 -25
  79. package/lib/states/user.js +74 -20
  80. package/lib/states/webhook.js +10 -6
  81. package/lib/team/manager.js +124 -7
  82. package/lib/util/blocklet.js +1223 -246
  83. package/lib/util/chain.js +1 -1
  84. package/lib/util/default-node-config.js +5 -23
  85. package/lib/util/disk-monitor.js +13 -10
  86. package/lib/util/domain-status.js +84 -15
  87. package/lib/util/get-accessible-external-node-ip.js +2 -2
  88. package/lib/util/get-domain-for-blocklet.js +13 -0
  89. package/lib/util/get-meta-from-url.js +33 -0
  90. package/lib/util/index.js +207 -272
  91. package/lib/util/ip.js +6 -0
  92. package/lib/util/maintain.js +233 -0
  93. package/lib/util/public-to-store.js +85 -0
  94. package/lib/util/ready.js +1 -1
  95. package/lib/util/requirement.js +28 -9
  96. package/lib/util/reset-node.js +22 -7
  97. package/lib/util/router.js +13 -0
  98. package/lib/util/rpc.js +16 -0
  99. package/lib/util/store.js +179 -0
  100. package/lib/util/sysinfo.js +44 -0
  101. package/lib/util/ua.js +54 -0
  102. package/lib/validators/blocklet-extra.js +24 -0
  103. package/lib/validators/node.js +25 -12
  104. package/lib/validators/permission.js +16 -1
  105. package/lib/validators/role.js +17 -3
  106. package/lib/validators/router.js +40 -20
  107. package/lib/validators/trusted-passport.js +1 -0
  108. package/lib/validators/util.js +22 -5
  109. package/lib/webhook/index.js +45 -35
  110. package/lib/webhook/sender/index.js +5 -0
  111. package/lib/webhook/sender/slack/index.js +1 -1
  112. package/lib/webhook/sender/wallet/index.js +48 -0
  113. package/package.json +54 -36
  114. package/lib/blocklet/registry.js +0 -205
  115. package/lib/states/https-cert.js +0 -67
  116. package/lib/util/get-ip-dns-domain-for-blocklet.js +0 -19
  117. package/lib/util/service.js +0 -66
  118. package/lib/util/upgrade.js +0 -178
  119. /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,3 @@
1
+ module.exports = {
2
+ CACHE_KEY: 'blocklet:manager:downloadCache',
3
+ };
@@ -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
+ };