hfs 0.57.26 → 0.57.27-beta3
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/admin/assets/{index-CHMhmNYS.js → index-CrWL6F86.js} +104 -103
- package/admin/assets/{sha512-Dsv49IAs.js → sha512-Dt-WJwnp.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/{index-legacy-BI8icwFp.js → index-legacy-BdpconSd.js} +4 -4
- package/frontend/assets/{sha512-legacy-D82alHBq.js → sha512-legacy-Dbrn-acf.js} +1 -1
- package/frontend/index.html +1 -1
- package/package.json +4 -3
- package/src/QuickZipStream.js +11 -8
- package/src/SendList.js +10 -8
- package/src/ThrottledStream.js +8 -6
- package/src/acme.js +5 -7
- package/src/adminApis.js +6 -7
- package/src/api.accounts.js +4 -6
- package/src/api.auth.js +7 -11
- package/src/api.get_file_list.js +11 -16
- package/src/api.lang.js +1 -1
- package/src/api.monitor.js +2 -3
- package/src/api.net.js +1 -2
- package/src/api.plugins.js +8 -10
- package/src/api.vfs.js +14 -19
- package/src/apiMiddleware.js +4 -4
- package/src/auth.js +6 -9
- package/src/basicWeb.js +1 -2
- package/src/block.js +1 -1
- package/src/commands.js +9 -9
- package/src/comments.js +4 -5
- package/src/config.js +8 -9
- package/src/connections.js +11 -6
- package/src/consoleLog.js +6 -6
- package/src/const.js +4 -4
- package/src/cross.js +19 -21
- package/src/customHtml.js +3 -4
- package/src/ddns.js +4 -4
- package/src/debounceAsync.js +3 -3
- package/src/errorPages.js +2 -3
- package/src/events.js +7 -10
- package/src/fileAttr.js +6 -6
- package/src/first.js +7 -5
- package/src/frontEndApis.js +5 -5
- package/src/geo.js +3 -5
- package/src/github.js +14 -17
- package/src/i18n.js +3 -4
- package/src/icons.js +1 -1
- package/src/index.js +2 -3
- package/src/lang.js +5 -6
- package/src/listen.js +25 -28
- package/src/log.js +18 -17
- package/src/middlewares.js +12 -15
- package/src/misc.js +9 -11
- package/src/nat.js +14 -16
- package/src/outboundProxy.js +7 -7
- package/src/perm.js +7 -12
- package/src/persistence.js +1 -1
- package/src/plugins.js +64 -74
- package/src/roots.js +1 -2
- package/src/selfCheck.js +5 -4
- package/src/serveFile.js +6 -7
- package/src/serveGuiAndSharedFiles.js +7 -9
- package/src/serveGuiFiles.js +13 -20
- package/src/stat.js +40 -0
- package/src/statWorker.js +11 -0
- package/src/throttler.js +5 -7
- package/src/update.js +7 -7
- package/src/upload.js +11 -13
- package/src/util-files.js +42 -32
- package/src/util-http.js +22 -22
- package/src/util-os.js +2 -4
- package/src/vfs.js +78 -68
- package/src/walkDir.js +9 -9
- package/src/watchLoad.js +4 -4
- package/src/zip.js +5 -6
package/src/plugins.js
CHANGED
|
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
exports.PLUGIN_MAIN_FILE = exports.pluginsConfig = exports.suspendPlugins = exports.enablePlugins = exports.pluginsWatcher = exports.SERVER_CODE_ID = exports.Plugin = exports.pluginsMiddleware = exports.STORAGE_FOLDER = exports.DELETE_ME_SUFFIX = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
40
|
+
exports.PLUGIN_MAIN_FILE = exports.pluginsConfig = exports.suspendPlugins = exports.enablePlugins = exports.pluginsWatcher = exports.SERVER_CODE_ID = exports.Plugin = exports.pluginsMiddleware = exports.pluginsScanned = exports.STORAGE_FOLDER = exports.DELETE_ME_SUFFIX = exports.DISABLING_SUFFIX = exports.PATH = void 0;
|
|
41
41
|
exports.isPluginRunning = isPluginRunning;
|
|
42
42
|
exports.isPluginEnabled = isPluginEnabled;
|
|
43
43
|
exports.enablePlugin = enablePlugin;
|
|
@@ -61,6 +61,7 @@ const Const = __importStar(require("./const"));
|
|
|
61
61
|
const misc_1 = require("./misc");
|
|
62
62
|
const misc = __importStar(require("./misc"));
|
|
63
63
|
const config_1 = require("./config");
|
|
64
|
+
const vfs_1 = require("./vfs");
|
|
64
65
|
const serveFile_1 = require("./serveFile");
|
|
65
66
|
const events_1 = __importDefault(require("./events"));
|
|
66
67
|
const promises_1 = require("fs/promises");
|
|
@@ -83,6 +84,7 @@ exports.PATH = 'plugins';
|
|
|
83
84
|
exports.DISABLING_SUFFIX = '-disabled';
|
|
84
85
|
exports.DELETE_ME_SUFFIX = '-delete_me' + exports.DISABLING_SUFFIX;
|
|
85
86
|
exports.STORAGE_FOLDER = 'storage';
|
|
87
|
+
exports.pluginsScanned = (0, misc_1.pendingPromise)();
|
|
86
88
|
setTimeout(async () => {
|
|
87
89
|
for (const x of await (0, promises_1.readdir)(exports.PATH))
|
|
88
90
|
if (x.endsWith(exports.DELETE_ME_SUFFIX))
|
|
@@ -90,8 +92,7 @@ setTimeout(async () => {
|
|
|
90
92
|
}, 1000);
|
|
91
93
|
const plugins = new Map(); // now that we care about the order, a simple object wouldn't do, because numbers are always at the beginning
|
|
92
94
|
function isPluginRunning(id) {
|
|
93
|
-
|
|
94
|
-
return Boolean((_a = plugins.get(id)) === null || _a === void 0 ? void 0 : _a.started);
|
|
95
|
+
return Boolean(plugins.get(id)?.started);
|
|
95
96
|
}
|
|
96
97
|
function isPluginEnabled(id, considerSuspension = false) {
|
|
97
98
|
return (!considerSuspension || !exports.suspendPlugins.get()) && exports.enablePlugins.get().includes(id);
|
|
@@ -113,8 +114,7 @@ async function stopPlugin(id) {
|
|
|
113
114
|
await waitRunning(id, false);
|
|
114
115
|
}
|
|
115
116
|
async function startPlugin(id) {
|
|
116
|
-
|
|
117
|
-
if ((_a = getPluginInfo(id)) === null || _a === void 0 ? void 0 : _a.isTheme)
|
|
117
|
+
if (getPluginInfo(id)?.isTheme)
|
|
118
118
|
await Promise.all(mapPlugins((pl, id) => pl.isTheme && stopPlugin(id)));
|
|
119
119
|
enablePlugin(id);
|
|
120
120
|
await waitRunning(id);
|
|
@@ -132,7 +132,7 @@ function setPluginConfig(id, changes) {
|
|
|
132
132
|
exports.pluginsConfig.set(allConfigs => {
|
|
133
133
|
const fields = getPluginConfigFields(id);
|
|
134
134
|
const oldConfig = allConfigs[id];
|
|
135
|
-
const newConfig = changes && lodash_1.default.pickBy({ ...oldConfig, ...changes }, (v, k) =>
|
|
135
|
+
const newConfig = changes && lodash_1.default.pickBy({ ...oldConfig, ...changes }, (v, k) => v != null && !(0, misc_1.same)(v, fields?.[k]?.defaultValue));
|
|
136
136
|
return { ...allConfigs, [id]: lodash_1.default.isEmpty(newConfig) ? undefined : newConfig };
|
|
137
137
|
});
|
|
138
138
|
}
|
|
@@ -146,16 +146,13 @@ function findPluginByRepo(repo) {
|
|
|
146
146
|
return pl;
|
|
147
147
|
return lodash_1.default.find(inactivePlugins, match);
|
|
148
148
|
function match(rec) {
|
|
149
|
-
|
|
150
|
-
return repo === ((_b = (_a = rec === null || rec === void 0 ? void 0 : rec.repo) === null || _a === void 0 ? void 0 : _a.main) !== null && _b !== void 0 ? _b : rec === null || rec === void 0 ? void 0 : rec.repo);
|
|
149
|
+
return repo === (rec?.repo?.main ?? rec?.repo);
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
152
|
function getPluginConfigFields(id) {
|
|
154
|
-
|
|
155
|
-
return (_a = plugins.get(id)) === null || _a === void 0 ? void 0 : _a.getData().config;
|
|
153
|
+
return plugins.get(id)?.getData().config;
|
|
156
154
|
}
|
|
157
155
|
async function initPlugin(pl, morePassedToInit) {
|
|
158
|
-
var _a;
|
|
159
156
|
const undoEvents = [];
|
|
160
157
|
const timeouts = [];
|
|
161
158
|
const controlledEvents = Object.create(events_1.default, (0, misc_1.objFromKeys)(['on', 'once', 'multi'], k => ({
|
|
@@ -165,12 +162,12 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
165
162
|
return ret;
|
|
166
163
|
}
|
|
167
164
|
})));
|
|
168
|
-
const res = await
|
|
165
|
+
const res = await pl.init?.({
|
|
169
166
|
Const, require,
|
|
170
167
|
// intercept all subscriptions, so to be able to undo them on unload
|
|
171
168
|
events: controlledEvents,
|
|
172
169
|
log: console.log,
|
|
173
|
-
setError(msg) { setError(
|
|
170
|
+
setError(msg) { setError(morePassedToInit?.id || 'server_code', msg); },
|
|
174
171
|
getHfsConfig: config_1.getConfig,
|
|
175
172
|
setInterval() {
|
|
176
173
|
const ret = setInterval(...arguments);
|
|
@@ -191,10 +188,10 @@ async function initPlugin(pl, morePassedToInit) {
|
|
|
191
188
|
controlledEvents.on('listening', ({ server }) => cb(server));
|
|
192
189
|
},
|
|
193
190
|
misc, _: lodash_1.default,
|
|
194
|
-
customApiCall, notifyClient: frontEndApis_1.notifyClient, addBlock: block_1.addBlock, ctxBelongsTo: perm_1.ctxBelongsTo, getConnections: connections_1.getConnections,
|
|
191
|
+
customApiCall, notifyClient: frontEndApis_1.notifyClient, addBlock: block_1.addBlock, ctxBelongsTo: perm_1.ctxBelongsTo, getConnections: connections_1.getConnections, normalizeFilename: vfs_1.normalizeFilename,
|
|
195
192
|
getCurrentUsername: auth_1.getCurrentUsername, getAccount: perm_1.getAccount, getUsernames: perm_1.getUsernames, addAccount: perm_1.addAccount, delAccount: perm_1.delAccount, updateAccount: perm_1.updateAccount, renameAccount: perm_1.renameAccount,
|
|
196
193
|
...morePassedToInit
|
|
197
|
-
})
|
|
194
|
+
});
|
|
198
195
|
Object.assign(pl, typeof res === 'function' ? { unload: res } : res);
|
|
199
196
|
(0, misc_1.patchKey)(pl, 'unload', was => () => {
|
|
200
197
|
for (const x of timeouts)
|
|
@@ -215,15 +212,13 @@ function warnOnce(msg) {
|
|
|
215
212
|
console.log('Warning: ' + msg);
|
|
216
213
|
}
|
|
217
214
|
const pluginsMiddleware = async (ctx, next) => {
|
|
218
|
-
var _a;
|
|
219
215
|
const after = {};
|
|
220
216
|
// run middleware plugins
|
|
221
217
|
let lastStatus = ctx.status;
|
|
222
218
|
let lastBody = ctx.body;
|
|
223
219
|
await Promise.all(mapPlugins(async (pl, id) => {
|
|
224
|
-
var _a;
|
|
225
220
|
try {
|
|
226
|
-
const res = await
|
|
221
|
+
const res = await pl.middleware?.(ctx);
|
|
227
222
|
if (lastStatus !== ctx.status || lastBody !== ctx.body) {
|
|
228
223
|
console.debug("plugin changed response", id);
|
|
229
224
|
lastStatus = ctx.status;
|
|
@@ -248,7 +243,7 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
248
243
|
const a = path.substring(const_1.PLUGINS_PUB_URI.length).split('/');
|
|
249
244
|
const name = a.shift();
|
|
250
245
|
if (plugins.has(name)) { // do it only if the plugin is loaded
|
|
251
|
-
if (
|
|
246
|
+
if (ctx.get('referer')?.endsWith('/'))
|
|
252
247
|
ctx.state.considerAsGui = true;
|
|
253
248
|
await (0, serveFile_1.serveFile)(ctx, plugins.get(name).folder + '/public/' + a.join('/'), const_1.MIME_AUTO);
|
|
254
249
|
}
|
|
@@ -265,7 +260,7 @@ const pluginsMiddleware = async (ctx, next) => {
|
|
|
265
260
|
printError(id, e);
|
|
266
261
|
}
|
|
267
262
|
function printError(id, e) {
|
|
268
|
-
console.log(`error middleware plugin ${id}: ${
|
|
263
|
+
console.log(`error middleware plugin ${id}: ${e?.message || e}`);
|
|
269
264
|
console.debug(e);
|
|
270
265
|
}
|
|
271
266
|
};
|
|
@@ -275,12 +270,18 @@ events_1.default.once('app', () => Object.assign(index_1.app.context, {
|
|
|
275
270
|
stop() { return this.isStopped = true; }
|
|
276
271
|
}));
|
|
277
272
|
class Plugin {
|
|
273
|
+
id;
|
|
274
|
+
folder;
|
|
275
|
+
data;
|
|
276
|
+
onUnload;
|
|
277
|
+
started = new Date();
|
|
278
|
+
icons;
|
|
279
|
+
log;
|
|
278
280
|
constructor(id, folder, data, onUnload) {
|
|
279
281
|
this.id = id;
|
|
280
282
|
this.folder = folder;
|
|
281
283
|
this.data = data;
|
|
282
284
|
this.onUnload = onUnload;
|
|
283
|
-
this.started = new Date();
|
|
284
285
|
if (!data)
|
|
285
286
|
throw 'invalid data';
|
|
286
287
|
this.log = [];
|
|
@@ -313,41 +314,36 @@ class Plugin {
|
|
|
313
314
|
plugins.set(k, temp);
|
|
314
315
|
}
|
|
315
316
|
}
|
|
316
|
-
get version() {
|
|
317
|
-
get description() {
|
|
318
|
-
get apiRequired() {
|
|
319
|
-
get isTheme() {
|
|
320
|
-
get repo() {
|
|
321
|
-
get depend() {
|
|
322
|
-
get afterPlugin() {
|
|
323
|
-
get beforePlugin() {
|
|
317
|
+
get version() { return this.data?.version; }
|
|
318
|
+
get description() { return this.data?.description; }
|
|
319
|
+
get apiRequired() { return this.data?.apiRequired; }
|
|
320
|
+
get isTheme() { return this.data?.isTheme; }
|
|
321
|
+
get repo() { return this.data?.repo; }
|
|
322
|
+
get depend() { return this.data?.depend; }
|
|
323
|
+
get afterPlugin() { return this.data?.afterPlugin; }
|
|
324
|
+
get beforePlugin() { return this.data?.beforePlugin; }
|
|
324
325
|
get middleware() {
|
|
325
|
-
|
|
326
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.middleware;
|
|
326
|
+
return this.data?.middleware;
|
|
327
327
|
}
|
|
328
328
|
get frontend_css() {
|
|
329
|
-
|
|
330
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_css;
|
|
329
|
+
return this.data?.frontend_css;
|
|
331
330
|
}
|
|
332
331
|
get frontend_js() {
|
|
333
|
-
|
|
334
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_js;
|
|
332
|
+
return this.data?.frontend_js;
|
|
335
333
|
}
|
|
336
334
|
get onDirEntry() {
|
|
337
|
-
|
|
338
|
-
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.onDirEntry;
|
|
335
|
+
return this.data?.onDirEntry;
|
|
339
336
|
}
|
|
340
337
|
getData() {
|
|
341
338
|
return this.data;
|
|
342
339
|
}
|
|
343
340
|
async unload(reloading = false) {
|
|
344
|
-
var _a, _b;
|
|
345
341
|
if (!this.started)
|
|
346
342
|
return;
|
|
347
343
|
this.started = null;
|
|
348
344
|
const { id } = this;
|
|
349
345
|
try {
|
|
350
|
-
await
|
|
346
|
+
await this.data?.unload?.();
|
|
351
347
|
}
|
|
352
348
|
catch (e) {
|
|
353
349
|
console.log('error unloading plugin', id, String(e));
|
|
@@ -362,11 +358,10 @@ class Plugin {
|
|
|
362
358
|
exports.Plugin = Plugin;
|
|
363
359
|
exports.SERVER_CODE_ID = '.'; // a name that will surely be not found among plugin folders
|
|
364
360
|
const serverCode = (0, config_1.defineConfig)('server_code', '', async (script, { k }) => {
|
|
365
|
-
var _a;
|
|
366
361
|
try {
|
|
367
|
-
(
|
|
362
|
+
(await serverCode.compiled())?.unload();
|
|
368
363
|
}
|
|
369
|
-
catch
|
|
364
|
+
catch { }
|
|
370
365
|
const res = {};
|
|
371
366
|
try {
|
|
372
367
|
new Function('exports', script)(res); // parse
|
|
@@ -414,7 +409,7 @@ if (!(0, fs_1.existsSync)(exports.PATH))
|
|
|
414
409
|
try {
|
|
415
410
|
(0, fs_1.mkdirSync)(exports.PATH);
|
|
416
411
|
}
|
|
417
|
-
catch
|
|
412
|
+
catch { }
|
|
418
413
|
exports.pluginsWatcher = (0, misc_1.watchDir)(exports.PATH, rescanAsap);
|
|
419
414
|
exports.enablePlugins = (0, config_1.defineConfig)('enable_plugins', ['antibrute']);
|
|
420
415
|
exports.enablePlugins.sub(rescanAsap);
|
|
@@ -426,22 +421,23 @@ async function rescan() {
|
|
|
426
421
|
console.debug('scanning plugins');
|
|
427
422
|
const patterns = [exports.PATH + '/*'];
|
|
428
423
|
if (const_1.APP_PATH !== process.cwd())
|
|
429
|
-
patterns.unshift((0, misc_1.
|
|
430
|
-
const
|
|
424
|
+
patterns.unshift((0, misc_1.escapeGlobPath)(const_1.APP_PATH) + '/' + patterns[0]); // first search bundled plugins, because otherwise they won't be loaded because of the folders with same name in .hfs/plugins (used for storage)
|
|
425
|
+
const existing = [];
|
|
431
426
|
for (const { path, dirent } of await (0, fast_glob_1.default)(patterns, { onlyFiles: false, suppressErrors: true, objectMode: true })) {
|
|
432
427
|
if (!dirent.isDirectory() || path.endsWith(exports.DISABLING_SUFFIX))
|
|
433
428
|
continue;
|
|
434
429
|
const id = path.split('/').slice(-1)[0];
|
|
435
|
-
|
|
430
|
+
existing.push(id);
|
|
436
431
|
if (!pluginWatchers.has(id))
|
|
437
432
|
pluginWatchers.set(id, watchPlugin(id, (0, path_1.join)(path, exports.PLUGIN_MAIN_FILE)));
|
|
438
433
|
}
|
|
439
434
|
for (const [id, cancelWatcher] of pluginWatchers.entries())
|
|
440
|
-
if (!
|
|
435
|
+
if (!existing.includes(id)) {
|
|
441
436
|
enablePlugin(id, false);
|
|
442
437
|
cancelWatcher();
|
|
443
438
|
pluginWatchers.delete(id);
|
|
444
439
|
}
|
|
440
|
+
exports.pluginsScanned.resolve();
|
|
445
441
|
}
|
|
446
442
|
function watchPlugin(id, path) {
|
|
447
443
|
console.debug('plugin watch', id);
|
|
@@ -499,14 +495,13 @@ function watchPlugin(id, path) {
|
|
|
499
495
|
events_1.default.emit('pluginStopped', p);
|
|
500
496
|
}
|
|
501
497
|
async function start() {
|
|
502
|
-
var _a;
|
|
503
498
|
if (starting)
|
|
504
499
|
return;
|
|
505
500
|
try {
|
|
506
501
|
starting = (0, misc_1.pendingPromise)();
|
|
507
502
|
// if dependencies are not ready right now, we give some time. Not super-solid but good enough for now.
|
|
508
503
|
const info = await parsePlugin();
|
|
509
|
-
if (!await (0, misc_1.waitFor)(() => lodash_1.default.isEmpty(getMissingDependencies(info)), { timeout:
|
|
504
|
+
if (!await (0, misc_1.waitFor)(() => lodash_1.default.isEmpty(getMissingDependencies(info)), { timeout: 5_000 }))
|
|
510
505
|
throw Error("plugin missing dependencies: " + lodash_1.default.map(getMissingDependencies(info), x => x.repo).join(', '));
|
|
511
506
|
if (getPluginInfo(id))
|
|
512
507
|
setError(id, '');
|
|
@@ -517,7 +512,7 @@ function watchPlugin(id, path) {
|
|
|
517
512
|
calculateBadApi(pluginData);
|
|
518
513
|
if (pluginData.badApi)
|
|
519
514
|
throw Error(pluginData.badApi);
|
|
520
|
-
await
|
|
515
|
+
await alreadyRunning?.unload(true);
|
|
521
516
|
console.debug("starting plugin", id);
|
|
522
517
|
const storageDir = (0, path_1.resolve)(exports.PATH, id, exports.STORAGE_FOLDER) + (const_1.IS_WINDOWS ? '\\' : '/');
|
|
523
518
|
await (0, promises_1.mkdir)(storageDir, { recursive: true });
|
|
@@ -551,9 +546,8 @@ function watchPlugin(id, path) {
|
|
|
551
546
|
});
|
|
552
547
|
},
|
|
553
548
|
getConfig(cfgKey) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
return cfgKey ? (_b = cur === null || cur === void 0 ? void 0 : cur[cfgKey]) !== null && _b !== void 0 ? _b : (_d = (_c = pluginData.config) === null || _c === void 0 ? void 0 : _c[cfgKey]) === null || _d === void 0 ? void 0 : _d.defaultValue
|
|
549
|
+
const cur = exports.pluginsConfig.get()?.[id];
|
|
550
|
+
return cfgKey ? cur?.[cfgKey] ?? pluginData.config?.[cfgKey]?.defaultValue
|
|
557
551
|
: lodash_1.default.defaults(cur, (0, misc_1.objSameKeys)(pluginData.config, x => x.defaultValue));
|
|
558
552
|
},
|
|
559
553
|
setConfig: (cfgKey, value) => setPluginConfig(id, { [cfgKey]: value }),
|
|
@@ -605,23 +599,22 @@ function watchPlugin(id, path) {
|
|
|
605
599
|
}
|
|
606
600
|
catch (e) {
|
|
607
601
|
await markItInactive();
|
|
608
|
-
const parsed =
|
|
609
|
-
const where =
|
|
602
|
+
const parsed = e.stack?.split('\n\n'); // this form is used by syntax-errors inside the plugin, which is useful to show
|
|
603
|
+
const where = parsed?.length > 1 ? `\n${parsed[0]}` : '';
|
|
610
604
|
e = (0, misc_1.prefix)('', e.message, where) || String(e);
|
|
611
605
|
setError(id, e);
|
|
612
606
|
}
|
|
613
607
|
finally {
|
|
614
|
-
starting
|
|
608
|
+
starting?.resolve();
|
|
615
609
|
starting = undefined;
|
|
616
610
|
}
|
|
617
611
|
}
|
|
618
612
|
}
|
|
619
613
|
function customApiCall(method, ...params) {
|
|
620
|
-
return mapPlugins(pl =>
|
|
614
|
+
return mapPlugins(pl => pl.getData().customApi?.[method]?.(...params));
|
|
621
615
|
}
|
|
622
616
|
function getError(id) {
|
|
623
|
-
|
|
624
|
-
return (_a = getPluginInfo(id)) === null || _a === void 0 ? void 0 : _a.error;
|
|
617
|
+
return getPluginInfo(id)?.error;
|
|
625
618
|
}
|
|
626
619
|
// returns true if there's an error, and it has changed
|
|
627
620
|
function setError(id, error) {
|
|
@@ -638,15 +631,13 @@ function setError(id, error) {
|
|
|
638
631
|
return true;
|
|
639
632
|
}
|
|
640
633
|
function deleteModule(id) {
|
|
641
|
-
var _a;
|
|
642
|
-
var _b;
|
|
643
634
|
const { cache } = require;
|
|
644
635
|
// build reversed map of dependencies
|
|
645
636
|
const requiredBy = { '.': ['.'] }; // don't touch main entry
|
|
646
637
|
for (const k in cache)
|
|
647
638
|
if (k !== id)
|
|
648
|
-
for (const child of (0, misc_1.wantArray)(
|
|
649
|
-
(requiredBy[
|
|
639
|
+
for (const child of (0, misc_1.wantArray)(cache[k]?.children))
|
|
640
|
+
(requiredBy[child.id] ||= []).push(k);
|
|
650
641
|
const deleted = [];
|
|
651
642
|
(function deleteCache(id) {
|
|
652
643
|
const mod = cache[id];
|
|
@@ -661,17 +652,16 @@ function deleteModule(id) {
|
|
|
661
652
|
}
|
|
662
653
|
(0, first_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
|
|
663
654
|
function parsePluginSource(id, source) {
|
|
664
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
665
655
|
const pl = { id };
|
|
666
|
-
pl.description = (0, misc_1.tryJson)(
|
|
667
|
-
pl.repo = (0, misc_1.tryJson)(
|
|
668
|
-
pl.version =
|
|
669
|
-
pl.apiRequired = (
|
|
670
|
-
pl.isTheme = (
|
|
671
|
-
pl.preview = (
|
|
672
|
-
pl.depend = (
|
|
656
|
+
pl.description = (0, misc_1.tryJson)(/exports.description\s*=\s*(".*")/.exec(source)?.[1]);
|
|
657
|
+
pl.repo = (0, misc_1.tryJson)(/exports.repo\s*=\s*(\S*)/.exec(source)?.[1]);
|
|
658
|
+
pl.version = Number(/exports.version\s*=\s*(\d*\.?\d+)/.exec(source)?.[1]) ?? undefined;
|
|
659
|
+
pl.apiRequired = (0, misc_1.tryJson)(/exports.apiRequired\s*=\s*([ \d.,[\]]+)/.exec(source)?.[1]) ?? undefined;
|
|
660
|
+
pl.isTheme = (0, misc_1.tryJson)(/exports.isTheme\s*=\s*(true|false|"light"|"dark")/.exec(source)?.[1]) ?? (id.endsWith('-theme') || undefined);
|
|
661
|
+
pl.preview = (0, misc_1.tryJson)(/exports.preview\s*=\s*(.+)/.exec(source)?.[1]) ?? undefined;
|
|
662
|
+
pl.depend = (0, misc_1.tryJson)(/exports.depend\s*=\s*(\[[\s\S]*?])/m.exec(source)?.[1])?.filter((x) => typeof x.repo === 'string' && x.version === undefined || typeof x.version === 'number'
|
|
673
663
|
|| console.warn("plugin dependency discarded", x));
|
|
674
|
-
pl.changelog = (0, misc_1.tryJson)(
|
|
664
|
+
pl.changelog = (0, misc_1.tryJson)(/exports.changelog\s*=\s*(\[[\s\S]*?])/m.exec(source)?.[1]);
|
|
675
665
|
if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(lodash_1.default.isFinite))) // validate [from,to] form
|
|
676
666
|
pl.apiRequired = undefined;
|
|
677
667
|
calculateBadApi(pl);
|
|
@@ -685,13 +675,13 @@ function calculateBadApi(data) {
|
|
|
685
675
|
: undefined;
|
|
686
676
|
}
|
|
687
677
|
function getMissingDependencies(plugin) {
|
|
688
|
-
return (0, misc_1.onlyTruthy)((
|
|
678
|
+
return (0, misc_1.onlyTruthy)((plugin?.depend || []).map((dep) => {
|
|
689
679
|
const res = findPluginByRepo(dep.repo);
|
|
690
680
|
const error = !res ? 'missing'
|
|
691
681
|
: (res.version || 0) < dep.version ? 'version'
|
|
692
682
|
: !isPluginEnabled(res.id) ? 'disabled'
|
|
693
683
|
: !isPluginRunning(res.id) ? 'stopped'
|
|
694
684
|
: '';
|
|
695
|
-
return error && { repo: dep.repo, error, id: res
|
|
685
|
+
return error && { repo: dep.repo, error, id: res?.id };
|
|
696
686
|
}));
|
|
697
687
|
}
|
package/src/roots.js
CHANGED
|
@@ -17,7 +17,6 @@ exports.roots = (0, config_1.defineConfig)(misc_1.CFG.roots, {}, map => {
|
|
|
17
17
|
});
|
|
18
18
|
const forceAddress = (0, config_1.defineConfig)(misc_1.CFG.force_address, false);
|
|
19
19
|
const rootsMiddleware = (ctx, next) => (() => {
|
|
20
|
-
var _a;
|
|
21
20
|
if (!ctx.path) // it was once reported "Cannot read properties of null (reading 'startsWith')" but I can't reproduce it, and it shouldn't happen anyway
|
|
22
21
|
return (0, connections_1.disconnect)(ctx, 'no path'); // let's assume that such requests can't be served anyway. This will leave a trace with an ip, anyway
|
|
23
22
|
ctx.state.originalPath = ctx.path;
|
|
@@ -33,7 +32,7 @@ const rootsMiddleware = (ctx, next) => (() => {
|
|
|
33
32
|
}
|
|
34
33
|
if (lodash_1.default.isEmpty(exports.roots.get()))
|
|
35
34
|
return;
|
|
36
|
-
const root = ctx.state.root =
|
|
35
|
+
const root = ctx.state.root = exports.roots.compiled()?.(ctx.host);
|
|
37
36
|
if (!ctx.state.skipFilters && forceAddress.get()
|
|
38
37
|
&& root === undefined && !(0, misc_1.isLocalHost)(ctx) && ctx.host !== listen_1.baseUrl.compiled())
|
|
39
38
|
return (0, connections_1.disconnect)(ctx, forceAddress.key()); // returning truthy will not call next
|
package/src/selfCheck.js
CHANGED
|
@@ -21,7 +21,6 @@ const selfCheckMiddleware = (ctx, next) => {
|
|
|
21
21
|
};
|
|
22
22
|
exports.selfCheckMiddleware = selfCheckMiddleware;
|
|
23
23
|
async function selfCheck(url) {
|
|
24
|
-
var _a;
|
|
25
24
|
const prjInfo = await (0, github_1.getProjectInfo)();
|
|
26
25
|
console.log(`checking server ${url}`);
|
|
27
26
|
const parsed = new URL(url);
|
|
@@ -39,7 +38,7 @@ async function selfCheck(url) {
|
|
|
39
38
|
console.debug(svc);
|
|
40
39
|
body = applySymbols(body);
|
|
41
40
|
serviceUrl = applySymbols(serviceUrl);
|
|
42
|
-
const res = await (0, cross_1.haveTimeout)(
|
|
41
|
+
const res = await (0, cross_1.haveTimeout)(6_000, (0, util_http_1.httpString)(serviceUrl, { family, ...rest, body }));
|
|
43
42
|
const success = new RegExp(regexpSuccess).test(res);
|
|
44
43
|
const failure = new RegExp(regexpFailure).test(res);
|
|
45
44
|
if (success === failure)
|
|
@@ -49,7 +48,7 @@ async function selfCheck(url) {
|
|
|
49
48
|
}));
|
|
50
49
|
}
|
|
51
50
|
catch (e) {
|
|
52
|
-
console.debug(
|
|
51
|
+
console.debug(e?.errors?.map(String) || e?.cause || String(e));
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -57,6 +56,8 @@ async function selfCheck(url) {
|
|
|
57
56
|
selfChecking = false;
|
|
58
57
|
}
|
|
59
58
|
function applySymbols(s) {
|
|
60
|
-
return s
|
|
59
|
+
return s?.replace('$IP', parsed.hostname)
|
|
60
|
+
.replace('$PORT', parsed.port || (parsed.protocol === 'https:' ? '443' : '80'))
|
|
61
|
+
.replace('$URL', url.replace(/\/$/, '') + CHECK_URL);
|
|
61
62
|
}
|
|
62
63
|
}
|
package/src/serveFile.js
CHANGED
|
@@ -41,7 +41,6 @@ function disposition(ctx, name, forceDownload = false) {
|
|
|
41
41
|
+ `filename="${toAsciiEquivalent(name)}"; filename*=UTF-8''${encodeURIComponent(name)}`);
|
|
42
42
|
}
|
|
43
43
|
async function serveFileNode(ctx, node) {
|
|
44
|
-
var _a;
|
|
45
44
|
const { source, mime } = node;
|
|
46
45
|
const name = (0, vfs_1.getNodeName)(node);
|
|
47
46
|
const mimeString = typeof mime === 'string' ? mime
|
|
@@ -55,7 +54,7 @@ async function serveFileNode(ctx, node) {
|
|
|
55
54
|
ctx.state.vfsNode = node; // useful to tell service files from files shared by the user
|
|
56
55
|
const download = 'dl' in ctx.query;
|
|
57
56
|
disposition(ctx, name, download);
|
|
58
|
-
if (!download &&
|
|
57
|
+
if (!download && ctx.get('referer')?.endsWith('/') && (0, misc_1.with_)(ctx.get('accept'), x => x && !x.includes('text')))
|
|
59
58
|
ctx.state.considerAsGui = true;
|
|
60
59
|
await serveFile(ctx, source || '', mimeString);
|
|
61
60
|
if (await maxDownloadsPerAccount(ctx) === undefined) // returning false will not execute other limits
|
|
@@ -71,7 +70,7 @@ const cacheControlDiskFiles = (0, config_1.defineConfig)('cache_control_disk_fil
|
|
|
71
70
|
async function serveFile(ctx, source, mime, content) {
|
|
72
71
|
if (!source)
|
|
73
72
|
return;
|
|
74
|
-
mime
|
|
73
|
+
mime ??= mimeCfg.compiled()((0, path_1.basename)(source));
|
|
75
74
|
if (mime === undefined || mime === const_1.MIME_AUTO)
|
|
76
75
|
mime = mime_types_1.default.lookup(source) || '';
|
|
77
76
|
if (mime)
|
|
@@ -104,7 +103,7 @@ async function serveFile(ctx, source, mime, content) {
|
|
|
104
103
|
const range = applyRange(ctx, size);
|
|
105
104
|
ctx.body = (0, fs_1.createReadStream)(source, range);
|
|
106
105
|
if (ctx.state.vfsNode)
|
|
107
|
-
monitorAsDownload(ctx, size, range
|
|
106
|
+
monitorAsDownload(ctx, size, range?.start);
|
|
108
107
|
}
|
|
109
108
|
catch (e) {
|
|
110
109
|
return ctx.status = const_1.HTTP_NOT_FOUND;
|
|
@@ -133,10 +132,10 @@ function applyRange(ctx, totalSize = ctx.response.length) {
|
|
|
133
132
|
const [unit, ranges] = range.split('=');
|
|
134
133
|
if (unit !== 'bytes')
|
|
135
134
|
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range unit');
|
|
136
|
-
if (ranges
|
|
135
|
+
if (ranges?.includes(','))
|
|
137
136
|
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'multi-range not supported');
|
|
138
|
-
let bytes = ranges
|
|
139
|
-
if (!
|
|
137
|
+
let bytes = ranges?.split('-');
|
|
138
|
+
if (!bytes?.length)
|
|
140
139
|
return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range');
|
|
141
140
|
const max = totalSize - 1;
|
|
142
141
|
const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize - Number(bytes[1])); // a negative start is relative to the end
|
|
@@ -33,12 +33,11 @@ const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_UR
|
|
|
33
33
|
const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
|
|
34
34
|
const serveAdminPrefixed = (0, koa_mount_1.default)(cross_const_1.ADMIN_URI.slice(0, -1), serveAdminFiles);
|
|
35
35
|
const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
36
|
-
var _a, _b;
|
|
37
36
|
const { path } = ctx;
|
|
38
37
|
// dynamic import on frontend|admin (used for non-https login) while developing (vite4) is not producing a relative path
|
|
39
38
|
if (const_1.DEV && path.startsWith('/node_modules/')) {
|
|
40
39
|
const { referer: r } = ctx.headers;
|
|
41
|
-
return (0, misc_1.try_)(() =>
|
|
40
|
+
return (0, misc_1.try_)(() => r && new URL(r).pathname?.startsWith(cross_const_1.ADMIN_URI)) ? serveAdminFiles(ctx, next)
|
|
42
41
|
: serveFrontendFiles(ctx, next);
|
|
43
42
|
}
|
|
44
43
|
if (path.startsWith(cross_const_1.FRONTEND_URI))
|
|
@@ -53,11 +52,11 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
53
52
|
if (!iconName)
|
|
54
53
|
return;
|
|
55
54
|
const plugin = a.length > 1 && (0, plugins_1.getPluginInfo)(a[0]); // an extra level in the path indicates a plugin
|
|
56
|
-
const file = plugin ?
|
|
55
|
+
const file = plugin ? plugin.icons?.[iconName] : icons_1.customizedIcons?.[iconName];
|
|
57
56
|
if (!file)
|
|
58
57
|
return;
|
|
59
58
|
ctx.state.considerAsGui = true;
|
|
60
|
-
return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)(
|
|
59
|
+
return (0, serveFile_1.serveFile)(ctx, (0, path_1.join)(plugin?.folder || '', icons_1.ICONS_FOLDER, file), const_1.MIME_AUTO);
|
|
61
60
|
}
|
|
62
61
|
const { get } = ctx.query;
|
|
63
62
|
const getUploadTempHash = get === cross_const_1.UPLOAD_TEMP_HASH;
|
|
@@ -71,7 +70,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
71
70
|
ctx.state.uploadPath = decPath;
|
|
72
71
|
if (getUploadTempHash)
|
|
73
72
|
return !folder.source ? (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_NOT_FOUND)
|
|
74
|
-
: ctx.body = await (0, misc_1.
|
|
73
|
+
: ctx.body = await (0, misc_1.loadFileCached)((0, upload_1.getUploadTempFor)((0, path_1.join)(folder.source, rest)), calcHash); // negligible memory leak
|
|
75
74
|
const dest = (0, upload_1.uploadWriter)(folder, folderUri, rest, ctx);
|
|
76
75
|
if (dest) {
|
|
77
76
|
ctx.req.pipe(dest).on('error', err => {
|
|
@@ -126,7 +125,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
126
125
|
if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_delete', ctx))
|
|
127
126
|
return;
|
|
128
127
|
try {
|
|
129
|
-
if ((
|
|
128
|
+
if ((await events_1.default.emitAsync('deleting', { node, ctx }))?.isDefaultPrevented())
|
|
130
129
|
return ctx.status = cross_const_1.HTTP_FAILED_DEPENDENCY;
|
|
131
130
|
await (0, promises_1.rm)(source, { recursive: true });
|
|
132
131
|
void (0, comments_1.setCommentFor)(source, ''); // necessary only to clean a possible descript.ion or kvstorage
|
|
@@ -141,7 +140,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
141
140
|
const found = await (0, vfs_1.urlToNode)(node.default, ctx, node);
|
|
142
141
|
if (found && /\.html?/i.test(node.default))
|
|
143
142
|
ctx.state.considerAsGui = true;
|
|
144
|
-
node = found
|
|
143
|
+
node = found ?? node;
|
|
145
144
|
}
|
|
146
145
|
if (get === 'icon')
|
|
147
146
|
return (0, serveFile_1.serveFile)(ctx, node.icon || '|'); // pipe to cause not-found
|
|
@@ -172,8 +171,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
|
|
|
172
171
|
};
|
|
173
172
|
exports.serveGuiAndSharedFiles = serveGuiAndSharedFiles;
|
|
174
173
|
async function sendFolderList(node, ctx) {
|
|
175
|
-
|
|
176
|
-
if ((_a = (await events_1.default.emitAsync('getList', { node, ctx }))) === null || _a === void 0 ? void 0 : _a.isDefaultPrevented())
|
|
174
|
+
if ((await events_1.default.emitAsync('getList', { node, ctx }))?.isDefaultPrevented())
|
|
177
175
|
return;
|
|
178
176
|
let { depth = 0, folders, prepend } = ctx.query;
|
|
179
177
|
ctx.type = 'text';
|