hfs 0.57.26 → 0.57.27-beta2
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-DSkhXlWX.js} +103 -103
- package/admin/assets/{sha512-Dsv49IAs.js → sha512-CoddZKeD.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/{index-legacy-BI8icwFp.js → index-legacy-C4Gclsbz.js} +4 -4
- package/frontend/assets/{sha512-legacy-D82alHBq.js → sha512-legacy-C-FMk44u.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 +13 -8
- 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 +62 -73
- 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 +11 -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 +80 -29
- package/src/util-os.js +2 -4
- package/src/vfs.js +77 -68
- package/src/walkDir.js +9 -9
- package/src/watchLoad.js +4 -4
- package/src/zip.js +5 -6
package/src/api.net.js
CHANGED
|
@@ -15,7 +15,7 @@ const net_1 = require("net");
|
|
|
15
15
|
const nat_1 = require("./nat");
|
|
16
16
|
const acme_1 = require("./acme");
|
|
17
17
|
const selfCheck_1 = require("./selfCheck");
|
|
18
|
-
|
|
18
|
+
exports.default = {
|
|
19
19
|
get_nat: nat_1.getNatInfo,
|
|
20
20
|
get_public_ips: nat_1.getPublicIps,
|
|
21
21
|
async check_domain({ domain }) {
|
|
@@ -88,4 +88,3 @@ const apis = {
|
|
|
88
88
|
return (0, listen_1.getCertObject)() || { none: true };
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
|
-
exports.default = apis;
|
package/src/api.plugins.js
CHANGED
|
@@ -33,15 +33,14 @@ const apis = {
|
|
|
33
33
|
const errs = {};
|
|
34
34
|
list.events(ctx, {
|
|
35
35
|
pluginDownload({ repo, status }) {
|
|
36
|
-
|
|
37
|
-
list.update({ id: (_a = (0, plugins_1.findPluginByRepo)(repo)) === null || _a === void 0 ? void 0 : _a.id }, { downloading: status !== null && status !== void 0 ? status : null });
|
|
36
|
+
list.update({ id: (0, plugins_1.findPluginByRepo)(repo)?.id }, { downloading: status ?? null });
|
|
38
37
|
},
|
|
39
38
|
pluginDownloaded({ id }) {
|
|
40
39
|
list.update({ id }, { updated: true });
|
|
41
40
|
}
|
|
42
41
|
});
|
|
42
|
+
await plugins_1.pluginsScanned;
|
|
43
43
|
await Promise.allSettled(lodash_1.default.map((0, github_1.getFolder2repo)(), async (repo, folder) => {
|
|
44
|
-
var _a;
|
|
45
44
|
try {
|
|
46
45
|
if (!repo)
|
|
47
46
|
return;
|
|
@@ -63,7 +62,7 @@ const apis = {
|
|
|
63
62
|
}
|
|
64
63
|
catch (err) {
|
|
65
64
|
if (err.message !== '404') // the plugin is declaring a wrong repo
|
|
66
|
-
(errs[
|
|
65
|
+
(errs[err.code || err.message] ||= []).push(repo);
|
|
67
66
|
}
|
|
68
67
|
}));
|
|
69
68
|
if (!lodash_1.default.isEmpty(errs))
|
|
@@ -98,7 +97,7 @@ const apis = {
|
|
|
98
97
|
return {
|
|
99
98
|
enabled: plugins_1.enablePlugins.get().includes(id),
|
|
100
99
|
config: {
|
|
101
|
-
...(0, misc_1.newObj)((0, plugins_1.getPluginConfigFields)(id), v => v
|
|
100
|
+
...(0, misc_1.newObj)((0, plugins_1.getPluginConfigFields)(id), v => v?.defaultValue),
|
|
102
101
|
...plugins_1.pluginsConfig.get()[id]
|
|
103
102
|
}
|
|
104
103
|
};
|
|
@@ -121,7 +120,7 @@ const apis = {
|
|
|
121
120
|
},
|
|
122
121
|
pluginDownload({ repo, status }) {
|
|
123
122
|
if (repos.includes(repo))
|
|
124
|
-
list.update({ id: repo }, { downloading: status
|
|
123
|
+
list.update({ id: repo }, { downloading: status ?? null });
|
|
125
124
|
}
|
|
126
125
|
});
|
|
127
126
|
try {
|
|
@@ -180,14 +179,13 @@ const apis = {
|
|
|
180
179
|
};
|
|
181
180
|
exports.default = apis;
|
|
182
181
|
function serialize(p) {
|
|
183
|
-
var _a;
|
|
184
182
|
let o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
|
|
185
183
|
: { ...p }; // _.defaults mutates object, and we don't want that
|
|
186
184
|
if (typeof o.repo === 'object') // custom repo
|
|
187
185
|
o.repo = o.repo.web;
|
|
188
|
-
o.log = 'log' in p &&
|
|
189
|
-
o.config
|
|
190
|
-
: JSON.stringify(o.config, (_k, v) => lodash_1.default.isFunction(v) ? String(v) : v)
|
|
186
|
+
o.log = 'log' in p && p.log?.length > 0;
|
|
187
|
+
o.config &&= lodash_1.default.isFunction(o.config) ? String(o.config)
|
|
188
|
+
: JSON.stringify(o.config, (_k, v) => lodash_1.default.isFunction(v) ? String(v) : v); // allow simple functions
|
|
191
189
|
return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
|
|
192
190
|
}
|
|
193
191
|
async function checkDependencies(plugin) {
|
package/src/api.vfs.js
CHANGED
|
@@ -20,21 +20,20 @@ const walkDir_1 = require("./walkDir");
|
|
|
20
20
|
// to manipulate the tree we need the original node
|
|
21
21
|
async function urlToNodeOriginal(uri) {
|
|
22
22
|
const n = await (0, vfs_1.urlToNode)(uri);
|
|
23
|
-
return
|
|
23
|
+
return n?.isTemp ? n.original : n;
|
|
24
24
|
}
|
|
25
25
|
const ALLOWED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename', 'mime', 'url',
|
|
26
26
|
'target', 'comment', 'icon', 'order', ...misc_1.PERM_KEYS];
|
|
27
|
-
|
|
27
|
+
exports.default = {
|
|
28
28
|
async get_vfs() {
|
|
29
29
|
return { root: await recur() };
|
|
30
30
|
async function recur(node = vfs_1.vfs) {
|
|
31
|
-
var _a, _b, _c;
|
|
32
31
|
const { source } = node;
|
|
33
|
-
const stats =
|
|
34
|
-
const
|
|
32
|
+
const stats = await (0, vfs_1.nodeStats)(node);
|
|
33
|
+
const isFolder = (0, vfs_1.nodeIsFolder)(node);
|
|
35
34
|
const copyStats = stats ? lodash_1.default.pick(stats, ['size', 'birthtime', 'mtime'])
|
|
36
35
|
: { size: source ? -1 : undefined };
|
|
37
|
-
if (copyStats.mtime && (
|
|
36
|
+
if (copyStats.mtime && (stats?.mtimeMs - stats?.birthtimeMs) < 1000)
|
|
38
37
|
delete copyStats.mtime;
|
|
39
38
|
const inherited = node.parent && (0, vfs_1.permsFromParent)(node.parent, {});
|
|
40
39
|
const byMasks = node.original && lodash_1.default.pickBy(node, (v, k) => v !== node.original[k] // something is changing me...
|
|
@@ -45,17 +44,16 @@ const apis = {
|
|
|
45
44
|
...node.original || node,
|
|
46
45
|
inherited,
|
|
47
46
|
byMasks: lodash_1.default.isEmpty(byMasks) ? undefined : byMasks,
|
|
48
|
-
website: Boolean(
|
|
49
|
-
||
|
|
47
|
+
website: Boolean(node.children?.find((0, vfs_1.isSameFilenameAs)('index.html')))
|
|
48
|
+
|| isFolder && source && await (0, misc_1.statWithTimeout)((0, path_1.join)(source, 'index.html')).then(() => true, () => undefined)
|
|
50
49
|
|| undefined,
|
|
51
50
|
name: (0, vfs_1.getNodeName)(node),
|
|
52
|
-
type:
|
|
51
|
+
type: isFolder ? 'folder' : undefined,
|
|
53
52
|
children: node.children && await Promise.all(node.children.map(async (child) => recur(await (0, vfs_1.applyParentToChild)(child, node))))
|
|
54
53
|
};
|
|
55
54
|
}
|
|
56
55
|
},
|
|
57
56
|
async move_vfs({ from, parent }) {
|
|
58
|
-
var _a;
|
|
59
57
|
if (!from || !parent)
|
|
60
58
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
|
|
61
59
|
const fromNode = await urlToNodeOriginal(from);
|
|
@@ -69,18 +67,17 @@ const apis = {
|
|
|
69
67
|
if (!parentNode)
|
|
70
68
|
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'parent not found');
|
|
71
69
|
const name = (0, vfs_1.getNodeName)(fromNode);
|
|
72
|
-
if (
|
|
70
|
+
if (parentNode.children?.find(x => name === (0, vfs_1.getNodeName)(x)))
|
|
73
71
|
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'item with same name already present in destination');
|
|
74
72
|
const oldParent = await urlToNodeOriginal((0, path_1.dirname)(from));
|
|
75
73
|
lodash_1.default.pull(oldParent.children, fromNode);
|
|
76
74
|
if (lodash_1.default.isEmpty(oldParent.children))
|
|
77
75
|
delete oldParent.children;
|
|
78
|
-
(parentNode.children
|
|
76
|
+
(parentNode.children ||= []).push(fromNode);
|
|
79
77
|
await (0, vfs_1.saveVfs)();
|
|
80
78
|
return {};
|
|
81
79
|
},
|
|
82
80
|
async set_vfs({ uri, props }) {
|
|
83
|
-
var _a;
|
|
84
81
|
const n = await urlToNodeOriginal(uri);
|
|
85
82
|
if (!n)
|
|
86
83
|
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
|
|
@@ -88,7 +85,7 @@ const apis = {
|
|
|
88
85
|
if (!(0, misc_1.isValidFileName)(props.name))
|
|
89
86
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
|
|
90
87
|
const parent = await urlToNodeOriginal((0, path_1.dirname)(uri));
|
|
91
|
-
if (
|
|
88
|
+
if (parent?.children?.find(x => (0, vfs_1.getNodeName)(x) === props.name))
|
|
92
89
|
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'name already present');
|
|
93
90
|
}
|
|
94
91
|
if (props.masks && typeof props.masks !== 'object')
|
|
@@ -100,7 +97,6 @@ const apis = {
|
|
|
100
97
|
return n;
|
|
101
98
|
},
|
|
102
99
|
async add_vfs({ parent, source, name, ...rest }) {
|
|
103
|
-
var _a;
|
|
104
100
|
if (!source && !name)
|
|
105
101
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'name or source required');
|
|
106
102
|
if (!(0, misc_1.isValidFileName)(name))
|
|
@@ -120,11 +116,11 @@ const apis = {
|
|
|
120
116
|
const ext = (0, path_1.extname)(name);
|
|
121
117
|
const noExt = ext ? name.slice(0, -ext.length) : name;
|
|
122
118
|
let idx = 2;
|
|
123
|
-
while (
|
|
119
|
+
while (parentNode.children?.find((0, vfs_1.isSameFilenameAs)(name)))
|
|
124
120
|
name = `${noExt} ${idx++}${ext}`;
|
|
125
121
|
child.name = name;
|
|
126
122
|
simplifyName(child);
|
|
127
|
-
(parentNode.children
|
|
123
|
+
(parentNode.children ||= []).unshift(child);
|
|
128
124
|
await (0, vfs_1.saveVfs)();
|
|
129
125
|
const link = rest.url ? undefined : await (0, listen_1.getBaseUrlOrDefault)()
|
|
130
126
|
+ (parent ? (0, misc_1.enforceStarting)('/', (0, misc_1.enforceFinal)('/', parent)) : '/')
|
|
@@ -207,7 +203,7 @@ const apis = {
|
|
|
207
203
|
k: isDir ? 'd' : undefined,
|
|
208
204
|
});
|
|
209
205
|
}
|
|
210
|
-
catch
|
|
206
|
+
catch { } // just ignore entries we can't stat
|
|
211
207
|
});
|
|
212
208
|
await sendPropsAsap.catch(() => { });
|
|
213
209
|
list.close();
|
|
@@ -251,7 +247,6 @@ const apis = {
|
|
|
251
247
|
return {};
|
|
252
248
|
},
|
|
253
249
|
};
|
|
254
|
-
exports.default = apis;
|
|
255
250
|
// pick only selected props, and consider null and empty string as undefined, as it's the default value and we don't want to store it
|
|
256
251
|
function pickProps(o, keys) {
|
|
257
252
|
const ret = {};
|
package/src/apiMiddleware.js
CHANGED
|
@@ -13,6 +13,7 @@ const const_1 = require("./const");
|
|
|
13
13
|
const config_1 = require("./config");
|
|
14
14
|
const plugins_1 = require("./plugins");
|
|
15
15
|
class ApiError extends Error {
|
|
16
|
+
status;
|
|
16
17
|
constructor(status, message) {
|
|
17
18
|
super(typeof message === 'string' ? message : message && message instanceof Error ? message.message : JSON.stringify(message));
|
|
18
19
|
this.status = status;
|
|
@@ -22,20 +23,19 @@ exports.ApiError = ApiError;
|
|
|
22
23
|
const logApi = (0, config_1.defineConfig)(misc_1.CFG.log_api, true);
|
|
23
24
|
function apiMiddleware(apis) {
|
|
24
25
|
return async (ctx) => {
|
|
25
|
-
var _a;
|
|
26
26
|
if (!logApi.get())
|
|
27
27
|
ctx.state.dontLog = true;
|
|
28
28
|
const isPost = ctx.state.params;
|
|
29
29
|
const params = isPost ? ctx.state.params || {} : ctx.query;
|
|
30
30
|
const apiName = ctx.path;
|
|
31
31
|
console.debug('API', ctx.method, apiName, { ...params });
|
|
32
|
-
const noBrowser =
|
|
32
|
+
const noBrowser = ctx.get('user-agent')?.startsWith('curl');
|
|
33
33
|
const safe = noBrowser || isPost && ctx.get('x-hfs-anti-csrf') // POST is safe because browser will enforce SameSite cookie
|
|
34
34
|
|| apiName.startsWith('get_'); // "get_" apis are safe because they make no change
|
|
35
35
|
if (!safe)
|
|
36
36
|
return send(const_1.HTTP_FOOL, "missing header x-hfs-anti-csrf=1");
|
|
37
37
|
const customApiRest = apiName.startsWith(const_1.PLUGIN_CUSTOM_REST_PREFIX) && apiName.slice(const_1.PLUGIN_CUSTOM_REST_PREFIX.length);
|
|
38
|
-
const apiFun = customApiRest && (0, plugins_1.firstPlugin)(pl =>
|
|
38
|
+
const apiFun = customApiRest && (0, plugins_1.firstPlugin)(pl => pl.getData().customRest?.[customApiRest])
|
|
39
39
|
|| apis.hasOwnProperty(apiName) && apis[apiName];
|
|
40
40
|
if (!apiFun)
|
|
41
41
|
return send(const_1.HTTP_BAD_REQUEST, 'invalid api');
|
|
@@ -74,5 +74,5 @@ function apiMiddleware(apis) {
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
function isAsyncGenerator(x) {
|
|
77
|
-
return typeof
|
|
77
|
+
return typeof x?.next === 'function';
|
|
78
78
|
}
|
package/src/auth.js
CHANGED
|
@@ -28,10 +28,10 @@ async function srpServerStep1(account) {
|
|
|
28
28
|
const srpServer = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier));
|
|
29
29
|
return { srpServer, salt, pubKey: String(srpServer.B) }; // cast to string cause bigint can't be jsonized
|
|
30
30
|
}
|
|
31
|
-
const cache = (0, expiringCache_1.expiringCache)(
|
|
31
|
+
const cache = (0, expiringCache_1.expiringCache)(60_000);
|
|
32
32
|
async function srpCheck(username, password) {
|
|
33
33
|
const account = (0, perm_1.getAccount)(username);
|
|
34
|
-
if (!
|
|
34
|
+
if (!account?.srp || !password)
|
|
35
35
|
return;
|
|
36
36
|
const k = (0, node_crypto_1.createHash)('sha256').update(username + password + account.srp).digest("hex");
|
|
37
37
|
const good = await cache.try(k, async () => {
|
|
@@ -42,17 +42,15 @@ async function srpCheck(username, password) {
|
|
|
42
42
|
return good ? account : undefined;
|
|
43
43
|
}
|
|
44
44
|
function getCurrentUsername(ctx) {
|
|
45
|
-
|
|
46
|
-
return ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.username) || '';
|
|
45
|
+
return ctx.state.account?.username || '';
|
|
47
46
|
}
|
|
48
47
|
async function clearTextLogin(ctx, u, p, via) {
|
|
49
|
-
var _a;
|
|
50
48
|
if (!p)
|
|
51
49
|
return;
|
|
52
|
-
if ((
|
|
50
|
+
if ((await events_1.default.emitAsync('attemptingLogin', { ctx, username: u, via }))?.isDefaultPrevented())
|
|
53
51
|
return;
|
|
54
52
|
const plugins = await events_1.default.emitAsync('clearTextLogin', { ctx, username: u, password: p, via }); // provide clear password to plugins
|
|
55
|
-
const a =
|
|
53
|
+
const a = plugins?.some(x => x === true) ? (0, perm_1.getAccount)(u) : await srpCheck(u, p);
|
|
56
54
|
if (a) {
|
|
57
55
|
await setLoggedIn(ctx, a.username);
|
|
58
56
|
ctx.headers['x-username'] = a.username; // give an easier way to determine if the login was successful
|
|
@@ -63,7 +61,6 @@ async function clearTextLogin(ctx, u, p, via) {
|
|
|
63
61
|
}
|
|
64
62
|
// centralized log-in state
|
|
65
63
|
async function setLoggedIn(ctx, username) {
|
|
66
|
-
var _a;
|
|
67
64
|
const s = ctx.session;
|
|
68
65
|
if (!s)
|
|
69
66
|
return ctx.throw(cross_const_1.HTTP_SERVER_ERROR, 'session');
|
|
@@ -80,7 +77,7 @@ async function setLoggedIn(ctx, username) {
|
|
|
80
77
|
s.username = (0, perm_1.normalizeUsername)(username);
|
|
81
78
|
s.ts = Date.now();
|
|
82
79
|
const k = cross_const_1.ALLOW_SESSION_IP_CHANGE;
|
|
83
|
-
s[k] = k in ctx.query || Boolean(
|
|
80
|
+
s[k] = k in ctx.query || Boolean(ctx.state.params?.[k]) || undefined; // login APIs will get ctx.state.params, others can rely on ctx.query
|
|
84
81
|
if (!a.expire && a.days_to_live)
|
|
85
82
|
(0, perm_1.updateAccount)(a, { expire: new Date(Date.now() + a.days_to_live * cross_1.DAY) });
|
|
86
83
|
await events_1.default.emitAsync('login', ctx);
|
package/src/basicWeb.js
CHANGED
|
@@ -60,8 +60,7 @@ function basicWeb(ctx, node) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
function detectBasicAgent(ctx) {
|
|
63
|
-
var _a;
|
|
64
63
|
const ua = ctx.get('user-agent');
|
|
65
64
|
return autoBasic.get() && /^$|Mozilla\/4|WebKit\/([234]\d\d|5[012]\d|53[0123456])[. ]|Trident|Lynx|curl|Firefox\/(\d|[1234]\d)\./.test(ua)
|
|
66
|
-
||
|
|
65
|
+
|| autoBasic.compiled()?.test(ua);
|
|
67
66
|
}
|
package/src/block.js
CHANGED
|
@@ -17,7 +17,7 @@ exports.block = (0, config_1.defineConfig)('block', [], rules => {
|
|
|
17
17
|
const now = new Date();
|
|
18
18
|
const ret = !Array.isArray(rules) ? []
|
|
19
19
|
: (0, misc_1.onlyTruthy)(rules.map(rule => {
|
|
20
|
-
rule.expire
|
|
20
|
+
rule.expire &&= new Date(rule.expire);
|
|
21
21
|
return !rule.disabled && (rule.expire || now) >= now && (0, misc_1.makeNetMatcher)(rule.ip);
|
|
22
22
|
}));
|
|
23
23
|
// reapply new block to existing connections
|
package/src/commands.js
CHANGED
|
@@ -39,15 +39,15 @@ if (!argv_1.argv.updating && !config_1.showHelp) {
|
|
|
39
39
|
function clean() {
|
|
40
40
|
if (isClean)
|
|
41
41
|
return;
|
|
42
|
-
return cleaning
|
|
42
|
+
return cleaning ||= new Promise(resolve => {
|
|
43
43
|
(0, node_readline_1.cursorTo)(process.stdout, 0, undefined, () => {
|
|
44
44
|
resolve();
|
|
45
45
|
cleaning = undefined;
|
|
46
46
|
isClean = true;
|
|
47
47
|
});
|
|
48
|
-
})
|
|
48
|
+
});
|
|
49
49
|
}
|
|
50
|
-
showPrompt
|
|
50
|
+
showPrompt?.();
|
|
51
51
|
// print this hint when we have not been printing anything else for a while, to not get mixed too much
|
|
52
52
|
let printHintOnce = tty && lodash_1.default.debounce(() => {
|
|
53
53
|
(0, consoleLog_1.consoleHint)("this is an interactive console, you can enter commands");
|
|
@@ -63,13 +63,13 @@ if (!argv_1.argv.updating && !config_1.showHelp) {
|
|
|
63
63
|
v(...args);
|
|
64
64
|
}
|
|
65
65
|
finally {
|
|
66
|
-
showPrompt
|
|
67
|
-
printHintOnce
|
|
66
|
+
showPrompt?.();
|
|
67
|
+
printHintOnce?.();
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
|
-
catch
|
|
72
|
+
catch {
|
|
73
73
|
console.log("console commands not available");
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -79,7 +79,7 @@ async function parseCommandLine(line) {
|
|
|
79
79
|
let [name, ...params] = line.trim().split(/ +/);
|
|
80
80
|
name = aliases[name] || name;
|
|
81
81
|
let cmd = commands[name];
|
|
82
|
-
if (cmd
|
|
82
|
+
if (cmd?.alias)
|
|
83
83
|
cmd = commands[cmd.alias];
|
|
84
84
|
if (!cmd)
|
|
85
85
|
return console.error("invalid command, try 'help'");
|
|
@@ -90,7 +90,7 @@ async function parseCommandLine(line) {
|
|
|
90
90
|
console.log("+++ command executed");
|
|
91
91
|
}
|
|
92
92
|
catch (err) {
|
|
93
|
-
if (typeof err !== 'string' && !
|
|
93
|
+
if (typeof err !== 'string' && !err?.message)
|
|
94
94
|
throw err;
|
|
95
95
|
console.error("command failed:", err.message || err);
|
|
96
96
|
}
|
|
@@ -131,7 +131,7 @@ const commands = {
|
|
|
131
131
|
try {
|
|
132
132
|
v = JSON.parse(v);
|
|
133
133
|
}
|
|
134
|
-
catch
|
|
134
|
+
catch { }
|
|
135
135
|
await (0, config_1.setConfig)({ [key]: v });
|
|
136
136
|
}
|
|
137
137
|
},
|
package/src/comments.js
CHANGED
|
@@ -19,8 +19,7 @@ const promises_1 = require("node:fs/promises");
|
|
|
19
19
|
exports.DESCRIPT_ION = 'descript.ion';
|
|
20
20
|
const commentsStorage = (0, config_1.defineConfig)(cross_1.CFG.comments_storage, '', v => ['', 'attr+ion'].includes(v)); // compiled tell us if we are using descript.ion
|
|
21
21
|
(0, config_1.defineConfig)('descript_ion', true, (v, more) => {
|
|
22
|
-
|
|
23
|
-
if (!v && ((_a = more.version) === null || _a === void 0 ? void 0 : _a.olderThan('0.57.0-alpha1')))
|
|
22
|
+
if (!v && more.version?.olderThan('0.57.0-alpha1'))
|
|
24
23
|
commentsStorage.set('attr');
|
|
25
24
|
});
|
|
26
25
|
const descriptIonEncoding = (0, config_1.defineConfig)('descript_ion_encoding', 'utf8');
|
|
@@ -35,7 +34,7 @@ async function getCommentFor(path) {
|
|
|
35
34
|
return !path ? undefined : Promise.all([
|
|
36
35
|
commentsStorage.get() ? (0, misc_1.loadFileAttr)(path, COMMENT_ATTR) : undefined,
|
|
37
36
|
readFromDescriptIon(path)
|
|
38
|
-
]).then(([fromAttr, fromIon]) => fromAttr || fromIon);
|
|
37
|
+
]).then(([fromAttr, fromIon]) => fromAttr || fromIon || undefined);
|
|
39
38
|
}
|
|
40
39
|
async function setCommentFor(path, comment) {
|
|
41
40
|
if (commentsStorage.get()) {
|
|
@@ -59,7 +58,7 @@ const setCommentDescriptIon = (0, misc_1.singleWorkerFromBatchWorker)(async (job
|
|
|
59
58
|
if (!comments.size)
|
|
60
59
|
return (0, promises_1.unlink)(path);
|
|
61
60
|
// encode comments in descript.ion format
|
|
62
|
-
const ws = await (0, util_files_1.
|
|
61
|
+
const ws = await (0, util_files_1.createSafeWriteStream)(path);
|
|
63
62
|
comments.forEach((comment, filename) => {
|
|
64
63
|
const multiline = comment.includes('\n');
|
|
65
64
|
const line = (filename.includes(' ') ? `"${filename}"` : filename)
|
|
@@ -78,7 +77,7 @@ function areCommentsEnabled() {
|
|
|
78
77
|
const MULTILINE_SUFFIX = Buffer.from([4, 0xC2]);
|
|
79
78
|
function readDescriptIon(path) {
|
|
80
79
|
// decoding could also be done with native TextDecoder.decode, but we need iconv for the encoding anyway
|
|
81
|
-
return (0, util_files_1.
|
|
80
|
+
return (0, util_files_1.parseFile)((0, path_1.join)(path, exports.DESCRIPT_ION), raw => {
|
|
82
81
|
// for simplicity we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
|
|
83
82
|
for (let i = 0; i < raw.length; i++)
|
|
84
83
|
if (raw[i] === MULTILINE_SUFFIX[0] && raw[i + 1] === MULTILINE_SUFFIX[1] && [undefined, 13, 10].includes(raw[i + 2]))
|
package/src/config.js
CHANGED
|
@@ -37,7 +37,7 @@ const filePath = (0, cross_1.with_)(argv_1.argv.config || process.env.HFS_CONFIG
|
|
|
37
37
|
if ((0, fs_1.statSync)(p).isDirectory()) // try to detect if path points to a folder, in which case we add the standard filename
|
|
38
38
|
return (0, path_1.join)(p, const_1.CONFIG_FILE);
|
|
39
39
|
}
|
|
40
|
-
catch
|
|
40
|
+
catch { }
|
|
41
41
|
return p;
|
|
42
42
|
});
|
|
43
43
|
// takes a semver like 1.2.3-alpha1, but alpha and beta numbers must share the number progression
|
|
@@ -52,6 +52,7 @@ exports.versionToScalar = lodash_1.default.memoize((ver) => {
|
|
|
52
52
|
return officialScalar - betaScalar;
|
|
53
53
|
});
|
|
54
54
|
class Version extends String {
|
|
55
|
+
scalar;
|
|
55
56
|
constructor(v) {
|
|
56
57
|
super(v);
|
|
57
58
|
this.scalar = (0, exports.versionToScalar)(v);
|
|
@@ -101,7 +102,7 @@ function defineConfig(k, defaultValue, compiler) {
|
|
|
101
102
|
return events_1.default.emitAsync(CONFIG_CHANGE_EVENT_PREFIX + k, was, was, const_1.VERSION, true);
|
|
102
103
|
}
|
|
103
104
|
};
|
|
104
|
-
let compiled = compiler
|
|
105
|
+
let compiled = compiler?.(defaultValue, { k, version: exports.currentVersion, defaultValue, object });
|
|
105
106
|
if (compiler)
|
|
106
107
|
object.sub((v, more) => {
|
|
107
108
|
if (!more.onlyCompileChanged)
|
|
@@ -114,13 +115,12 @@ function configKeyExists(k) {
|
|
|
114
115
|
}
|
|
115
116
|
const stack = [];
|
|
116
117
|
function getConfig(k) {
|
|
117
|
-
|
|
118
|
-
return (_a = state[k]) !== null && _a !== void 0 ? _a : lodash_1.default.cloneDeep((_b = configProps[k]) === null || _b === void 0 ? void 0 : _b.defaultValue); // clone to avoid changing
|
|
118
|
+
return state[k] ?? lodash_1.default.cloneDeep(configProps[k]?.defaultValue); // clone to avoid changing
|
|
119
119
|
}
|
|
120
120
|
function getWholeConfig({ omit, only }) {
|
|
121
121
|
const defs = (0, cross_1.newObj)(configProps, x => x.defaultValue);
|
|
122
122
|
let copy = lodash_1.default.defaults({}, state, defs);
|
|
123
|
-
if (omit
|
|
123
|
+
if (omit?.length)
|
|
124
124
|
copy = lodash_1.default.omit(copy, omit);
|
|
125
125
|
if (only)
|
|
126
126
|
copy = lodash_1.default.pick(copy, only);
|
|
@@ -150,18 +150,17 @@ async function setConfig(newCfg, save) {
|
|
|
150
150
|
await Promise.allSettled(Object.keys(configProps).map(k => newCfg.hasOwnProperty(k) || apply(k, undefined, true)));
|
|
151
151
|
started = true;
|
|
152
152
|
events_1.default.emit('configReady', startedWithoutConfig);
|
|
153
|
-
if (
|
|
153
|
+
if (version?.valueOf() !== const_1.VERSION) // be sure to save the new version in the file
|
|
154
154
|
(0, exports.saveConfigAsap)();
|
|
155
155
|
function apply(k, newV, isDefault = false) {
|
|
156
156
|
return setConfig1(k, newV, save === undefined, argCfg && k in argCfg || isDefault ? exports.currentVersion : version);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
async function setConfig1(k, newV, saveChanges = true, valueVersion) {
|
|
160
|
-
var _a;
|
|
161
160
|
if (lodash_1.default.isPlainObject(newV))
|
|
162
161
|
newV = lodash_1.default.pickBy(newV, x => x !== undefined);
|
|
163
|
-
const def =
|
|
164
|
-
if (same(newV
|
|
162
|
+
const def = configProps[k]?.defaultValue;
|
|
163
|
+
if (same(newV ?? null, def ?? null))
|
|
165
164
|
newV = undefined;
|
|
166
165
|
if (started && same(newV, state[k]))
|
|
167
166
|
return; // no change
|
package/src/connections.js
CHANGED
|
@@ -17,12 +17,18 @@ const events_1 = __importDefault(require("./events"));
|
|
|
17
17
|
const geo_1 = require("./geo");
|
|
18
18
|
const lodash_1 = __importDefault(require("lodash"));
|
|
19
19
|
class Connection {
|
|
20
|
+
socket;
|
|
21
|
+
started = new Date();
|
|
22
|
+
sent = 0; // socket-scoped, not request-scoped
|
|
23
|
+
got = 0;
|
|
24
|
+
outSpeed;
|
|
25
|
+
inSpeed;
|
|
26
|
+
ctx; // this is set externally, during koa middleware, using updateConnectionForCtx, but only for regular requests; some connections may never have a ctx
|
|
27
|
+
country;
|
|
28
|
+
_cachedIp;
|
|
20
29
|
// the sockets we collect are the plain ones, as soon as the tcp layer is connected
|
|
21
30
|
constructor(socket) {
|
|
22
31
|
this.socket = socket;
|
|
23
|
-
this.started = new Date();
|
|
24
|
-
this.sent = 0; // socket-scoped, not request-scoped
|
|
25
|
-
this.got = 0;
|
|
26
32
|
all.push(this);
|
|
27
33
|
socket.on('close', () => {
|
|
28
34
|
all.splice(all.indexOf(this), 1);
|
|
@@ -31,12 +37,11 @@ class Connection {
|
|
|
31
37
|
events_1.default.emit('connection', this);
|
|
32
38
|
}
|
|
33
39
|
get ip() {
|
|
34
|
-
var _a, _b;
|
|
35
40
|
if (this._cachedIp && this.ctx && this._cachedIp !== this.ctx.ip) {
|
|
36
41
|
events_1.default.emit('connectionNewIp', this, this._cachedIp, this.ctx.ip);
|
|
37
42
|
this._cachedIp = undefined;
|
|
38
43
|
}
|
|
39
|
-
return
|
|
44
|
+
return this.ctx?.ip || (this._cachedIp ??= normalizeIp(this.socket.remoteAddress || ''));
|
|
40
45
|
}
|
|
41
46
|
get secure() {
|
|
42
47
|
return this.socket.server.cert > '';
|
|
@@ -50,7 +55,7 @@ const all = [];
|
|
|
50
55
|
function newConnection(socket) {
|
|
51
56
|
const ip = normalizeIp(socket.remoteAddress || '');
|
|
52
57
|
const res = events_1.default.emit('newSocket', { socket, ip });
|
|
53
|
-
const msg =
|
|
58
|
+
const msg = res?.isDefaultPrevented() ? 'plugin (newSocket)' : res?.find(lodash_1.default.isString);
|
|
54
59
|
if (!msg)
|
|
55
60
|
return new Connection(socket);
|
|
56
61
|
disconnect(socket, msg);
|
package/src/consoleLog.js
CHANGED
|
@@ -21,10 +21,10 @@ for (const k of ['log', 'warn', 'error', 'debug']) {
|
|
|
21
21
|
const msg = safeJoin(args); // if args contains a symbol, join will throw
|
|
22
22
|
const rec = { ts, k, msg };
|
|
23
23
|
exports.consoleLog.push(rec);
|
|
24
|
-
if (exports.consoleLog.length >
|
|
25
|
-
exports.consoleLog.splice(0,
|
|
24
|
+
if (exports.consoleLog.length > 100_000) // limit to avoid infinite space
|
|
25
|
+
exports.consoleLog.splice(0, 1_000);
|
|
26
26
|
events_1.default.emit('console', rec);
|
|
27
|
-
f
|
|
27
|
+
f?.write(`${(0, cross_1.formatTimestamp)(ts)} [${k}] ${msg}\n`);
|
|
28
28
|
if (k !== 'log')
|
|
29
29
|
args.unshift('!');
|
|
30
30
|
}
|
|
@@ -35,20 +35,20 @@ function safeJoin(a) {
|
|
|
35
35
|
try {
|
|
36
36
|
return a.join(' ');
|
|
37
37
|
}
|
|
38
|
-
catch
|
|
38
|
+
catch {
|
|
39
39
|
return a.map(x => {
|
|
40
40
|
if (x == null)
|
|
41
41
|
return '';
|
|
42
42
|
try {
|
|
43
43
|
return String(x);
|
|
44
44
|
}
|
|
45
|
-
catch
|
|
45
|
+
catch {
|
|
46
46
|
if (Array.isArray(x))
|
|
47
47
|
return `[${safeJoin(x)}]`;
|
|
48
48
|
try {
|
|
49
49
|
return JSON.stringify(x);
|
|
50
50
|
}
|
|
51
|
-
catch
|
|
51
|
+
catch {
|
|
52
52
|
return 'N/A';
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/const.js
CHANGED
|
@@ -49,7 +49,7 @@ const path_1 = require("path");
|
|
|
49
49
|
const cross_1 = require("./cross");
|
|
50
50
|
const argv_1 = require("./argv");
|
|
51
51
|
__exportStar(require("./cross-const"), exports);
|
|
52
|
-
exports.API_VERSION = 12.
|
|
52
|
+
exports.API_VERSION = 12.95;
|
|
53
53
|
exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
|
|
54
54
|
// you can add arguments with this file, currently used for the update process on mac/linux
|
|
55
55
|
exports.ARGS_FILE = (0, path_1.join)((0, os_1.homedir)(), 'hfs-args');
|
|
@@ -59,12 +59,12 @@ try {
|
|
|
59
59
|
lodash_1.default.defaults(argv_1.argv, (0, minimist_1.default)(JSON.parse(s)));
|
|
60
60
|
fs.unlinkSync(exports.ARGS_FILE);
|
|
61
61
|
}
|
|
62
|
-
catch
|
|
62
|
+
catch { }
|
|
63
63
|
exports.DEV = process.env.DEV ? 'DEV' : '';
|
|
64
64
|
exports.ORIGINAL_CWD = process.cwd();
|
|
65
65
|
exports.HFS_STARTED = new Date();
|
|
66
66
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
67
|
-
exports.BUILD_TIMESTAMP = "2025-11-
|
|
67
|
+
exports.BUILD_TIMESTAMP = "2025-11-30T11:15:41.563Z";
|
|
68
68
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
69
69
|
exports.VERSION = pkg.version;
|
|
70
70
|
exports.RUNNING_BETA = exports.VERSION.includes('-');
|
|
@@ -123,7 +123,7 @@ function useHomeDir() {
|
|
|
123
123
|
fs.writeFileSync(exports.CONFIG_FILE, ''); // not found, try to create
|
|
124
124
|
fs.unlinkSync(exports.CONFIG_FILE);
|
|
125
125
|
}
|
|
126
|
-
catch
|
|
126
|
+
catch {
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
129
|
}
|