hfs 3.1.0 → 3.1.2
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-BXz3BvVz.js → index-CpeyrW2d.js} +59 -59
- package/admin/assets/{index-CKmdAsqw.js → index-DI_SOdzQ.js} +1 -1
- package/admin/assets/{sha512-DWu0aUqL.js → sha512-QTaxzUvZ.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/{index-legacy-CQixuY0w.js → index-legacy-BQCEUtxs.js} +2 -2
- package/frontend/assets/{index-legacy-3MaA1vS1.js → index-legacy-DLISyaZ1.js} +1 -1
- package/frontend/assets/{sha512-legacy-iPKPjhP1.js → sha512-legacy-CHdxh7x9.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +91 -82
- package/package.json +4 -4
- package/src/first.js +25 -8
- package/src/listen.js +2 -2
- package/src/nat.js +2 -2
- package/src/update.js +25 -1
- package/src/util-files.js +2 -2
- package/src/vfs.js +4 -3
- package/src/webdav.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"test": "node --import tsx --test tests/test.ts",
|
|
25
25
|
"test-with-server": "sh -c 'npm run port-is-free && tsc && rm -rf tests/work tests/tmp && (node dist/src --cwd tests/work --config tests & echo $! > .server_pid) && sleep 2 && node --import tsx --test \"$@\" tests/test.ts; _exit=$?; if [ -f ./.server_pid ]; then SERVER_PID=$(cat ./.server_pid); kill \"$SERVER_PID\" 2>/dev/null || true; rm -f ./.server_pid; fi; exit $_exit' --",
|
|
26
26
|
"port-is-free": "node -e \"const port=process.argv[1]||8081;process.exit(await fetch('http://localhost:'+port).then(() => console.log('BUSY')||1, () => 0))\" --",
|
|
27
|
-
"test-ui": "npx playwright test frontend && npx playwright test serial && npx playwright test admin-vfs",
|
|
27
|
+
"test-ui": "npm run port-is-free -- 8081 && rm -rf tests/work tests/work2 && npx playwright test frontend && npx playwright test serial && npx playwright test admin-vfs",
|
|
28
28
|
"test-with-ui": "sh -c 'npm run port-is-free -- 3005 && npm run start-frontend & npm run port-is-free -- 3006 && npm run start-admin & cross-env TEST_WITH_UI=1 npx playwright test --ui \"$@\"' --",
|
|
29
29
|
"pub": "cd dist && npm publish",
|
|
30
30
|
"dist": "STASHED=; if ! git diff-index --quiet HEAD --; then git stash push -m 'dist' && STASHED=1; fi; CI=1 FORCE_COLOR=1 npm run dist-uncommitted || (EXIT_CODE=$?; [ -n \"$STASHED\" ] && git stash pop; exit $EXIT_CODE); [ -n \"$STASHED\" ] && git stash pop",
|
|
@@ -82,7 +82,8 @@
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@gregoranders/csv": "^0.0.13",
|
|
85
|
-
"@rejetto/kvstorage": "^0.17.
|
|
85
|
+
"@rejetto/kvstorage": "^0.17.7",
|
|
86
|
+
"@rejetto/nat-upnp": "^2.1.3",
|
|
86
87
|
"acme-client": "^5.4.0",
|
|
87
88
|
"busboy": "^1.6.0",
|
|
88
89
|
"crc-32": "^1.2.2",
|
|
@@ -102,7 +103,6 @@
|
|
|
102
103
|
"lodash": "^4.17.21",
|
|
103
104
|
"mime-types": "*",
|
|
104
105
|
"minimist": "^1.2.8",
|
|
105
|
-
"nat-upnp-rejetto": "^2.1.2",
|
|
106
106
|
"node-forge": "^1.3.1",
|
|
107
107
|
"open": "^8.4.0",
|
|
108
108
|
"picomatch": "^4.0.3",
|
package/src/first.js
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.quitting = void 0;
|
|
4
7
|
exports.onProcessExit = onProcessExit;
|
|
5
8
|
exports.onFirstEvent = onFirstEvent;
|
|
9
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
10
|
+
const assert_1 = __importDefault(require("assert"));
|
|
6
11
|
const cbsOnExit = new Set();
|
|
7
|
-
function onProcessExit(cb) {
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
function onProcessExit(cb, order = 10) {
|
|
13
|
+
(0, assert_1.default)(Number.isInteger(order) && order >= 0, 'order must be an integer >= 0');
|
|
14
|
+
const rec = { cb, order };
|
|
15
|
+
cbsOnExit.add(rec);
|
|
16
|
+
return () => cbsOnExit.delete(rec);
|
|
10
17
|
}
|
|
11
18
|
exports.quitting = false;
|
|
12
19
|
// 'exit' event is handled as the last resort, but it's not compatible with async callbacks
|
|
13
|
-
onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], signal => {
|
|
20
|
+
onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], async (signal) => {
|
|
14
21
|
console.log('Quitting with signal:', signal || 'unknown');
|
|
15
22
|
exports.quitting = true;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
const byOrder = lodash_1.default.groupBy(Array.from(cbsOnExit), 'order'); // this will be inherently ordered because keys are positive integers
|
|
24
|
+
for (const recs of Object.values(byOrder))
|
|
25
|
+
await Promise.allSettled(recs.map(({ cb }) => {
|
|
26
|
+
try {
|
|
27
|
+
return cb(signal);
|
|
28
|
+
}
|
|
29
|
+
// keep exit moving even when a synchronous cleanup fails after partially shutting down
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.error("Error while quitting", e);
|
|
32
|
+
return Promise.reject(e);
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
console.debug('Process exit');
|
|
36
|
+
process.exit(0);
|
|
20
37
|
});
|
|
21
38
|
// keep calling cb in a sync fashion – returning a promise instead would break the code for argv.updating (update.ts)
|
|
22
39
|
function onFirstEvent(emitter, events, cb) {
|
package/src/listen.js
CHANGED
|
@@ -70,8 +70,8 @@ const consoleLog_1 = require("./consoleLog");
|
|
|
70
70
|
const first_1 = require("./first");
|
|
71
71
|
let httpSrv;
|
|
72
72
|
let httpsSrv;
|
|
73
|
-
// update relaunch can keep a bridge process alive, so we proactively close listeners here to release ports before the next binary binds
|
|
74
|
-
(0, first_1.onProcessExit)(() => Promise.all([stopServer(httpSrv), stopServer(httpsSrv)]));
|
|
73
|
+
// the update relaunch can keep a bridge process alive, so we proactively close listeners here to release ports before the next binary binds; do it before (5) the storage file is closed, because sockets write there
|
|
74
|
+
(0, first_1.onProcessExit)(() => Promise.all([stopServer(httpSrv), stopServer(httpsSrv)]), 5);
|
|
75
75
|
const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', true);
|
|
76
76
|
exports.baseUrl = (0, config_1.defineConfig)(misc_1.CFG.base_url, '', x => /(?<=\/\/)[^\/]+/.exec(x)?.[0]); // compiled is host only
|
|
77
77
|
async function getBaseUrlOrDefault() {
|
package/src/nat.js
CHANGED
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getNatInfo = exports.getPublicIps = exports.upnpClient = exports.defaultBaseUrl = void 0;
|
|
7
7
|
const valtio_1 = require("valtio");
|
|
8
|
-
const
|
|
8
|
+
const nat_upnp_1 = require("@rejetto/nat-upnp");
|
|
9
9
|
const debounceAsync_1 = require("./debounceAsync");
|
|
10
10
|
const cross_1 = require("./cross");
|
|
11
11
|
const github_1 = require("./github");
|
|
@@ -30,7 +30,7 @@ exports.defaultBaseUrl = (0, valtio_1.proxy)({
|
|
|
30
30
|
return `${this.proto}://${(0, cross_1.ipForUrl)(ip || 'localhost')}${!port || port === defPort ? '' : ':' + port}`;
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
|
-
exports.upnpClient = new
|
|
33
|
+
exports.upnpClient = new nat_upnp_1.Client({ timeout: 4_000 });
|
|
34
34
|
const originalMethod = exports.upnpClient.getGateway;
|
|
35
35
|
// other client methods call getGateway too, so this will ensure they reuse this same result
|
|
36
36
|
exports.upnpClient.getGateway = (0, debounceAsync_1.debounceAsync)(() => originalMethod.apply(exports.upnpClient), { retain: cross_1.HOUR, retainFailure: 30_000 });
|
package/src/update.js
CHANGED
|
@@ -181,7 +181,18 @@ async function update(tagOrUrl = '') {
|
|
|
181
181
|
catch { }
|
|
182
182
|
(0, fs_1.renameSync)(bin, oldBin);
|
|
183
183
|
if (!preserveTerminal) {
|
|
184
|
-
|
|
184
|
+
try {
|
|
185
|
+
renameSyncWithBusyRetry(newBin, (0, path_1.join)(binPath, binFile));
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
try {
|
|
189
|
+
(0, fs_1.renameSync)(oldBin, bin);
|
|
190
|
+
} // restore the service target because hfs.exe was already moved aside
|
|
191
|
+
catch (rollbackError) {
|
|
192
|
+
console.error("Couldn't restore original binary after failed update", rollbackError);
|
|
193
|
+
}
|
|
194
|
+
throw e;
|
|
195
|
+
}
|
|
185
196
|
console.log("Updated binary in place, exiting for process supervisor to restart");
|
|
186
197
|
return;
|
|
187
198
|
}
|
|
@@ -197,6 +208,19 @@ async function update(tagOrUrl = '') {
|
|
|
197
208
|
throw e?.message || String(e);
|
|
198
209
|
}
|
|
199
210
|
}
|
|
211
|
+
function renameSyncWithBusyRetry(src, dest) {
|
|
212
|
+
const sleepSyncBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
213
|
+
for (let retry = 0;; retry++) {
|
|
214
|
+
try {
|
|
215
|
+
return (0, fs_1.renameSync)(src, dest);
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
if (e?.code !== 'EBUSY' || retry >= 20)
|
|
219
|
+
throw e;
|
|
220
|
+
Atomics.wait(sleepSyncBuffer, 0, 0, 500);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
200
224
|
if (argv_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
|
|
201
225
|
const bin = process.execPath;
|
|
202
226
|
const dest = (0, path_1.join)((0, path_1.dirname)(bin), argv_1.argv.updating);
|
package/src/util-files.js
CHANGED
|
@@ -27,7 +27,7 @@ const fs_1 = require("fs");
|
|
|
27
27
|
const path_1 = require("path");
|
|
28
28
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
29
29
|
const const_1 = require("./const");
|
|
30
|
-
const
|
|
30
|
+
const promises_2 = require("stream/promises");
|
|
31
31
|
const stat_1 = require("./stat");
|
|
32
32
|
// @ts-ignore
|
|
33
33
|
const unzip_stream_1 = __importDefault(require("unzip-stream"));
|
|
@@ -115,7 +115,7 @@ async function unzip(stream, cb) {
|
|
|
115
115
|
return entry.autodrain();
|
|
116
116
|
console.debug('Unzip', dest);
|
|
117
117
|
const thisFile = entry.pipe(await createSafeWriteStream(dest));
|
|
118
|
-
await (0,
|
|
118
|
+
await (0, promises_2.finished)(thisFile);
|
|
119
119
|
})));
|
|
120
120
|
}
|
|
121
121
|
async function ensureParentFolder(path, dirnameIt = true) {
|
package/src/vfs.js
CHANGED
|
@@ -162,12 +162,13 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
|
|
|
162
162
|
return ret;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
const smartUncFolderDetection = (0, config_1.defineConfig)('smart_unc_folder_detection',
|
|
165
|
+
const smartUncFolderDetection = (0, config_1.defineConfig)('smart_unc_folder_detection', false);
|
|
166
166
|
async function setIsFolder(node) {
|
|
167
167
|
if (!node.source)
|
|
168
168
|
return;
|
|
169
|
-
const isFolder =
|
|
170
|
-
|
|
169
|
+
const isFolder = /[\\/]$/.test(node.source)
|
|
170
|
+
|| smartUncFolderDetection.get() && (0, misc_1.getUncHost)(node.source) && !(0, path_1.basename)(node.source).includes('.') // no dot = folder; not very reliable but fast for unreachable unc hosts, and it's an opt-in
|
|
171
|
+
|| await nodeStats(node).then(x => x?.isDirectory(), () => undefined);
|
|
171
172
|
(0, misc_1.setHidden)(node, { isFolder });
|
|
172
173
|
return isFolder;
|
|
173
174
|
}
|
package/src/webdav.js
CHANGED
|
@@ -75,9 +75,12 @@ const webdav = async (ctx, next) => {
|
|
|
75
75
|
const ua = ctx.get('user-agent');
|
|
76
76
|
if (path.includes('/._') && ua?.startsWith('WebDAVFS')) { // too much spam from Finder for these files that can contain metas
|
|
77
77
|
ctx.state.dontLog = true;
|
|
78
|
+
ctx.state.webdavDetected = true;
|
|
78
79
|
return ctx.status = cross_1.HTTP_FORBIDDEN;
|
|
79
80
|
}
|
|
80
81
|
const isWebdavAuthRequest = WEBDAV_METHODS.has(ctx.method) || WEBDAV_HINT_HEADERS.some(h => ctx.get(h));
|
|
82
|
+
if (isWebdavAuthRequest)
|
|
83
|
+
ctx.state.webdavDetected = true;
|
|
81
84
|
if (isWebdavAuthRequest && ua && (0, auth_1.getCurrentUsername)(ctx))
|
|
82
85
|
webdavDetectedAgents.try(webdavAgentKey(ctx, ua), () => true);
|
|
83
86
|
if (ctx.method === 'OPTIONS') {
|