@abtnode/util 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/async-pm2.js +13 -0
- package/lib/axios.js +32 -4
- package/lib/base32.js +14 -0
- package/lib/can-pkg-rw.js +1 -1
- package/lib/check-domain-match.js +8 -3
- package/lib/did-document.js +128 -0
- package/lib/download-file.js +51 -9
- package/lib/ensure-endpoint-healthy.js +1 -1
- package/lib/format-back-slash.js +9 -0
- package/lib/format-context.js +28 -0
- package/lib/format-name.js +5 -0
- package/lib/get-folder-size.js +16 -1
- package/lib/get-ip.js +6 -6
- package/lib/is-ec2.js +9 -8
- package/lib/is-path-prefix-equal.js +3 -0
- package/lib/locate-npm-global-by-binary.js +16 -6
- package/lib/log.js +391 -0
- package/lib/logo-middleware.js +136 -0
- package/lib/nft.js +15 -0
- package/lib/run-script.js +131 -0
- package/lib/security.js +45 -0
- package/lib/sleep.js +1 -0
- package/lib/url-evaluation/check-accessible-browser.js +17 -0
- package/lib/url-evaluation/check-accessible-node.js +14 -0
- package/lib/url-evaluation/index.js +86 -0
- package/lib/user-avatar.js +69 -0
- package/package.json +42 -19
- package/lib/sys-info.js +0 -56
- /package/lib/{get-node-wallet.js → get-app-wallet.js} +0 -0
package/lib/log.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const createArchive = require('archiver');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const dayjs = require('dayjs');
|
|
6
|
+
const glob = require('fast-glob');
|
|
7
|
+
const isEqual = require('lodash/isEqual');
|
|
8
|
+
const { Tail } = require('tail');
|
|
9
|
+
const readLastLines = require('read-last-lines');
|
|
10
|
+
const { BLOCKLET_MODES } = require('@blocklet/constant');
|
|
11
|
+
const logger = require('@abtnode/logger')(require('../package.json').name);
|
|
12
|
+
|
|
13
|
+
class StreamLog {
|
|
14
|
+
constructor() {
|
|
15
|
+
this._files = null; // Object { <level>: <filePath> }
|
|
16
|
+
this._tails = null; // Object { <level>: <Tail> }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {Object} files Object { <level>: <filePath> }
|
|
21
|
+
* @return {Boolean} if files change
|
|
22
|
+
*/
|
|
23
|
+
async setFiles(files) {
|
|
24
|
+
if (!isEqual(this._files, files)) {
|
|
25
|
+
this.clearTails();
|
|
26
|
+
this._files = files;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getRecent(lineNum, callback) {
|
|
33
|
+
if (!this._files) {
|
|
34
|
+
callback(new Error('files is empty'));
|
|
35
|
+
}
|
|
36
|
+
Object.entries(this._files).forEach(([level, file]) => {
|
|
37
|
+
if (!file || !fs.existsSync(file)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
readLastLines
|
|
41
|
+
.read(file, lineNum || 0)
|
|
42
|
+
.then((data) => {
|
|
43
|
+
callback(null, level, data);
|
|
44
|
+
})
|
|
45
|
+
.catch((error) => {
|
|
46
|
+
callback(error, level);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ensureTails() {
|
|
52
|
+
if (this._tails) {
|
|
53
|
+
return this._tails;
|
|
54
|
+
}
|
|
55
|
+
if (!this._files) {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
this._tails = {};
|
|
60
|
+
Object.entries(this._files).forEach(([level, file]) => {
|
|
61
|
+
if (!file || !fs.existsSync(file)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 测试发现 Windows 下 fs.watch 没有效果,但是 fs.watch 的性能更好,所以只在 windows 下使用 fs.watchFile
|
|
66
|
+
this._tails[level] = new Tail(file, {
|
|
67
|
+
useWatchFile: process.platform === 'win32',
|
|
68
|
+
fsWatchOptions: { interval: 1500 },
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return this._tails;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
this._tails = null;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
clearTails() {
|
|
79
|
+
if (this._tails) {
|
|
80
|
+
Object.values(this._tails).forEach((tail) => {
|
|
81
|
+
tail.unwatch();
|
|
82
|
+
});
|
|
83
|
+
this._tails = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const createFile = (files) => {
|
|
89
|
+
Object.values(files).forEach((file) => {
|
|
90
|
+
if (!fs.existsSync(file)) {
|
|
91
|
+
try {
|
|
92
|
+
const dir = path.dirname(file);
|
|
93
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
94
|
+
} catch (err) {
|
|
95
|
+
// Do nothing
|
|
96
|
+
}
|
|
97
|
+
fs.writeFileSync(file, '', 'utf8');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const getLogFiles = async ({ name, node }) => {
|
|
103
|
+
const dt = new Date();
|
|
104
|
+
const { yyyy, mm, dd } = {
|
|
105
|
+
yyyy: dt.getFullYear(),
|
|
106
|
+
mm: `${dt.getMonth() + 1}`.padStart(2, 0),
|
|
107
|
+
dd: `${dt.getDate()}`.padStart(2, 0),
|
|
108
|
+
};
|
|
109
|
+
const date = `${yyyy}-${mm}-${dd}`;
|
|
110
|
+
|
|
111
|
+
if (name === 'abtnode') {
|
|
112
|
+
const logDir = path.join(node.dataDirs.logs, '_abtnode');
|
|
113
|
+
|
|
114
|
+
const info = path.join(logDir, `daemon-${date}.log`);
|
|
115
|
+
const error = path.join(logDir, `daemon-error-${date}.log`);
|
|
116
|
+
const access = path.join(logDir, 'access.log');
|
|
117
|
+
const stdout = path.join(logDir, 'daemon.stdout.log');
|
|
118
|
+
const stderr = path.join(logDir, 'daemon.stderr.log');
|
|
119
|
+
|
|
120
|
+
createFile({
|
|
121
|
+
info,
|
|
122
|
+
error,
|
|
123
|
+
access,
|
|
124
|
+
stdout,
|
|
125
|
+
stderr,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
info,
|
|
130
|
+
error,
|
|
131
|
+
access,
|
|
132
|
+
stdout,
|
|
133
|
+
stderr,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (name === 'blocklet-services') {
|
|
138
|
+
const logDir = path.join(node.dataDirs.logs, '_abtnode');
|
|
139
|
+
const info = path.join(logDir, 'service.log');
|
|
140
|
+
const error = path.join(logDir, 'service.error.log');
|
|
141
|
+
createFile({ info, error });
|
|
142
|
+
return { info, error };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (name.indexOf('blocklet-') === 0) {
|
|
146
|
+
const did = name.substring('blocklet-'.length);
|
|
147
|
+
const blocklet = await node.getBlocklet({ did, attachConfig: false });
|
|
148
|
+
const { name: blockletName } = blocklet.meta;
|
|
149
|
+
|
|
150
|
+
const dir = path.join(node.dataDirs.logs, blockletName);
|
|
151
|
+
const info = path.join(dir, `info-${date}.log`);
|
|
152
|
+
const error = path.join(dir, `info-error-${date}.log`);
|
|
153
|
+
const access = path.join(dir, 'access.log');
|
|
154
|
+
const stdout = path.join(dir, 'output.log');
|
|
155
|
+
const stderr = path.join(dir, 'error.log');
|
|
156
|
+
|
|
157
|
+
createFile({
|
|
158
|
+
info,
|
|
159
|
+
error,
|
|
160
|
+
access,
|
|
161
|
+
stdout,
|
|
162
|
+
stderr,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
info,
|
|
167
|
+
error,
|
|
168
|
+
access,
|
|
169
|
+
stdout,
|
|
170
|
+
stderr,
|
|
171
|
+
recent: blocklet.mode === BLOCKLET_MODES.DEVELOPMENT ? 0 : 100,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (name.startsWith('service-gateway-')) {
|
|
176
|
+
const providerName = name.substring('service-gateway-'.length);
|
|
177
|
+
const provider = node.getRouterProvider(providerName);
|
|
178
|
+
if (!provider) {
|
|
179
|
+
logger.error('router engine is empty', { name, providerName });
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return provider.getLogFilesForToday();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {};
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const getDownloadLogFilesFromServer = async ({ dates, nodeInfo, node } = {}) => {
|
|
190
|
+
const logDir = path.join(node.dataDirs.logs, '_abtnode');
|
|
191
|
+
|
|
192
|
+
const pm2Log = path.join(logDir, 'pm2.log');
|
|
193
|
+
const pm2LogSrc = path.join(process.env.PM2_HOME, 'pm2.log');
|
|
194
|
+
if (fs.existsSync(pm2LogSrc)) {
|
|
195
|
+
await fs.copy(pm2LogSrc, pm2Log);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const provider = node.getRouterProvider(nodeInfo.routing.provider);
|
|
199
|
+
if (!provider) {
|
|
200
|
+
logger.error('router engine is empty');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const list = [];
|
|
204
|
+
|
|
205
|
+
dates.forEach((d) => {
|
|
206
|
+
// log file created by @abtnode/log
|
|
207
|
+
// log file created by abt-node-log-rotate
|
|
208
|
+
list.push(path.join(logDir, `*-${d}*`));
|
|
209
|
+
|
|
210
|
+
// log file create by router
|
|
211
|
+
const routerLogDir = provider.getLogDir();
|
|
212
|
+
if (routerLogDir) {
|
|
213
|
+
list.push(path.join(routerLogDir, `*-${d}*`));
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// abt-node-daemon console & gateway
|
|
218
|
+
list.push(path.join(logDir, 'daemon.stdout.log*'));
|
|
219
|
+
list.push(path.join(logDir, 'daemon.stderr.log*'));
|
|
220
|
+
list.push(path.join(logDir, 'access.log*'));
|
|
221
|
+
|
|
222
|
+
// abt-node-service console & gateway
|
|
223
|
+
list.push(path.join(logDir, 'service.output.log*'));
|
|
224
|
+
list.push(path.join(logDir, 'service.error.log*'));
|
|
225
|
+
list.push(path.join(logDir, 'service.log*'));
|
|
226
|
+
|
|
227
|
+
// abt-node-db-hub console & backup
|
|
228
|
+
list.push(path.join(logDir, 'db.output.log*'));
|
|
229
|
+
list.push(path.join(logDir, 'db.error.log*'));
|
|
230
|
+
|
|
231
|
+
// abt-node-event-hub console
|
|
232
|
+
list.push(path.join(logDir, 'event.output.log*'));
|
|
233
|
+
list.push(path.join(logDir, 'event.error.log*'));
|
|
234
|
+
|
|
235
|
+
// abt-node-log-rotate console
|
|
236
|
+
list.push(path.join(logDir, 'pm2-logrotate.stdout.log*'));
|
|
237
|
+
list.push(path.join(logDir, 'pm2-logrotate.stderr.log*'));
|
|
238
|
+
|
|
239
|
+
// abt-node-updater console
|
|
240
|
+
list.push(path.join(logDir, 'updater.error.log*'));
|
|
241
|
+
list.push(path.join(logDir, 'updater.output.log*'));
|
|
242
|
+
|
|
243
|
+
// fallback log
|
|
244
|
+
list.push(path.join(logDir, 'stderr.log*'));
|
|
245
|
+
list.push(path.join(logDir, 'stdout.log*'));
|
|
246
|
+
|
|
247
|
+
// router
|
|
248
|
+
list.push(...Object.values(provider.getLogFilesForToday() || {}));
|
|
249
|
+
|
|
250
|
+
// pm2 log
|
|
251
|
+
list.push(pm2Log);
|
|
252
|
+
|
|
253
|
+
return glob(list);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const getDownloadLogFilesFromBlocklet = async ({ dates, blocklet } = {}) => {
|
|
257
|
+
const logDir = path.join(blocklet.env.logsDir);
|
|
258
|
+
|
|
259
|
+
const list = [];
|
|
260
|
+
|
|
261
|
+
dates.forEach((d) => {
|
|
262
|
+
// log file created by @abtnode/log
|
|
263
|
+
// log file created by abt-node-log-rotate
|
|
264
|
+
list.push(path.join(logDir, `*-${d}*`));
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
list.push(path.join(logDir, 'output.log*'));
|
|
268
|
+
list.push(path.join(logDir, 'error.log*'));
|
|
269
|
+
list.push(path.join(logDir, 'access.log*'));
|
|
270
|
+
|
|
271
|
+
return glob(list);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const getDownloadLogFiles = async ({ did, node, days = 1, now } = {}) => {
|
|
275
|
+
const dates = [dayjs(now).format('YYYY-MM-DD')];
|
|
276
|
+
|
|
277
|
+
for (let i = 1; i <= days; i++) {
|
|
278
|
+
dates.unshift(dayjs(now).subtract(i, 'day').format('YYYY-MM-DD'));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const nodeInfo = await node.getNodeInfo();
|
|
282
|
+
|
|
283
|
+
if (nodeInfo.did === did) {
|
|
284
|
+
return getDownloadLogFilesFromServer({ dates, node, nodeInfo });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const blocklet = await node.getBlocklet({ did, attachRuntimeInfo: false });
|
|
288
|
+
if (!blocklet) {
|
|
289
|
+
throw new Error('blocklet not found');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return getDownloadLogFilesFromBlocklet({ dates, blocklet });
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const createStreamLogManager = ({ onLog, onGetLogFiles }) => {
|
|
296
|
+
const store = {}; // Object<name: streamLog>
|
|
297
|
+
|
|
298
|
+
const ensure = (name) => {
|
|
299
|
+
if (!store[name]) {
|
|
300
|
+
store[name] = new StreamLog();
|
|
301
|
+
}
|
|
302
|
+
return store[name];
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const destroy = (name) => {
|
|
306
|
+
logger.info('log stream: destroy', { name });
|
|
307
|
+
if (!store[name]) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const log = store[name];
|
|
311
|
+
try {
|
|
312
|
+
log.clearTails();
|
|
313
|
+
delete store[name];
|
|
314
|
+
} catch (error) {
|
|
315
|
+
logger.error('log stream: remove ref error ', error);
|
|
316
|
+
delete store[name];
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const add = async (name, topic, firstLogCb) => {
|
|
321
|
+
logger.info('log stream: add', { name });
|
|
322
|
+
const log = ensure(name);
|
|
323
|
+
try {
|
|
324
|
+
// update files
|
|
325
|
+
// push recent 100 log
|
|
326
|
+
const { recent = 100, ...logFiles } = await onGetLogFiles(name);
|
|
327
|
+
const changed = await log.setFiles(logFiles);
|
|
328
|
+
logger.info('log stream: added', { name, logFiles });
|
|
329
|
+
log.getRecent(recent, (error, level, data) => {
|
|
330
|
+
if (error) {
|
|
331
|
+
logger.error('log stream error ', error);
|
|
332
|
+
}
|
|
333
|
+
if (firstLogCb) {
|
|
334
|
+
firstLogCb(level, data, logFiles);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
// stream
|
|
338
|
+
if (changed) {
|
|
339
|
+
const tails = log.ensureTails();
|
|
340
|
+
Object.entries(tails).forEach(([level, tail]) => {
|
|
341
|
+
tail.on('line', (data) => {
|
|
342
|
+
onLog({ topic, level, logFiles, data });
|
|
343
|
+
});
|
|
344
|
+
tail.on('error', (error) => {
|
|
345
|
+
logger.error('log tail error ', { error });
|
|
346
|
+
destroy(name);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
logger.error('log stream error ', error);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
add,
|
|
357
|
+
destroy,
|
|
358
|
+
};
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const createDownloadLogStream = async ({ node, did, days, now }) => {
|
|
362
|
+
const files = await getDownloadLogFiles({ node, did, days, now });
|
|
363
|
+
|
|
364
|
+
if (!files.length) {
|
|
365
|
+
throw new Error('Log file does not found');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const cwd = path.dirname(node.dataDirs.logs);
|
|
369
|
+
|
|
370
|
+
const archive = createArchive('zip', { zlib: { level: 9 } });
|
|
371
|
+
files.forEach((x) => {
|
|
372
|
+
archive.file(x, {
|
|
373
|
+
name: x.replace(`${cwd}/logs`, '/logs').replace(`${cwd}`, '/logs').replace('/_abtnode', '/blocklet-server'),
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
archive.rawPipe = archive.pipe.bind(archive);
|
|
378
|
+
archive.pipe = (s) => {
|
|
379
|
+
archive.rawPipe(s);
|
|
380
|
+
archive.finalize();
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
return archive;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
module.exports = {
|
|
387
|
+
createStreamLogManager,
|
|
388
|
+
getLogFiles,
|
|
389
|
+
getDownloadLogFiles,
|
|
390
|
+
createDownloadLogStream,
|
|
391
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const get = require('lodash/get');
|
|
4
|
+
|
|
5
|
+
const logger = require('@abtnode/logger')('@abtnode/util:logo-middleware');
|
|
6
|
+
|
|
7
|
+
const ensureBlockletExist = async (req, res, next) => {
|
|
8
|
+
const { blocklet } = req;
|
|
9
|
+
|
|
10
|
+
if (!blocklet || !get(blocklet, 'env.appDir')) {
|
|
11
|
+
res.sendFallbackLogo();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
next();
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const ensureCustomSquareLogo = async (req, res, next) => {
|
|
19
|
+
const { blocklet, sendOptions } = req;
|
|
20
|
+
|
|
21
|
+
const customLogoSquare = get(blocklet, 'environmentObj.BLOCKLET_APP_LOGO_SQUARE');
|
|
22
|
+
|
|
23
|
+
if (customLogoSquare) {
|
|
24
|
+
if (customLogoSquare.startsWith('http')) {
|
|
25
|
+
res.redirect(customLogoSquare);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const logoFile = path.join(get(blocklet, 'env.appDir'), customLogoSquare);
|
|
30
|
+
if (fs.existsSync(logoFile)) {
|
|
31
|
+
res.sendFile(logoFile, sendOptions);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
next();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ensureBundleLogo = async (req, res, next) => {
|
|
40
|
+
const { blocklet, sendOptions } = req;
|
|
41
|
+
|
|
42
|
+
if (blocklet.meta.logo) {
|
|
43
|
+
const logoFile = path.join(get(blocklet, 'env.appDir'), blocklet.meta.logo);
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(logoFile)) {
|
|
46
|
+
res.sendFile(logoFile, sendOptions);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
next();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const fallbackLogo = async (req, res) => {
|
|
55
|
+
res.sendFallbackLogo();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line no-unused-vars
|
|
59
|
+
const cacheError = async (err, req, res, next) => {
|
|
60
|
+
logger.error('failed to send blocklet logo', { url: req.url, error: err });
|
|
61
|
+
res.sendFallbackLogo();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const ensureDefaultLogo = async (req, res, next) => {
|
|
65
|
+
const { blocklet, sendOptions } = req;
|
|
66
|
+
|
|
67
|
+
if (blocklet && get(blocklet, 'env.dataDir')) {
|
|
68
|
+
get(blocklet, 'env.dataDir');
|
|
69
|
+
|
|
70
|
+
const logoSvgFile = path.join(get(blocklet, 'env.dataDir'), 'logo.svg');
|
|
71
|
+
if (fs.existsSync(logoSvgFile)) {
|
|
72
|
+
res.sendFile(logoSvgFile, sendOptions);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const logoPngFile = path.join(get(blocklet, 'env.dataDir'), 'logo.png');
|
|
77
|
+
if (fs.existsSync(logoPngFile)) {
|
|
78
|
+
res.sendFile(logoPngFile, sendOptions);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
next();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const ensureCustomFavicon = async (req, res, next) => {
|
|
87
|
+
const { blocklet, sendOptions } = req;
|
|
88
|
+
|
|
89
|
+
const customLogoFavicon = get(blocklet, 'environmentObj.BLOCKLET_APP_LOGO_FAVICON');
|
|
90
|
+
if (customLogoFavicon) {
|
|
91
|
+
if (customLogoFavicon.startsWith('http')) {
|
|
92
|
+
res.redirect(customLogoFavicon);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const logoFile = path.join(get(blocklet, 'env.appDir'), customLogoFavicon);
|
|
97
|
+
if (fs.existsSync(logoFile)) {
|
|
98
|
+
res.sendFile(logoFile, sendOptions);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
next();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const attachSendLogoContext =
|
|
107
|
+
({ onSendFallbackLogo, onGetBlocklet }) =>
|
|
108
|
+
async (req, res, next) => {
|
|
109
|
+
const sendOptions = { maxAge: '1d' };
|
|
110
|
+
|
|
111
|
+
req.sendOptions = sendOptions;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
req.blocklet = await onGetBlocklet({ req });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
logger.error('failed to get blocklet', { url: req.url, error });
|
|
117
|
+
req.blocklet = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
res.sendFallbackLogo = () => {
|
|
121
|
+
onSendFallbackLogo({ res, sendOptions });
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
next();
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
attachSendLogoContext,
|
|
129
|
+
ensureBlockletExist,
|
|
130
|
+
ensureCustomSquareLogo,
|
|
131
|
+
ensureBundleLogo,
|
|
132
|
+
ensureCustomFavicon,
|
|
133
|
+
ensureDefaultLogo,
|
|
134
|
+
fallbackLogo,
|
|
135
|
+
cacheError,
|
|
136
|
+
};
|
package/lib/nft.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const last = require('lodash/last');
|
|
2
|
+
const get = require('lodash/get');
|
|
3
|
+
|
|
4
|
+
const getNftExpirationDate = (asset) => last(get(asset, 'data.value.expirationDate', []));
|
|
5
|
+
|
|
6
|
+
const isNFTExpired = (asset) => {
|
|
7
|
+
const expirationDate = getNftExpirationDate(asset);
|
|
8
|
+
return isDateExpired(expirationDate);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const isDateExpired = (expirationDate) => !!expirationDate && new Date(expirationDate).getTime() <= Date.now();
|
|
12
|
+
|
|
13
|
+
const isNFTConsumed = (asset) => !!asset.data.value.consumedTime;
|
|
14
|
+
|
|
15
|
+
module.exports = { isNFTExpired, getNftExpirationDate, isNFTConsumed };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/* eslint-disable prefer-const */
|
|
2
|
+
/* eslint-disable no-nested-ternary */
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const which = require('which');
|
|
5
|
+
const stream = require('stream');
|
|
6
|
+
const spawn = require('cross-spawn');
|
|
7
|
+
|
|
8
|
+
class PrefixTransform extends stream.Transform {
|
|
9
|
+
constructor(prefix, state) {
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
this.prefix = prefix;
|
|
13
|
+
this.state = state;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// transform all output from child process with a prefix for better readability
|
|
17
|
+
_transform(chunk, _encoding, callback) {
|
|
18
|
+
const { prefix, state } = this;
|
|
19
|
+
const nPrefix = `\n${prefix}`;
|
|
20
|
+
const firstPrefix = state.lastIsLineBreak ? prefix : state.lastPrefix !== prefix ? '\n' : '';
|
|
21
|
+
const prefixed = `${firstPrefix}${chunk}`.replace(/\n/g, nPrefix);
|
|
22
|
+
const index = prefixed.indexOf(prefix, Math.max(0, prefixed.length - prefix.length));
|
|
23
|
+
|
|
24
|
+
state.lastPrefix = prefix;
|
|
25
|
+
state.lastIsLineBreak = index !== -1;
|
|
26
|
+
|
|
27
|
+
callback(null, index !== -1 ? prefixed.slice(0, index) : prefixed);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const wrapStream = (next, prefix, state) => {
|
|
32
|
+
const transform = new PrefixTransform(`[${prefix}] `, state);
|
|
33
|
+
transform.pipe(next);
|
|
34
|
+
return transform;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Run script with a childProcess.spawn
|
|
39
|
+
*
|
|
40
|
+
* @param {string} script
|
|
41
|
+
* @param {string} label
|
|
42
|
+
* @param {object} options
|
|
43
|
+
* @param {object} options.env
|
|
44
|
+
* @param {string} options.cwd
|
|
45
|
+
* @param {number} options.timeout
|
|
46
|
+
* @param {boolean} options.silent
|
|
47
|
+
* @return {Promise}
|
|
48
|
+
*/
|
|
49
|
+
const runScript = (script, label, options = {}) => {
|
|
50
|
+
if (!script) {
|
|
51
|
+
throw new Error('script is required');
|
|
52
|
+
}
|
|
53
|
+
if (!label) {
|
|
54
|
+
throw new Error('label is required');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let child = null;
|
|
58
|
+
|
|
59
|
+
const cleanup = () => {
|
|
60
|
+
if (child) {
|
|
61
|
+
child.kill();
|
|
62
|
+
child = null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const promise = new Promise((resolve, reject) => {
|
|
67
|
+
process.stdout.setMaxListeners(0);
|
|
68
|
+
process.stderr.setMaxListeners(0);
|
|
69
|
+
|
|
70
|
+
let [command, ...args] = script.split(' ');
|
|
71
|
+
if (fs.existsSync(command) === false) {
|
|
72
|
+
command = which.sync(command);
|
|
73
|
+
if (!command) {
|
|
74
|
+
reject(new Error(`Command not found: ${command}`));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const state = {
|
|
80
|
+
lastPrefix: null,
|
|
81
|
+
lastIsLineBreak: true,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const stdout = wrapStream(process.stdout, label, state);
|
|
85
|
+
const stderr = wrapStream(process.stderr, label, state);
|
|
86
|
+
|
|
87
|
+
child = spawn(command, args, {
|
|
88
|
+
timeout: 2 * 60 * 1000,
|
|
89
|
+
detached: false,
|
|
90
|
+
shell: false,
|
|
91
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
92
|
+
...options,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (!options.silent) {
|
|
96
|
+
child.stdout.pipe(stdout, { end: false });
|
|
97
|
+
child.stderr.pipe(stderr, { end: false });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let hasUnhandledRejection = false;
|
|
101
|
+
const errorMessages = [];
|
|
102
|
+
child.stderr.on('data', (err) => {
|
|
103
|
+
errorMessages.push(err);
|
|
104
|
+
if (err.includes('UnhandledPromiseRejectionWarning')) {
|
|
105
|
+
hasUnhandledRejection = true;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
child.on('error', (err) => {
|
|
110
|
+
console.error(err);
|
|
111
|
+
errorMessages.push(err.message);
|
|
112
|
+
return reject(new Error(errorMessages.join('\r\n')));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
child.on('close', (code) => {
|
|
116
|
+
if (errorMessages.length > 0) {
|
|
117
|
+
if (code !== 0 || hasUnhandledRejection) {
|
|
118
|
+
return reject(new Error(errorMessages.join('\r\n')));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return resolve({ code, script });
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
promise.abort = cleanup;
|
|
127
|
+
|
|
128
|
+
return promise;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
module.exports = runScript;
|
package/lib/security.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const AES = require('@ocap/mcrypto/lib/crypter/aes-legacy').default;
|
|
3
|
+
|
|
4
|
+
const encrypt = (m, s, i) => AES.encrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, 'sha512').toString('hex'));
|
|
5
|
+
const decrypt = (m, s, i) => AES.decrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, 'sha512').toString('hex'));
|
|
6
|
+
|
|
7
|
+
const formatEnv = (raw, stringifyObject = true) => {
|
|
8
|
+
let value = raw;
|
|
9
|
+
|
|
10
|
+
// ensure no
|
|
11
|
+
if (Array.isArray(value) && value.every((x) => x.originFileObj && x.url)) {
|
|
12
|
+
value = value.map((x) => x.url);
|
|
13
|
+
if (value.length === 1) {
|
|
14
|
+
[value] = value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ensure no objects
|
|
19
|
+
if (stringifyObject) {
|
|
20
|
+
if (value && typeof value === 'object') {
|
|
21
|
+
value = JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ensure no line breaks for environment variables
|
|
26
|
+
if (value && typeof value === 'string') {
|
|
27
|
+
value = value.replace(/(\r\n|\n|\r)/gm, ' ');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return value;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// https://github.com/joaquimserafim/base64-url/blob/54d9c9ede66a8724f280cf24fd18c38b9a53915f/index.js#L10
|
|
34
|
+
const escape = (str) => str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
35
|
+
const encodeEncryptionKey = (key) => escape(Buffer.from(key).toString('base64'));
|
|
36
|
+
const unescape = (str) => (str + '==='.slice((str.length + 3) % 4)).replace(/-/g, '+').replace(/_/g, '/');
|
|
37
|
+
const decodeEncryptionKey = (str) => new Uint8Array(Buffer.from(unescape(str), 'base64'));
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
encrypt,
|
|
41
|
+
decrypt,
|
|
42
|
+
formatEnv,
|
|
43
|
+
encodeEncryptionKey,
|
|
44
|
+
decodeEncryptionKey,
|
|
45
|
+
};
|