hfs 0.48.0-alpha2 → 0.48.0-alpha4
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-67290e78.js +536 -0
- package/admin/assets/{sha512-136b5a7c.js → sha512-92cfe0b0.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/index-110410d0.js +94 -0
- package/frontend/assets/{sha512-549e6c3a.js → sha512-8d938ca9.js} +1 -1
- package/frontend/index.html +1 -1
- package/package.json +4 -3
- package/src/QuickZipStream.js +1 -0
- package/src/api.file_list.js +38 -37
- package/src/api.net.js +141 -46
- package/src/api.plugins.js +25 -20
- package/src/config.js +2 -3
- package/src/const.js +3 -3
- package/src/cross.js +30 -1
- package/src/debounceAsync.js +13 -4
- package/src/frontEndApis.js +5 -11
- package/src/github.js +30 -29
- package/src/index.js +2 -0
- package/src/langs/embedded.js +2 -1
- package/src/langs/hfs-lang-fi.json +131 -0
- package/src/langs/hfs-lang-it.json +2 -0
- package/src/langs/hfs-lang-ru.json +11 -5
- package/src/listen.js +29 -15
- package/src/log.js +4 -5
- package/src/middlewares.js +2 -2
- package/src/misc.js +29 -2
- package/src/util-files.js +3 -7
- package/src/watchLoad.js +5 -5
- package/admin/assets/index-344723f8.js +0 -520
- package/frontend/assets/index-12411ff6.js +0 -94
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g as OF,c as UF}from"./index-
|
|
1
|
+
import{g as OF,c as UF}from"./index-110410d0.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
|
|
2
2
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
3
3
|
*
|
|
4
4
|
* @version 0.8.0
|
package/frontend/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
|
|
6
6
|
<link href="/fontello.css" rel="stylesheet" />
|
|
7
7
|
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-110410d0.js"></script>
|
|
9
9
|
<link rel="stylesheet" href="/assets/index-05dfd82f.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.48.0-
|
|
3
|
+
"version": "0.48.0-alpha4",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"dist-bin": "npm run dist-modules && cd dist && pkg . --public -C gzip && mv -f hfs-win-x64.exe hfs.exe && zip hfs-windows.zip hfs.exe -r plugins && cp -f hfs-linux-x64 hfs && zip hfs-linux.zip hfs -r plugins && cp -f hfs-macos-x64 hfs && zip hfs-mac.zip hfs -r plugins && cp -f hfs-macos-arm64 hfs && zip hfs-mac-arm.zip hfs -r plugins && rm hfs",
|
|
33
33
|
"dist-modules": "cp package*.json central.json dist && cd dist && npm ci --omit=dev && npm run dist-crclib && rm package-lock.json && cd .. && node prune_modules",
|
|
34
34
|
"dist-crclib": "npm i -f --no-save --omit=dev @node-rs/crc32-win32-x64-msvc @node-rs/crc32-darwin-arm64 @node-rs/crc32-darwin-x64 @node-rs/crc32-linux-x64-gnu",
|
|
35
|
-
"dist-win": "npm run dist-modules && cd dist && pkg . --public -C gzip -t
|
|
36
|
-
"dist-mac": "npm run dist-modules && cd dist && pkg . --public -C gzip -t
|
|
35
|
+
"dist-win": "npm run dist-modules && cd dist && pkg . --public -C gzip -t node18-win-x64",
|
|
36
|
+
"dist-mac": "npm run dist-modules && cd dist && pkg . --public -C gzip -t node18-macos-arm64",
|
|
37
37
|
"dist-node": "npm run dist-modules && cd dist && zip hfs-node.zip -r * -x *.zip *.exe hfs-* *.log logs"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@koa/router": "^10.1.1",
|
|
70
70
|
"@node-rs/crc32": "^1.6.0",
|
|
71
|
+
"acme-client": "^5.0.0",
|
|
71
72
|
"basic-auth": "^2.0.1",
|
|
72
73
|
"buffer-crc32": "^0.2.13",
|
|
73
74
|
"fast-glob": "^3.2.7",
|
package/src/QuickZipStream.js
CHANGED
|
@@ -162,6 +162,7 @@ class QuickZipStream extends stream_1.Readable {
|
|
|
162
162
|
data.on('error', (err) => console.error(err));
|
|
163
163
|
data.on('end', () => {
|
|
164
164
|
this.workingFile = undefined;
|
|
165
|
+
entry.crc = crc;
|
|
165
166
|
if (sourcePath)
|
|
166
167
|
crcCache[sourcePath] = { ts, crc };
|
|
167
168
|
this.entries.push(entry);
|
package/src/api.file_list.js
CHANGED
|
@@ -34,7 +34,8 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
|
|
|
34
34
|
const walker = (0, vfs_1.walkNode)(node, ctx, search ? Infinity : 0);
|
|
35
35
|
const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry);
|
|
36
36
|
const can_upload = (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
|
|
37
|
-
const
|
|
37
|
+
const fakeChild = (0, vfs_1.applyParentToChild)({}, node); // we want to know if we want to delete children
|
|
38
|
+
const can_delete = (0, vfs_1.hasPermission)(fakeChild, 'can_delete', ctx);
|
|
38
39
|
const props = { can_upload, can_delete, accept: node.accept };
|
|
39
40
|
if (!list)
|
|
40
41
|
return { ...props, list: await (0, misc_1.asyncGeneratorToArray)(produceEntries()) };
|
|
@@ -84,41 +85,41 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
|
|
|
84
85
|
break;
|
|
85
86
|
}
|
|
86
87
|
}
|
|
88
|
+
async function nodeToDirEntry(ctx, node) {
|
|
89
|
+
let { source, default: def } = node;
|
|
90
|
+
const name = (0, vfs_1.getNodeName)(node);
|
|
91
|
+
if (!source)
|
|
92
|
+
return name ? { n: name + '/' } : null;
|
|
93
|
+
if (def)
|
|
94
|
+
return { n: name };
|
|
95
|
+
try {
|
|
96
|
+
const st = await (0, promises_1.stat)(source);
|
|
97
|
+
const folder = st.isDirectory();
|
|
98
|
+
const { ctime, mtime } = st;
|
|
99
|
+
const pl = node.can_list === vfs_1.WHO_NO_ONE ? 'l'
|
|
100
|
+
: !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
|
|
101
|
+
: '';
|
|
102
|
+
// no download here, but maybe inside?
|
|
103
|
+
const pr = node.can_read === vfs_1.WHO_NO_ONE && !(folder && filesInsideCould()) ? 'r'
|
|
104
|
+
: !(0, vfs_1.hasPermission)(node, 'can_read', ctx) ? 'R'
|
|
105
|
+
: '';
|
|
106
|
+
const pd = !can_delete && (0, vfs_1.hasPermission)(node, 'can_delete', ctx) ? 'd' : '';
|
|
107
|
+
return {
|
|
108
|
+
n: name + (folder ? '/' : ''),
|
|
109
|
+
c: ctime,
|
|
110
|
+
m: Math.abs(+mtime - +ctime) < 1000 ? undefined : mtime,
|
|
111
|
+
s: folder ? undefined : st.size,
|
|
112
|
+
p: (pr + pl + pd) || undefined
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (_a) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function filesInsideCould(n = node) {
|
|
119
|
+
var _a;
|
|
120
|
+
return (0, vfs_1.masksCouldGivePermission)(n.masks, 'can_read')
|
|
121
|
+
|| ((_a = n.children) === null || _a === void 0 ? void 0 : _a.some(c => c.can_read || filesInsideCould(c))); // we count on the boolean-compliant nature of the permission type here
|
|
122
|
+
}
|
|
123
|
+
}
|
|
87
124
|
};
|
|
88
125
|
exports.get_file_list = get_file_list;
|
|
89
|
-
async function nodeToDirEntry(ctx, node) {
|
|
90
|
-
let { source, default: def } = node;
|
|
91
|
-
const name = (0, vfs_1.getNodeName)(node);
|
|
92
|
-
if (!source)
|
|
93
|
-
return name ? { n: name + '/' } : null;
|
|
94
|
-
if (def)
|
|
95
|
-
return { n: name };
|
|
96
|
-
try {
|
|
97
|
-
const st = await (0, promises_1.stat)(source);
|
|
98
|
-
const folder = st.isDirectory();
|
|
99
|
-
const { ctime, mtime } = st;
|
|
100
|
-
const pl = node.can_list === vfs_1.WHO_NO_ONE ? 'l'
|
|
101
|
-
: !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
|
|
102
|
-
: '';
|
|
103
|
-
// no download here, but maybe inside?
|
|
104
|
-
const pr = node.can_read === vfs_1.WHO_NO_ONE && !(folder && filesInsideCould()) ? 'r'
|
|
105
|
-
: !(0, vfs_1.hasPermission)(node, 'can_read', ctx) ? 'R'
|
|
106
|
-
: '';
|
|
107
|
-
const pd = (0, vfs_1.hasPermission)(node, 'can_delete', ctx) ? 'd' : '';
|
|
108
|
-
return {
|
|
109
|
-
n: name + (folder ? '/' : ''),
|
|
110
|
-
c: ctime,
|
|
111
|
-
m: Math.abs(+mtime - +ctime) < 1000 ? undefined : mtime,
|
|
112
|
-
s: folder ? undefined : st.size,
|
|
113
|
-
p: (pr + pl + pd) || undefined
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
catch (_a) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
function filesInsideCould(n = node) {
|
|
120
|
-
var _a;
|
|
121
|
-
return (0, vfs_1.masksCouldGivePermission)(n.masks, 'can_read')
|
|
122
|
-
|| ((_a = n.children) === null || _a === void 0 ? void 0 : _a.some(c => c.can_read || filesInsideCould(c))); // we count on the boolean-compliant nature of the permission type here
|
|
123
|
-
}
|
|
124
|
-
}
|
package/src/api.net.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.externalIp = void 0;
|
|
7
|
+
exports.acmeMiddleware = exports.externalIp = void 0;
|
|
8
8
|
const apiMiddleware_1 = require("./apiMiddleware");
|
|
9
9
|
const nat_upnp_1 = require("nat-upnp");
|
|
10
10
|
const const_1 = require("./const");
|
|
@@ -16,17 +16,15 @@ const github_1 = require("./github");
|
|
|
16
16
|
const util_http_1 = require("./util-http");
|
|
17
17
|
const child_process_1 = require("child_process");
|
|
18
18
|
const misc_1 = require("./misc");
|
|
19
|
-
const
|
|
20
|
-
const
|
|
19
|
+
const acme_client_1 = __importDefault(require("acme-client"));
|
|
20
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
21
|
+
const http_1 = require("http");
|
|
22
|
+
const promises_2 = require("dns/promises");
|
|
23
|
+
const client = new nat_upnp_1.Client({ timeout: 4000 });
|
|
24
|
+
const originalMethod = client.getGateway;
|
|
21
25
|
// other client methods call getGateway too, so this will ensure they reuse this same result
|
|
22
|
-
client.getGateway =
|
|
23
|
-
|
|
24
|
-
client.getGateway = () => promise; // multiple callings = same job
|
|
25
|
-
promise.then(() => console.debug('caching gateway'), // store in cache only if successful.
|
|
26
|
-
() => client.getGateway = getGatewayCaching); // failed, try again
|
|
27
|
-
return promise;
|
|
28
|
-
};
|
|
29
|
-
client.getGateway();
|
|
26
|
+
client.getGateway = (0, misc_1.debounceAsync)(() => originalMethod.apply(client), 0, { retain: misc_1.HOUR, retainFailure: 30000 });
|
|
27
|
+
client.getGateway().catch(() => { });
|
|
30
28
|
exports.externalIp = Promise.resolve(''); // poll external ip
|
|
31
29
|
(0, misc_1.repeat)(10 * misc_1.MINUTE, () => {
|
|
32
30
|
const was = exports.externalIp;
|
|
@@ -42,8 +40,7 @@ const getNatInfo = (0, misc_1.debounceAsync)(async () => {
|
|
|
42
40
|
const gatewayIp = res ? new URL(res.gateway.description).hostname : await findGateway().catch(() => null);
|
|
43
41
|
const localIp = (res === null || res === void 0 ? void 0 : res.address) || (await (0, listen_1.getIps)())[0];
|
|
44
42
|
const internalPort = ((_a = status === null || status === void 0 ? void 0 : status.https) === null || _a === void 0 ? void 0 : _a.listening) && status.https.port || ((_b = status === null || status === void 0 ? void 0 : status.http) === null || _b === void 0 ? void 0 : _b.listening) && status.http.port;
|
|
45
|
-
const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort
|
|
46
|
-
console.debug('responding');
|
|
43
|
+
const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
|
|
47
44
|
return {
|
|
48
45
|
upnp: Boolean(res),
|
|
49
46
|
localIp,
|
|
@@ -80,57 +77,155 @@ function findGateway() {
|
|
|
80
77
|
resolve((_a = re.exec(out)) === null || _a === void 0 ? void 0 : _a[1]);
|
|
81
78
|
}));
|
|
82
79
|
}
|
|
80
|
+
let acmeMiddlewareEnabled = false;
|
|
81
|
+
const acmeTokens = {};
|
|
82
|
+
const acmeListener = (req, res) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const BASE = '/.well-known/acme-challenge/';
|
|
85
|
+
if (!((_a = req.url) === null || _a === void 0 ? void 0 : _a.startsWith(BASE)))
|
|
86
|
+
return;
|
|
87
|
+
const token = req.url.slice(BASE.length);
|
|
88
|
+
console.debug("got http challenge", token);
|
|
89
|
+
res.statusCode = const_1.HTTP_OK;
|
|
90
|
+
res.end(acmeTokens[token]);
|
|
91
|
+
return true;
|
|
92
|
+
};
|
|
93
|
+
const acmeMiddleware = (ctx, next) => {
|
|
94
|
+
if (!acmeMiddlewareEnabled || !Boolean(acmeListener(ctx.req, ctx.res)))
|
|
95
|
+
return next();
|
|
96
|
+
};
|
|
97
|
+
exports.acmeMiddleware = acmeMiddleware;
|
|
98
|
+
async function checkDomain(domain) {
|
|
99
|
+
const { address: domainIp } = await (0, promises_2.lookup)(domain).catch(e => {
|
|
100
|
+
throw e.code !== 'ENOTFOUND' ? e : new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "this domain doesn't exist");
|
|
101
|
+
});
|
|
102
|
+
const { publicIp } = await getNatInfo(); // do this before stopping the server
|
|
103
|
+
if (publicIp !== domainIp)
|
|
104
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, `please configure your domain to point to ${publicIp} (currently on ${domainIp}) --- a change can take hours to be effective`);
|
|
105
|
+
}
|
|
106
|
+
async function generateSSLCert(domain, email) {
|
|
107
|
+
await checkDomain(domain);
|
|
108
|
+
// will answer challenge through our koa app (if on port 80) or must we spawn a dedicated server?
|
|
109
|
+
const { http } = await (0, listen_1.getServerStatus)();
|
|
110
|
+
const tempSrv = http.listening && http.port === 80 ? undefined : (0, http_1.createServer)(acmeListener);
|
|
111
|
+
if (tempSrv)
|
|
112
|
+
await new Promise((resolve, reject) => tempSrv.listen(80, resolve).on('error', (e) => reject(e.code || e)));
|
|
113
|
+
else
|
|
114
|
+
acmeMiddlewareEnabled = true;
|
|
115
|
+
console.debug('acme challenge server ready');
|
|
116
|
+
try {
|
|
117
|
+
const { upnp, externalPort } = await getNatInfo(); // do this before stopping the server
|
|
118
|
+
let check = await checkPort(domain, 80); // some check services may not consider the domain, but we already verified that
|
|
119
|
+
if (check && !check.success && upnp && externalPort !== 80) { // consider a short-lived mapping
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
await client.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs challenge', ttl: 0 }).catch(() => { });
|
|
122
|
+
check = await checkPort(domain, 80); // repeat test
|
|
123
|
+
}
|
|
124
|
+
if (!check)
|
|
125
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "couldn't test port 80");
|
|
126
|
+
if (!check.success)
|
|
127
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "port 80 is not working on the specified domain");
|
|
128
|
+
const client = new acme_client_1.default.Client({
|
|
129
|
+
accountKey: await acme_client_1.default.crypto.createPrivateKey(),
|
|
130
|
+
directoryUrl: acme_client_1.default.directory.letsencrypt.production
|
|
131
|
+
});
|
|
132
|
+
const [key, csr] = await acme_client_1.default.crypto.createCsr({ commonName: domain });
|
|
133
|
+
const cert = await client.auto({
|
|
134
|
+
csr,
|
|
135
|
+
email,
|
|
136
|
+
challengePriority: ['http-01'],
|
|
137
|
+
skipChallengeVerification: true,
|
|
138
|
+
termsOfServiceAgreed: true,
|
|
139
|
+
async challengeCreateFn(_, c, ka) {
|
|
140
|
+
console.debug("producing challenge");
|
|
141
|
+
acmeTokens[c.token] = ka;
|
|
142
|
+
},
|
|
143
|
+
async challengeRemoveFn(_, c) {
|
|
144
|
+
delete acmeTokens[c.token];
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
return { key, cert };
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
acmeMiddlewareEnabled = false;
|
|
151
|
+
if (tempSrv)
|
|
152
|
+
await new Promise(res => tempSrv.close(res));
|
|
153
|
+
console.debug('acme terminated');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function checkPort(ip, port) {
|
|
157
|
+
const prjInfo = await (0, github_1.getProjectInfo)();
|
|
158
|
+
for (const services of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.checkServerServices), 2)) {
|
|
159
|
+
try {
|
|
160
|
+
return Promise.any(services.map(async (svc) => {
|
|
161
|
+
var _a, _b;
|
|
162
|
+
const service = new URL(svc.url).hostname;
|
|
163
|
+
console.log('trying service', service);
|
|
164
|
+
const api = axios_1.default[svc.method];
|
|
165
|
+
const body = ((_a = svc.body) === null || _a === void 0 ? void 0 : _a.replace('$IP', ip).replace('$PORT', String(port))) || '';
|
|
166
|
+
const res = await api(svc.url, body, { headers: svc.headers });
|
|
167
|
+
const parsed = (_b = (0, node_html_parser_1.parse)(res.data).querySelector(svc.selector)) === null || _b === void 0 ? void 0 : _b.innerText;
|
|
168
|
+
if (!parsed)
|
|
169
|
+
throw console.debug('empty:' + service);
|
|
170
|
+
const success = new RegExp(svc.regexpSuccess).test(parsed);
|
|
171
|
+
const failure = new RegExp(svc.regexpFailure).test(parsed);
|
|
172
|
+
if (success === failure)
|
|
173
|
+
throw console.debug('inconsistent:' + service); // this result cannot be trusted
|
|
174
|
+
console.debug(service, 'responded', success);
|
|
175
|
+
return { success, service };
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
catch (_a) { }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
83
181
|
const apis = {
|
|
84
182
|
get_nat: getNatInfo,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
183
|
+
check_domain({ domain }) {
|
|
184
|
+
(0, misc_1.apiAssertTypes)({ string: domain });
|
|
185
|
+
return checkDomain(domain);
|
|
186
|
+
},
|
|
187
|
+
async map_port({ external, internal }) {
|
|
188
|
+
const { upnp, externalPort, internalPort } = await getNatInfo();
|
|
189
|
+
if (!upnp)
|
|
88
190
|
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, 'upnp failed');
|
|
89
191
|
if (!internalPort)
|
|
90
192
|
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
|
|
91
|
-
if (
|
|
193
|
+
if (externalPort)
|
|
92
194
|
try {
|
|
93
|
-
await client.removeMapping({ public: { host: '', port:
|
|
195
|
+
await client.removeMapping({ public: { host: '', port: externalPort } });
|
|
94
196
|
}
|
|
95
197
|
catch (e) {
|
|
96
198
|
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, 'removeMapping failed: ' + String(e));
|
|
97
199
|
}
|
|
98
|
-
if (external) // must use the object form of 'public' to
|
|
99
|
-
await client.createMapping({ private: internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 });
|
|
200
|
+
if (external) // must use the object form of 'public' to work around a bug of the library
|
|
201
|
+
await client.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 });
|
|
100
202
|
return {};
|
|
101
203
|
},
|
|
102
|
-
async check_server() {
|
|
204
|
+
async check_server({ port }) {
|
|
103
205
|
const { publicIp, internalPort, externalPort } = await getNatInfo();
|
|
104
206
|
if (!publicIp)
|
|
105
207
|
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, 'cannot detect public ip');
|
|
106
208
|
if (!internalPort)
|
|
107
209
|
return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
|
|
108
|
-
|
|
109
|
-
const port = externalPort || internalPort;
|
|
210
|
+
port || (port = externalPort || internalPort);
|
|
110
211
|
console.log(`checking server ${publicIp}:${port}`);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (success === failure)
|
|
126
|
-
throw console.debug('inconsistent:' + service); // this result cannot be trusted
|
|
127
|
-
console.debug(service, 'responded', success);
|
|
128
|
-
return { success, service };
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
catch (_a) { }
|
|
132
|
-
}
|
|
133
|
-
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, 'no service available to detect upnp mapping');
|
|
212
|
+
return await checkPort(publicIp, port)
|
|
213
|
+
|| new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE);
|
|
214
|
+
},
|
|
215
|
+
async make_cert({ domain, email }) {
|
|
216
|
+
if (!domain)
|
|
217
|
+
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad params');
|
|
218
|
+
const res = await generateSSLCert(domain, email);
|
|
219
|
+
const CERT_FILE = 'acme.cert';
|
|
220
|
+
const KEY_FILE = 'acme.key';
|
|
221
|
+
await promises_1.default.writeFile(CERT_FILE, res.cert);
|
|
222
|
+
await promises_1.default.writeFile(KEY_FILE, res.key);
|
|
223
|
+
listen_1.cert.set(CERT_FILE); // update config
|
|
224
|
+
listen_1.privateKey.set(KEY_FILE);
|
|
225
|
+
return {};
|
|
134
226
|
},
|
|
227
|
+
get_cert() {
|
|
228
|
+
return (0, misc_1.objSameKeys)(lodash_1.default.pick((0, listen_1.getCertObject)(), ['subject', 'issuer', 'validFrom', 'validTo']), v => v);
|
|
229
|
+
}
|
|
135
230
|
};
|
|
136
231
|
exports.default = apis;
|
package/src/api.plugins.js
CHANGED
|
@@ -24,13 +24,6 @@ const apis = {
|
|
|
24
24
|
},
|
|
25
25
|
pluginUninstalled: id => list.remove({ id }),
|
|
26
26
|
});
|
|
27
|
-
function serialize(p) {
|
|
28
|
-
const o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
|
|
29
|
-
: { ...p }; // _.defaults mutates object, and we don't want that
|
|
30
|
-
if (typeof o.repo === 'object') // custom repo
|
|
31
|
-
o.repo = o.repo.web;
|
|
32
|
-
return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
|
|
33
|
-
}
|
|
34
27
|
},
|
|
35
28
|
async get_plugin_updates() {
|
|
36
29
|
return new apiMiddleware_1.SendListReadable({
|
|
@@ -44,8 +37,13 @@ const apis = {
|
|
|
44
37
|
if (!(online === null || online === void 0 ? void 0 : online.apiRequired) || online.badApi)
|
|
45
38
|
return;
|
|
46
39
|
const disk = (0, plugins_1.getPluginInfo)(folder);
|
|
47
|
-
if (
|
|
40
|
+
if (!disk)
|
|
41
|
+
return; // plugin removed in the meantime?
|
|
42
|
+
if (online.version > disk.version) { // it IS newer
|
|
43
|
+
online.id = disk.id; // id is installation-dependant, and online cannot know
|
|
44
|
+
online.repo = serialize(disk).repo; // show the user the current repo we are getting this update from, not a possibly-changed future one
|
|
48
45
|
list.add(online);
|
|
46
|
+
}
|
|
49
47
|
}
|
|
50
48
|
catch (err) {
|
|
51
49
|
if (err.message === '404') // the plugin is declaring a wrong repo
|
|
@@ -97,8 +95,8 @@ const apis = {
|
|
|
97
95
|
const undo = [];
|
|
98
96
|
ctx.req.once('close', () => undo.forEach(x => x()));
|
|
99
97
|
const folder2repo = (0, github_1.getFolder2repo)();
|
|
100
|
-
for await (const pl of (0, github_1.searchPlugins)(text)) {
|
|
101
|
-
const repo = pl.id;
|
|
98
|
+
for await (const pl of await (0, github_1.searchPlugins)(text)) {
|
|
99
|
+
const repo = pl.repo || pl.id; // .repo property can be more trustworthy in case github user renamed and left the previous link in 'repo'
|
|
102
100
|
if (lodash_1.default.includes(folder2repo, repo))
|
|
103
101
|
continue; // don't include installed plugins
|
|
104
102
|
list.add(pl);
|
|
@@ -125,24 +123,24 @@ const apis = {
|
|
|
125
123
|
}
|
|
126
124
|
});
|
|
127
125
|
},
|
|
128
|
-
async download_plugin(
|
|
129
|
-
await checkDependencies(
|
|
130
|
-
const res = await (0, github_1.downloadPlugin)(
|
|
126
|
+
async download_plugin({ id, branch }) {
|
|
127
|
+
await checkDependencies(id, branch);
|
|
128
|
+
const res = await (0, github_1.downloadPlugin)(id, { branch });
|
|
131
129
|
if (typeof res !== 'string')
|
|
132
130
|
return res;
|
|
133
131
|
return (await (0, misc_1.waitFor)(() => (0, plugins_1.getPluginInfo)(res), { timeout: 5000 }))
|
|
134
132
|
|| new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
|
|
135
133
|
},
|
|
136
|
-
async update_plugin(
|
|
137
|
-
|
|
138
|
-
const found = (0, plugins_1.findPluginByRepo)(pl.id); // github id !== local id
|
|
134
|
+
async update_plugin({ id }) {
|
|
135
|
+
const found = (0, plugins_1.getPluginInfo)(id);
|
|
139
136
|
if (!found)
|
|
140
137
|
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
await (0,
|
|
138
|
+
await checkDependencies(found.repo);
|
|
139
|
+
const enabled = (0, plugins_1.isPluginEnabled)(id);
|
|
140
|
+
await (0, plugins_1.stopPlugin)(id);
|
|
141
|
+
await (0, github_1.downloadPlugin)(found.repo, { overwrite: true });
|
|
144
142
|
if (enabled)
|
|
145
|
-
(0, plugins_1.startPlugin)(
|
|
143
|
+
(0, plugins_1.startPlugin)(id).then(); // don't wait, in case it fails to start
|
|
146
144
|
return {};
|
|
147
145
|
},
|
|
148
146
|
async uninstall_plugin({ id }) {
|
|
@@ -152,6 +150,13 @@ const apis = {
|
|
|
152
150
|
}
|
|
153
151
|
};
|
|
154
152
|
exports.default = apis;
|
|
153
|
+
function serialize(p) {
|
|
154
|
+
const o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
|
|
155
|
+
: { ...p }; // _.defaults mutates object, and we don't want that
|
|
156
|
+
if (typeof o.repo === 'object') // custom repo
|
|
157
|
+
o.repo = o.repo.web;
|
|
158
|
+
return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
|
|
159
|
+
}
|
|
155
160
|
async function checkDependencies(repo, branch) {
|
|
156
161
|
const rec = await (0, github_1.readOnlinePlugin)(repo, branch);
|
|
157
162
|
if (!rec)
|
package/src/config.js
CHANGED
|
@@ -178,11 +178,10 @@ const saveDebounced = (0, misc_1.debounceAsync)(async () => {
|
|
|
178
178
|
await (0, misc_1.wait)(100);
|
|
179
179
|
// keep backup
|
|
180
180
|
const bak = filePath + '.bak';
|
|
181
|
-
const aWeekAgo = Date.now() -
|
|
181
|
+
const aWeekAgo = Date.now() - misc_1.DAY * 7;
|
|
182
182
|
if (await (0, promises_1.stat)(bak).then(x => aWeekAgo > Number(x.mtime || x.ctime), () => true))
|
|
183
183
|
await (0, promises_1.copyFile)(filePath, bak).catch(() => { }); // ignore errors
|
|
184
|
-
|
|
185
|
-
save(filePath, txt)
|
|
184
|
+
await save(yaml_1.default.stringify({ ...state, version: const_1.VERSION }, { lineWidth: 1000 }))
|
|
186
185
|
.catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
|
|
187
186
|
});
|
|
188
187
|
const saveConfigAsap = () => void (saveDebounced());
|
package/src/const.js
CHANGED
|
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.
|
|
30
|
+
exports.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.RUNNING_BETA = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
|
|
31
31
|
const minimist_1 = __importDefault(require("minimist"));
|
|
32
32
|
const fs = __importStar(require("fs"));
|
|
33
33
|
const os_1 = require("os");
|
|
@@ -38,11 +38,10 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
|
|
|
38
38
|
exports.ORIGINAL_CWD = process.cwd();
|
|
39
39
|
exports.HFS_STARTED = new Date();
|
|
40
40
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
41
|
-
exports.BUILD_TIMESTAMP = "2023-09-
|
|
41
|
+
exports.BUILD_TIMESTAMP = "2023-09-16T14:11:25.343Z";
|
|
42
42
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
43
43
|
exports.VERSION = pkg.version;
|
|
44
44
|
exports.RUNNING_BETA = exports.VERSION.includes('-');
|
|
45
|
-
exports.DAY = 86400000;
|
|
46
45
|
exports.API_VERSION = 8.3;
|
|
47
46
|
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
|
|
48
47
|
exports.HFS_REPO = 'rejetto/hfs';
|
|
@@ -99,3 +98,4 @@ else if (!winExe) { // still considering whether to use this behavior with Windo
|
|
|
99
98
|
process.chdir(dir);
|
|
100
99
|
}
|
|
101
100
|
console.log('cwd', process.cwd());
|
|
101
|
+
console.log('node', process.version);
|
package/src/cross.js
CHANGED
|
@@ -3,13 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.basename = exports.pendingPromise = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.enforceFinal = exports.objSameKeys = exports.wait = exports.prefix = exports.formatBytes = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
|
|
6
|
+
exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.basename = exports.pendingPromise = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.enforceFinal = exports.objSameKeys = exports.wait = exports.prefix = exports.formatBytes = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
|
|
7
7
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
8
8
|
// all content here is shared between client and server
|
|
9
9
|
const lodash_1 = __importDefault(require("lodash"));
|
|
10
10
|
exports.REPO_URL = 'https://github.com/rejetto/hfs/';
|
|
11
11
|
exports.WIKI_URL = exports.REPO_URL + 'wiki/';
|
|
12
12
|
exports.MINUTE = 60000;
|
|
13
|
+
exports.HOUR = 60 * exports.MINUTE;
|
|
14
|
+
exports.DAY = 24 * exports.HOUR;
|
|
13
15
|
const MULTIPLIERS = ['', 'K', 'M', 'G', 'T'];
|
|
14
16
|
function formatBytes(n, { post = 'B', k = 1024, digits = NaN } = {}) {
|
|
15
17
|
if (isNaN(Number(n)) || n < 0)
|
|
@@ -219,3 +221,30 @@ function repeat(every, cb) {
|
|
|
219
221
|
Promise.allSettled([cb()]).then(() => setTimeout(() => repeat(every, cb), every));
|
|
220
222
|
}
|
|
221
223
|
exports.repeat = repeat;
|
|
224
|
+
function formatTimestamp(x) {
|
|
225
|
+
return x ? new Date(x).toLocaleString() : '-';
|
|
226
|
+
}
|
|
227
|
+
exports.formatTimestamp = formatTimestamp;
|
|
228
|
+
function isPrimitive(x) {
|
|
229
|
+
return !x || Object(x) !== x;
|
|
230
|
+
}
|
|
231
|
+
exports.isPrimitive = isPrimitive;
|
|
232
|
+
function isIP(address) {
|
|
233
|
+
return /^([.:\da-f]+)$/i.test(address);
|
|
234
|
+
}
|
|
235
|
+
exports.isIP = isIP;
|
|
236
|
+
function isWindowsDrive(s) {
|
|
237
|
+
return s && /^[a-zA-Z]:$/.test(s);
|
|
238
|
+
}
|
|
239
|
+
exports.isWindowsDrive = isWindowsDrive;
|
|
240
|
+
function isEqualLax(a, b) {
|
|
241
|
+
return a == b //eslint-disable-line
|
|
242
|
+
|| (a && b && typeof a === 'object' && typeof b === 'object'
|
|
243
|
+
&& Object.entries(a).every(([k, v]) => isEqualLax(v, b[k])));
|
|
244
|
+
}
|
|
245
|
+
exports.isEqualLax = isEqualLax;
|
|
246
|
+
function xlate(input, table) {
|
|
247
|
+
var _a;
|
|
248
|
+
return (_a = table[input]) !== null && _a !== void 0 ? _a : input;
|
|
249
|
+
}
|
|
250
|
+
exports.xlate = xlate;
|
package/src/debounceAsync.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
// like lodash.debounce, but also avoids async invocations to overlap
|
|
5
5
|
function debounceAsync(callback, wait = 100, options = {}) {
|
|
6
|
-
const { leading = false, maxWait = Infinity, cancelable = false } = options;
|
|
6
|
+
const { leading = false, maxWait = Infinity, cancelable = false, retain = 0, retainFailure } = options;
|
|
7
7
|
let started = 0; // latest callback invocation
|
|
8
8
|
let runningCallback; // latest callback invocation result
|
|
9
9
|
let runningDebouncer; // latest wrapper invocation
|
|
10
10
|
let waitingSince = 0; // we are delaying invocation since
|
|
11
11
|
let whoIsWaiting; // args object identifies the pending instance, and incidentally stores args
|
|
12
|
+
let last;
|
|
13
|
+
let lastFailed = false;
|
|
14
|
+
let lastSince = 0;
|
|
12
15
|
const interceptingWrapper = (...args) => runningDebouncer = debouncer(...args);
|
|
13
16
|
return Object.assign(interceptingWrapper, {
|
|
14
17
|
flush: () => runningCallback !== null && runningCallback !== void 0 ? runningCallback : exec(),
|
|
@@ -22,10 +25,13 @@ function debounceAsync(callback, wait = 100, options = {}) {
|
|
|
22
25
|
async function debouncer(...args) {
|
|
23
26
|
if (runningCallback)
|
|
24
27
|
return runningCallback;
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
if (last && now - lastSince < (lastFailed ? retainFailure !== null && retainFailure !== void 0 ? retainFailure : retain : retain))
|
|
30
|
+
return await last;
|
|
25
31
|
whoIsWaiting = args;
|
|
26
|
-
waitingSince || (waitingSince =
|
|
27
|
-
const waitingCap = maxWait - (
|
|
28
|
-
const waitFor = Math.min(waitingCap, leading ? wait - (
|
|
32
|
+
waitingSince || (waitingSince = now);
|
|
33
|
+
const waitingCap = maxWait - (now - (waitingSince || started));
|
|
34
|
+
const waitFor = Math.min(waitingCap, leading ? wait - (now - started) : wait);
|
|
29
35
|
if (waitFor > 0)
|
|
30
36
|
await new Promise(resolve => setTimeout(resolve, waitFor));
|
|
31
37
|
if (!whoIsWaiting) // canceled
|
|
@@ -44,6 +50,9 @@ function debounceAsync(callback, wait = 100, options = {}) {
|
|
|
44
50
|
return await runningCallback; // await necessary to go-finally at the right time and even on exceptions
|
|
45
51
|
}
|
|
46
52
|
finally {
|
|
53
|
+
last = runningCallback;
|
|
54
|
+
last.then(() => lastFailed = false, () => lastFailed = true);
|
|
55
|
+
lastSince = Date.now();
|
|
47
56
|
whoIsWaiting = undefined;
|
|
48
57
|
runningCallback = undefined;
|
|
49
58
|
}
|