hfs 0.48.0-alpha2 → 0.48.0-beta6
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-d266c6e7.js +536 -0
- package/admin/assets/{sha512-136b5a7c.js → sha512-7fcb77a2.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/index-ad985b09.js +94 -0
- package/frontend/assets/{sha512-549e6c3a.js → sha512-ff90a9e0.js} +1 -1
- package/frontend/fontello.css +2 -11
- package/frontend/fontello.woff2 +0 -0
- package/frontend/index.html +1 -1
- package/package.json +4 -3
- package/plugins/list-uploader/plugin.js +1 -0
- package/plugins/list-uploader/public/main.js +3 -2
- package/src/QuickZipStream.js +1 -0
- package/src/api.file_list.js +38 -37
- package/src/api.net.js +150 -46
- package/src/api.plugins.js +25 -20
- package/src/config.js +2 -3
- package/src/const.js +4 -3
- package/src/cross.js +30 -1
- package/src/debounceAsync.js +13 -4
- package/src/frontEndApis.js +6 -12
- package/src/github.js +33 -45
- 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 +31 -16
- package/src/log.js +4 -5
- package/src/middlewares.js +2 -2
- package/src/misc.js +30 -3
- package/src/upload.js +1 -1
- 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
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
|
}
|
package/src/frontEndApis.js
CHANGED
|
@@ -38,11 +38,12 @@ const vfs_1 = require("./vfs");
|
|
|
38
38
|
const promises_1 = require("fs/promises");
|
|
39
39
|
const path_1 = require("path");
|
|
40
40
|
const upload_1 = require("./upload");
|
|
41
|
+
const misc_1 = require("./misc");
|
|
41
42
|
exports.frontEndApis = {
|
|
42
43
|
get_file_list: api_file_list_1.get_file_list,
|
|
43
44
|
...api_auth,
|
|
44
45
|
get_notifications({ channel }, ctx) {
|
|
45
|
-
apiAssertTypes({ string: { channel } });
|
|
46
|
+
(0, misc_1.apiAssertTypes)({ string: { channel } });
|
|
46
47
|
const list = new apiMiddleware_1.SendListReadable();
|
|
47
48
|
list.ready(); // on chrome109 EventSource doesn't emit 'open' until something is sent
|
|
48
49
|
return list.events(ctx, {
|
|
@@ -55,7 +56,7 @@ exports.frontEndApis = {
|
|
|
55
56
|
if (typeof (uris === null || uris === void 0 ? void 0 : uris[0]) !== 'string')
|
|
56
57
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad uris');
|
|
57
58
|
return {
|
|
58
|
-
details: Promise.all(uris.map(async (uri) => {
|
|
59
|
+
details: await Promise.all(uris.map(async (uri) => {
|
|
59
60
|
if (typeof uri !== 'string')
|
|
60
61
|
return false; // false means error
|
|
61
62
|
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
@@ -67,7 +68,7 @@ exports.frontEndApis = {
|
|
|
67
68
|
};
|
|
68
69
|
},
|
|
69
70
|
async create_folder({ uri, name }, ctx) {
|
|
70
|
-
apiAssertTypes({ string: { uri, name } });
|
|
71
|
+
(0, misc_1.apiAssertTypes)({ string: { uri, name } });
|
|
71
72
|
if (!(0, util_files_1.isValidFileName)(name) || (0, util_files_1.dirTraversal)(name))
|
|
72
73
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
|
|
73
74
|
const parentNode = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
@@ -84,7 +85,7 @@ exports.frontEndApis = {
|
|
|
84
85
|
}
|
|
85
86
|
},
|
|
86
87
|
async delete({ uri }, ctx) {
|
|
87
|
-
apiAssertTypes({ string: { uri } });
|
|
88
|
+
(0, misc_1.apiAssertTypes)({ string: { uri } });
|
|
88
89
|
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
89
90
|
if (!node)
|
|
90
91
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
@@ -101,7 +102,7 @@ exports.frontEndApis = {
|
|
|
101
102
|
}
|
|
102
103
|
},
|
|
103
104
|
async rename({ uri, dest }, ctx) {
|
|
104
|
-
apiAssertTypes({ string: { uri, dest } });
|
|
105
|
+
(0, misc_1.apiAssertTypes)({ string: { uri, dest } });
|
|
105
106
|
if (dest.includes('/') || (0, util_files_1.dirTraversal)(dest))
|
|
106
107
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
|
|
107
108
|
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
@@ -130,10 +131,3 @@ function notifyClient(ctx, name, data) {
|
|
|
130
131
|
}
|
|
131
132
|
exports.notifyClient = notifyClient;
|
|
132
133
|
const NOTIFICATION_PREFIX = 'notificationChannel:';
|
|
133
|
-
function apiAssertTypes(paramsByType) {
|
|
134
|
-
for (const [types, params] of Object.entries(paramsByType))
|
|
135
|
-
for (const type of types.split('_'))
|
|
136
|
-
for (const [name, val] of Object.entries(params))
|
|
137
|
-
if (typeof val !== type)
|
|
138
|
-
throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad ' + name);
|
|
139
|
-
}
|
package/src/github.js
CHANGED
|
@@ -23,8 +23,17 @@ function downloadProgress(id, status) {
|
|
|
23
23
|
downloading[id] = status;
|
|
24
24
|
events_1.default.emit('pluginDownload_' + id, status);
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
// determine default branch, possibly without consuming api quota
|
|
27
|
+
async function getGithubDefaultBranch(repo) {
|
|
28
|
+
var _a;
|
|
29
|
+
const res = await (0, misc_1.httpString)(`https://github.com/${repo}/archive/refs/heads/main.zip`, { method: 'HEAD' });
|
|
30
|
+
return res.ok ? 'main'
|
|
31
|
+
: (_a = (await getRepoInfo(repo))) === null || _a === void 0 ? void 0 : _a.default_branch;
|
|
32
|
+
}
|
|
33
|
+
async function downloadPlugin(repo, { branch = '', overwrite = false } = {}) {
|
|
27
34
|
var _a, _b, _c;
|
|
35
|
+
if (typeof repo !== 'string')
|
|
36
|
+
repo = repo.main;
|
|
28
37
|
if (downloading[repo])
|
|
29
38
|
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, "already downloading");
|
|
30
39
|
console.log('downloading plugin', repo);
|
|
@@ -42,9 +51,7 @@ async function downloadPlugin(repo, branch = '', overwrite) {
|
|
|
42
51
|
url = customRepo.web + url;
|
|
43
52
|
return await go(url, pl === null || pl === void 0 ? void 0 : pl.id, (_c = customRepo.zipRoot) !== null && _c !== void 0 ? _c : DIST_ROOT);
|
|
44
53
|
}
|
|
45
|
-
|
|
46
|
-
if (!branch)
|
|
47
|
-
branch = rec.default_branch;
|
|
54
|
+
branch || (branch = await getGithubDefaultBranch(repo));
|
|
48
55
|
const short = repo.split('/')[1]; // second part, repo without the owner
|
|
49
56
|
if (!short)
|
|
50
57
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
|
|
@@ -75,7 +82,7 @@ async function downloadPlugin(repo, branch = '', overwrite) {
|
|
|
75
82
|
});
|
|
76
83
|
const main = (0, path_1.join)(installPath, MAIN);
|
|
77
84
|
await (0, promises_1.rename)(main + plugins_1.DISABLING_POSTFIX, main) // we are good now, restore name
|
|
78
|
-
.catch(e => { throw e.code !== 'ENOENT' ? e : new apiMiddleware_1.ApiError(const_1.
|
|
85
|
+
.catch(e => { throw e.code !== 'ENOENT' ? e : new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, "missing main file"); });
|
|
79
86
|
return folder;
|
|
80
87
|
}
|
|
81
88
|
}
|
|
@@ -109,19 +116,13 @@ async function readOnlinePlugin(repo, branch = '') {
|
|
|
109
116
|
throw Error("bad repo.main");
|
|
110
117
|
return (0, plugins_1.parsePluginSource)(main, res.body); // use 'repo' as 'id' client-side
|
|
111
118
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!res)
|
|
120
|
-
continue;
|
|
121
|
-
const pl = (0, plugins_1.parsePluginSource)(repo, res); // use 'repo' as 'id' client-side
|
|
122
|
-
pl.branch = b || undefined;
|
|
123
|
-
return pl;
|
|
124
|
-
}
|
|
119
|
+
branch || (branch = await getGithubDefaultBranch(repo));
|
|
120
|
+
const res = await readGithubFile(`${repo}/${branch}/${DIST_ROOT}/plugin.js`);
|
|
121
|
+
if (!res)
|
|
122
|
+
throw Error("missing plugin.js");
|
|
123
|
+
const pl = (0, plugins_1.parsePluginSource)(repo, res); // use 'repo' as 'id' client-side
|
|
124
|
+
pl.branch = branch;
|
|
125
|
+
return pl;
|
|
125
126
|
}
|
|
126
127
|
exports.readOnlinePlugin = readOnlinePlugin;
|
|
127
128
|
function getFolder2repo() {
|
|
@@ -148,19 +149,19 @@ async function apiGithub(uri) {
|
|
|
148
149
|
: e;
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
async function
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
async function searchPlugins(text = '') {
|
|
153
|
+
const projectInfo = await (0, exports.getProjectInfo)();
|
|
154
|
+
const list = await apiGithub('search/repositories?q=topic:hfs-plugin+' + encodeURI(text));
|
|
155
|
+
return new misc_1.AsapStream(list.items.map(async (it) => {
|
|
156
|
+
var _a, _b;
|
|
156
157
|
const repo = it.full_name;
|
|
157
158
|
if ((_a = projectInfo === null || projectInfo === void 0 ? void 0 : projectInfo.plugins_blacklist) === null || _a === void 0 ? void 0 : _a.includes(repo))
|
|
158
|
-
|
|
159
|
+
return;
|
|
159
160
|
let pl = await readOnlinePlugin(repo, it.default_branch);
|
|
160
161
|
if (!(pl === null || pl === void 0 ? void 0 : pl.apiRequired))
|
|
161
|
-
|
|
162
|
+
return; // mandatory field
|
|
162
163
|
if (pl.badApi) { // we try other branches (starting with 'api')
|
|
163
|
-
const res = await apiGithub('repos/' +
|
|
164
|
+
const res = await apiGithub('repos/' + repo + '/branches');
|
|
164
165
|
const branches = res.map((x) => x === null || x === void 0 ? void 0 : x.name)
|
|
165
166
|
.filter((x) => typeof x === 'string' && x.startsWith('api'))
|
|
166
167
|
.sort().reverse();
|
|
@@ -175,30 +176,17 @@ async function* searchPlugins(text = '') {
|
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
if (!pl || pl.badApi)
|
|
178
|
-
|
|
179
|
+
return;
|
|
179
180
|
Object.assign(pl, {
|
|
180
181
|
downloading: downloading[repo],
|
|
181
182
|
license: (_b = it.license) === null || _b === void 0 ? void 0 : _b.spdx_id,
|
|
182
183
|
}, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count', 'default_branch']));
|
|
183
|
-
|
|
184
|
-
}
|
|
184
|
+
return pl;
|
|
185
|
+
}));
|
|
185
186
|
}
|
|
186
187
|
exports.searchPlugins = searchPlugins;
|
|
187
188
|
// centralized hosted information, to be used as little as possible
|
|
188
|
-
let cache;
|
|
189
189
|
const FN = 'central.json';
|
|
190
|
-
let
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
.catch(() => latest) // fall back to latest
|
|
194
|
-
.then(x => {
|
|
195
|
-
if (!x)
|
|
196
|
-
throw x; // go catch
|
|
197
|
-
setTimeout(() => cache = undefined, const_1.DAY); // invalidate cache
|
|
198
|
-
return latest = JSON.parse(x);
|
|
199
|
-
}).catch(() => {
|
|
200
|
-
setTimeout(() => cache = undefined, 10000); // invalidate cache sooner on errors
|
|
201
|
-
return latest;
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
exports.getProjectInfo = getProjectInfo;
|
|
190
|
+
let builtIn = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', FN), 'utf8'));
|
|
191
|
+
exports.getProjectInfo = (0, misc_1.debounceAsync)(() => readGithubFile(`${const_1.HFS_REPO}/${const_1.HFS_REPO_BRANCH}/${FN}`).then(JSON.parse, () => builtIn), // fall back to latest
|
|
192
|
+
0, { retain: misc_1.DAY, retainFailure: 60000 });
|
package/src/index.js
CHANGED
|
@@ -25,11 +25,13 @@ const assert_1 = require("assert");
|
|
|
25
25
|
const lodash_1 = __importDefault(require("lodash"));
|
|
26
26
|
const misc_1 = require("./misc");
|
|
27
27
|
const koa_session_1 = __importDefault(require("koa-session"));
|
|
28
|
+
const api_net_1 = require("./api.net");
|
|
28
29
|
(0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints, don't clash
|
|
29
30
|
process.title = 'HFS ' + const_1.VERSION;
|
|
30
31
|
const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(',')) || [(0, misc_1.randomId)(30)];
|
|
31
32
|
exports.app = new koa_1.default({ keys });
|
|
32
33
|
exports.app.use(middlewares_1.someSecurity)
|
|
34
|
+
.use(api_net_1.acmeMiddleware)
|
|
33
35
|
.use((0, koa_session_1.default)({ key: 'hfs_$id', signed: true, rolling: true, sameSite: 'lax' }, exports.app))
|
|
34
36
|
.use(middlewares_1.prepareState)
|
|
35
37
|
.use(middlewares_1.gzipper)
|
package/src/langs/embedded.js
CHANGED
|
@@ -17,4 +17,5 @@ const hfs_lang_es_json_1 = __importDefault(require("./hfs-lang-es.json"));
|
|
|
17
17
|
const hfs_lang_nl_json_1 = __importDefault(require("./hfs-lang-nl.json"));
|
|
18
18
|
const hfs_lang_el_json_1 = __importDefault(require("./hfs-lang-el.json"));
|
|
19
19
|
const hfs_lang_de_json_1 = __importDefault(require("./hfs-lang-de.json"));
|
|
20
|
-
|
|
20
|
+
const hfs_lang_fi_json_1 = __importDefault(require("./hfs-lang-fi.json"));
|
|
21
|
+
exports.default = { it: hfs_lang_it_json_1.default, zh: hfs_lang_zh_json_1.default, ru: hfs_lang_ru_json_1.default, sr: hfs_lang_sr_json_1.default, ko: hfs_lang_ko_json_1.default, ms: hfs_lang_ms_json_1.default, 'zh-tw': hfs_lang_zh_tw_json_1.default, fr: hfs_lang_fr_json_1.default, pt: hfs_lang_pt_json_1.default, vi: hfs_lang_vi_json_1.default, es: hfs_lang_es_json_1.default, nl: hfs_lang_nl_json_1.default, el: hfs_lang_el_json_1.default, de: hfs_lang_de_json_1.default, fi: hfs_lang_fi_json_1.default };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "Pultsari",
|
|
3
|
+
"version": 1.0,
|
|
4
|
+
"hfs_version": "0.47.0",
|
|
5
|
+
"translate": {
|
|
6
|
+
"Select": "Valitse",
|
|
7
|
+
"n_files": "{n,plural,one{# tiedosto} other{# tiedostoa}}",
|
|
8
|
+
"n_folders": "{n,plural,one{# kansio} other{# kansiota}}",
|
|
9
|
+
"filter_count": "{n,plural, one{# suodatettu} other{# suodatettu}}",
|
|
10
|
+
"select_count": "{n,plural, one{# valittu} other{# valittu}}",
|
|
11
|
+
"filter_placeholder": "Kirjoita tähän suodattaaksesi listasta",
|
|
12
|
+
"Select some files": "Valitse joitakin tiedostoja tai kansioita",
|
|
13
|
+
"zip_checkboxes": "Käytä valintaruutuja valitaksesi ladattavat tiedostot ja kansiot, sitten voit ladata pakatun zip tiedoston",
|
|
14
|
+
"zip_tooltip_selected": "Lataa valitut tiedostot ja kansiot pakatussa zip tiedostossa",
|
|
15
|
+
"zip_tooltip_whole": "Lataa ja pakkaa kaikki (suodattamattomana) yhtenä zip tiedostona. Jos valitset vain joitakin tiedostoja tai kansioita, pakataan ja ladataan vain ne.",
|
|
16
|
+
"zip_confirm_search": "Haluatko ladata kaikki haun tulokset pakattuna zip tiedostona?",
|
|
17
|
+
"zip_confirm_folder": "Haluatko ladata aivan kaiken pakattuna zip tiedostona?",
|
|
18
|
+
"select_tooltip": "Valinnat koskevat pakattua zip tiedostoa ja tiedostojen poistamista (kun sallittu), sen lisäksi voit suodattaa listaa.",
|
|
19
|
+
"delete_hint": "Poistaaksesi tiedostoja tai kansioita, klikkaa ensin Valitse yläpalkista",
|
|
20
|
+
"delete_confirm": "Poista {n,plural, one{# valittu tiedosto tai kansio} other{# valittua tiedostoa tai / ja kansiota}}?",
|
|
21
|
+
"delete_completed": "{n} tiedostoa tai / ja kansiota poistettu",
|
|
22
|
+
"delete_failed": ", {n,plural, one{# epäonnistui} other{# epäonnistui}}",
|
|
23
|
+
"delete_select": "Valitse tiedostoja tai / ja kansioita poistettavaksi",
|
|
24
|
+
"Delete": "Poista",
|
|
25
|
+
"Options": "Asetukset",
|
|
26
|
+
"Search": "Haku",
|
|
27
|
+
"Zip": "Zip",
|
|
28
|
+
"search_msg": "Hae tästä kansiosta ja alikansioista",
|
|
29
|
+
"Searching": "Haetaan",
|
|
30
|
+
"Searched": "Haettu",
|
|
31
|
+
"Clear search": "Tyhjennä haku",
|
|
32
|
+
"Interrupted": "Keskeytetty",
|
|
33
|
+
"stopped_before": "pysäytetty ennen",
|
|
34
|
+
"empty_list": "Tyhjä",
|
|
35
|
+
"filter_none": "Ei tuloksia",
|
|
36
|
+
"Admin-panel": "Hallintapaneeli",
|
|
37
|
+
"Login": "Kirjaudu",
|
|
38
|
+
"Username": "Käyttäjätunnus",
|
|
39
|
+
"Password": "Salasana",
|
|
40
|
+
"login_untrusted": "Kirjautuminen keskeytetty:: palvelimen identiteettiin ei voida luottaa",
|
|
41
|
+
"login_bad_credentials": "Käyttäjätunnus tai salasana virheellinen.",
|
|
42
|
+
"login_bad_cookies": "Evästeet eivät toimi - kirjautuminen epäonnistui",
|
|
43
|
+
"User panel": "Hallintapaneeli",
|
|
44
|
+
"Change password": "Vaihda salasana",
|
|
45
|
+
"enter_pass": "Kirjoita uusi salasana",
|
|
46
|
+
"enter_pass2": "Kirjoita uusi salasana uudelleen",
|
|
47
|
+
"pass2_mismatch": "Toinen kirjoittamasi salasana ei vastannut ensimmäistä. Salasanan vaihtaminen keskeytetty.",
|
|
48
|
+
"password_changed": "Salasana vaihdettu",
|
|
49
|
+
"Logout": "Kirjaudu ulos",
|
|
50
|
+
"connection error": "Yhteysvirhe",
|
|
51
|
+
"Full timestamp:": "Täysi aikaleima:",
|
|
52
|
+
"Search was interrupted": "Haku keskeytetty",
|
|
53
|
+
"Stop list": "Pysäytä listaus",
|
|
54
|
+
"upload_starting": "Aloitetaan lataaminen",
|
|
55
|
+
"wrong_account": "Tilillä {u} ei ole pääsyä, kokeile toista",
|
|
56
|
+
"no_upload_here": "Ei lähetysoikeutta tähän kansioon",
|
|
57
|
+
"Create folder": "Tee uusi kansio",
|
|
58
|
+
"Pick files": "Valitse lähetettävät tiedostot",
|
|
59
|
+
"Pick folder": "Valitse lähetettävä kansio",
|
|
60
|
+
"send_files": "Lähetä {n,plural,one{# tiedosto} other{# tiedostot}}, {size}",
|
|
61
|
+
"Clear": "Tyhjennä",
|
|
62
|
+
"failed_upload": "Lähetys epäonnistui {name}",
|
|
63
|
+
"confirm_resume": "Jatketaanko lähetystä?",
|
|
64
|
+
"file too large": "Tiedoston koko on liian suuri",
|
|
65
|
+
"Enter folder name": "Kirjoita kansion nimi",
|
|
66
|
+
"Successfully created": "Kansio luotu onnistuneesti",
|
|
67
|
+
"enter_folder": "Siirry kansioon",
|
|
68
|
+
"folder_exists": "Kansio samalla nimellä on jo olemassa",
|
|
69
|
+
"Sort by:": "Lajittele: {by}",
|
|
70
|
+
"name": "nimen mukaan",
|
|
71
|
+
"extension": "tiedostopäätteen mukaan",
|
|
72
|
+
"size": "koon mukaan",
|
|
73
|
+
"time": "ajan mukaan",
|
|
74
|
+
"Invert order": "Käänteinen järjestys",
|
|
75
|
+
"Folders first": "Kansiot ensin",
|
|
76
|
+
"Numeric names": "Numeroidut nimet",
|
|
77
|
+
"theme:": "Teema:",
|
|
78
|
+
"auto": "auto",
|
|
79
|
+
"light": "vaalea",
|
|
80
|
+
"dark": "tumma",
|
|
81
|
+
"parent folder": "Ylempi kansio",
|
|
82
|
+
"home": "Koti",
|
|
83
|
+
"Continue": "Jatka",
|
|
84
|
+
"Confirm": "Vahvista",
|
|
85
|
+
"Don't": "Älä",
|
|
86
|
+
"Warning": "Varoitus",
|
|
87
|
+
"Error": "Virhe",
|
|
88
|
+
"Info": "Tiedoksi",
|
|
89
|
+
"Unauthorized": "Ei lupaa",
|
|
90
|
+
"Forbidden": "Kielletty",
|
|
91
|
+
"Not found": "Ei löydy",
|
|
92
|
+
"Server error": "Palvelinvirhe",
|
|
93
|
+
"Upload": "Lähetä",
|
|
94
|
+
"upload_concluded": "Lähetys päättyi:",
|
|
95
|
+
"upload_finished": "{n} tiedostoa tai kansiota lähetetty onnistuneesti ({size})",
|
|
96
|
+
"upload_errors": "{n} epäonnistui",
|
|
97
|
+
"upload_file_rejected": "Joitakin tiedostoja ei hyväksytty",
|
|
98
|
+
"File menu": "Tiedostovalikko",
|
|
99
|
+
"Folder menu": "Kansiovalikko",
|
|
100
|
+
"Name": "Nimi",
|
|
101
|
+
"file_open": "Avaa",
|
|
102
|
+
"Download": "Lataa",
|
|
103
|
+
"Missing permission": "Oikeudet puuuttuvat",
|
|
104
|
+
"Reload": "Päivitä",
|
|
105
|
+
"Get list": "Näytä listana",
|
|
106
|
+
"Skip existing files": "Ohita jo olemassa olevat tiedostot",
|
|
107
|
+
"Size": "Koko",
|
|
108
|
+
"Timestamp": "Aikaleima",
|
|
109
|
+
"Show": "Näytä",
|
|
110
|
+
"Loading failed": "Lataaminen epäonnistui",
|
|
111
|
+
"Rename": "Nimeä uudelleen",
|
|
112
|
+
"Tiles mode:": "Kuvakkeiden koko:",
|
|
113
|
+
"off": "pienin",
|
|
114
|
+
"Operation successful": "Suoritettu onnistuneesti",
|
|
115
|
+
"Uploader": "Lähettäjä",
|
|
116
|
+
"Download counter": "Latauslaskuri",
|
|
117
|
+
"Switch zoom mode": "Vaihda zoomaus tilaa",
|
|
118
|
+
"Full screen": "Koko näyttö",
|
|
119
|
+
"File Show help": "Ohjeita",
|
|
120
|
+
"showHelpMain": "Voit käyttää näppäimistöä joihinkin toimintoihin:",
|
|
121
|
+
"showHelp_←/→": "←/→",
|
|
122
|
+
"showHelp_↑/↓": "↑/↓",
|
|
123
|
+
"showHelp_space": "välilyönti",
|
|
124
|
+
"showHelp_←/→_body": "siirry edelliseen/seuraavaan tiedostoon",
|
|
125
|
+
"showHelp_↑/↓_body": "vieritä korkeita kuvia",
|
|
126
|
+
"showHelp_space_body": "valitse",
|
|
127
|
+
"showHelp_D_body": "lataa",
|
|
128
|
+
"showHelp_Z_body": "vaihda zoomauksen tilaa",
|
|
129
|
+
"showHelp_F_body": "koko näyttö"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -119,6 +119,8 @@
|
|
|
119
119
|
"showHelp_D_body": "download",
|
|
120
120
|
"showHelp_Z_body": "cambia modalità zoom",
|
|
121
121
|
"showHelp_F_body": "pieno schermo",
|
|
122
|
+
"Destination": "Destinazione",
|
|
123
|
+
"in_queue": "{n} in coda",
|
|
122
124
|
"": "PLUGINS SECTION",
|
|
123
125
|
"": "PLUGIN thumbnails",
|
|
124
126
|
"Enable tiles mode": "Modalità griglia",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Mefistofell, SanokKule",
|
|
3
|
-
"version": 2.
|
|
4
|
-
"hfs_version": "0.
|
|
3
|
+
"version": 2.1,
|
|
4
|
+
"hfs_version": "0.48.0",
|
|
5
5
|
"translate": {
|
|
6
6
|
"Select": "Выбор",
|
|
7
7
|
"n_files": "{n,plural, one{1 файл} few{# файла} other{# файлов}}",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"zip_tooltip_whole": "Выбрать файлы для скачивания в zip-архиве. Если файлы не выбраны, то будет скачана вся папка",
|
|
16
16
|
"zip_confirm_search": "Скачать все результаты поиска в одном zip-архиве",
|
|
17
17
|
"zip_confirm_folder": "Скачать всю папку как zip-архив?",
|
|
18
|
-
"select_tooltip": "
|
|
18
|
+
"select_tooltip": "Позволяет выбрать файлы для скачивания или удаления, а так же использовать фильтры для быстрого поиска",
|
|
19
19
|
"delete_hint": "Для удаления файлов сначала нажмите Выбор",
|
|
20
20
|
"delete_confirm": "Удалить {n,plural, =1{элемент} one{# элемент} few{# элемента} many{# элементов} other{# элементов}}?",
|
|
21
21
|
"delete_completed": "{n,plural, one{Удалён} other{Удалено}} {n,plural, one{# элемент} few{# элемента} many{# элементов} other{# элементов}}",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"Download counter": "Счётчик загрузок",
|
|
117
117
|
"Switch zoom mode": "Переключить увеличение",
|
|
118
118
|
"Full screen": "Полный экран",
|
|
119
|
-
"File Show help": "Помощь
|
|
119
|
+
"File Show help": "Помощь",
|
|
120
120
|
"showHelpMain": "Для некоторых действий можно использовать клавиатуру:",
|
|
121
121
|
"showHelp_←/→": "←/→",
|
|
122
122
|
"showHelp_↑/↓": "↑/↓",
|
|
@@ -126,6 +126,12 @@
|
|
|
126
126
|
"showHelp_space_body": "выбрать файл",
|
|
127
127
|
"showHelp_D_body": "скачать",
|
|
128
128
|
"showHelp_Z_body": "переключение увеличения",
|
|
129
|
-
"showHelp_F_body": "полноэкранный режим"
|
|
129
|
+
"showHelp_F_body": "полноэкранный режим",
|
|
130
|
+
"Destination": "Назначение",
|
|
131
|
+
"in_queue": "{n} в очереди",
|
|
132
|
+
"": "PLUGINS SECTION",
|
|
133
|
+
"": "PLUGIN thumbnails",
|
|
134
|
+
"Enable tiles mode": "Просмотр в режиме плиток",
|
|
135
|
+
"thumbnails_switchBack": "Изменить вид можно в настройках"
|
|
130
136
|
}
|
|
131
137
|
}
|
package/src/listen.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.getUrls = exports.getIps = exports.getServerStatus = exports.httpsPortCfg = exports.openAdmin = exports.portCfg = exports.getHttpsWorkingPort = void 0;
|
|
30
|
+
exports.getUrls = exports.getIps = exports.getServerStatus = exports.stopServer = exports.startServer = exports.httpsPortCfg = exports.privateKey = exports.cert = exports.getCertObject = exports.openAdmin = exports.portCfg = exports.getHttpsWorkingPort = void 0;
|
|
31
31
|
const http = __importStar(require("http"));
|
|
32
32
|
const config_1 = require("./config");
|
|
33
33
|
const index_1 = require("./index");
|
|
@@ -51,12 +51,13 @@ function getHttpsWorkingPort() {
|
|
|
51
51
|
return (httpsSrv === null || httpsSrv === void 0 ? void 0 : httpsSrv.listening) && ((_a = httpsSrv.address()) === null || _a === void 0 ? void 0 : _a.port);
|
|
52
52
|
}
|
|
53
53
|
exports.getHttpsWorkingPort = getHttpsWorkingPort;
|
|
54
|
+
const commonOptions = { requestTimeout: 0 };
|
|
54
55
|
exports.portCfg = (0, config_1.defineConfig)('port', 80);
|
|
55
56
|
exports.portCfg.sub(async (port) => {
|
|
56
57
|
while (!index_1.app)
|
|
57
58
|
await (0, misc_1.wait)(100);
|
|
58
59
|
stopServer(httpSrv).then();
|
|
59
|
-
httpSrv = Object.assign(http.createServer(index_1.app.callback()), { name: 'http' });
|
|
60
|
+
httpSrv = Object.assign(http.createServer(commonOptions, index_1.app.callback()), { name: 'http' });
|
|
60
61
|
port = await startServer(httpSrv, { port });
|
|
61
62
|
if (!port)
|
|
62
63
|
return;
|
|
@@ -82,6 +83,12 @@ function openAdmin() {
|
|
|
82
83
|
console.log("openAdmin failed");
|
|
83
84
|
}
|
|
84
85
|
exports.openAdmin = openAdmin;
|
|
86
|
+
function getCertObject() {
|
|
87
|
+
const o = new crypto_1.X509Certificate(httpsOptions.cert);
|
|
88
|
+
const some = lodash_1.default.pick(o, ['subject', 'issuer', 'validFrom', 'validTo']);
|
|
89
|
+
return (0, misc_1.objSameKeys)(some, v => (v === null || v === void 0 ? void 0 : v.includes('=')) ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
|
|
90
|
+
}
|
|
91
|
+
exports.getCertObject = getCertObject;
|
|
85
92
|
const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
86
93
|
var _a, _b, _c;
|
|
87
94
|
stopServer(httpsSrv).then();
|
|
@@ -89,10 +96,10 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
|
89
96
|
try {
|
|
90
97
|
while (!index_1.app)
|
|
91
98
|
await (0, misc_1.wait)(100);
|
|
92
|
-
httpsSrv = Object.assign(https.createServer(port === PORT_DISABLED ? {} : { key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https' });
|
|
99
|
+
httpsSrv = Object.assign(https.createServer(port === PORT_DISABLED ? {} : { ...commonOptions, key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https' });
|
|
93
100
|
if (port >= 0) {
|
|
94
|
-
const cert =
|
|
95
|
-
const cn = (_a = cert.subject
|
|
101
|
+
const cert = getCertObject();
|
|
102
|
+
const cn = (_a = cert.subject) === null || _a === void 0 ? void 0 : _a.CN;
|
|
96
103
|
if (cn)
|
|
97
104
|
console.log("certificate loaded for", cn);
|
|
98
105
|
const now = new Date();
|
|
@@ -119,9 +126,9 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
|
119
126
|
httpsSrv.on('connection', connections_1.newConnection);
|
|
120
127
|
printUrls(httpsSrv.name);
|
|
121
128
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const httpsNeeds = [cert, privateKey];
|
|
129
|
+
exports.cert = (0, config_1.defineConfig)('cert', '');
|
|
130
|
+
exports.privateKey = (0, config_1.defineConfig)('private_key', '');
|
|
131
|
+
const httpsNeeds = [exports.cert, exports.privateKey];
|
|
125
132
|
const httpsOptions = { cert: '', private_key: '' };
|
|
126
133
|
for (const cfg of httpsNeeds) {
|
|
127
134
|
let unwatch;
|
|
@@ -170,7 +177,7 @@ function startServer(srv, { port, host }) {
|
|
|
170
177
|
}
|
|
171
178
|
function listen(host) {
|
|
172
179
|
return new Promise(async (resolve, reject) => {
|
|
173
|
-
srv === null || srv === void 0 ? void 0 : srv.listen({ port, host }, () => {
|
|
180
|
+
srv === null || srv === void 0 ? void 0 : srv.on('error', onError).listen({ port, host }, () => {
|
|
174
181
|
const ad = srv.address();
|
|
175
182
|
if (!ad)
|
|
176
183
|
return reject('no address');
|
|
@@ -178,8 +185,12 @@ function startServer(srv, { port, host }) {
|
|
|
178
185
|
srv.close();
|
|
179
186
|
return reject('type of socket not supported');
|
|
180
187
|
}
|
|
188
|
+
srv.removeListener('error', onError); // necessary in case someone calls stop/start many times
|
|
181
189
|
resolve(ad.port);
|
|
182
|
-
})
|
|
190
|
+
});
|
|
191
|
+
async function onError(e) {
|
|
192
|
+
if (!srv)
|
|
193
|
+
return;
|
|
183
194
|
srv.error = String(e);
|
|
184
195
|
srv.busy = undefined;
|
|
185
196
|
const { code } = e;
|
|
@@ -191,10 +202,11 @@ function startServer(srv, { port, host }) {
|
|
|
191
202
|
const k = (srv === httpSrv ? exports.portCfg : exports.httpsPortCfg).key();
|
|
192
203
|
console.log(` >> try specifying a different port, enter this command: config ${k} 8011`);
|
|
193
204
|
resolve(0);
|
|
194
|
-
}
|
|
205
|
+
}
|
|
195
206
|
});
|
|
196
207
|
}
|
|
197
208
|
}
|
|
209
|
+
exports.startServer = startServer;
|
|
198
210
|
function stopServer(srv) {
|
|
199
211
|
return new Promise(resolve => {
|
|
200
212
|
if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
|
|
@@ -209,19 +221,22 @@ function stopServer(srv) {
|
|
|
209
221
|
});
|
|
210
222
|
});
|
|
211
223
|
}
|
|
224
|
+
exports.stopServer = stopServer;
|
|
212
225
|
async function getServerStatus() {
|
|
213
226
|
return {
|
|
214
227
|
http: await serverStatus(httpSrv, exports.portCfg.get()),
|
|
215
228
|
https: await serverStatus(httpsSrv, exports.httpsPortCfg.get()),
|
|
216
229
|
};
|
|
217
|
-
async function serverStatus(
|
|
230
|
+
async function serverStatus(srv, configuredPort) {
|
|
218
231
|
var _a;
|
|
219
|
-
const busy = await (
|
|
232
|
+
const busy = await (srv === null || srv === void 0 ? void 0 : srv.busy);
|
|
220
233
|
await (0, misc_1.wait)(0); // simple trick to wait for also .error to be updated. If this trickery becomes necessary elsewhere, then we should make also error a Promise.
|
|
221
234
|
return {
|
|
222
|
-
...lodash_1.default.pick(
|
|
235
|
+
...lodash_1.default.pick(srv, ['listening', 'error']),
|
|
223
236
|
busy,
|
|
224
|
-
port: ((_a =
|
|
237
|
+
port: ((_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port) || configuredPort,
|
|
238
|
+
configuredPort,
|
|
239
|
+
srv,
|
|
225
240
|
};
|
|
226
241
|
}
|
|
227
242
|
}
|
|
@@ -232,7 +247,7 @@ async function getIps() {
|
|
|
232
247
|
&& v4first((0, misc_1.onlyTruthy)(nets.map(net => !net.internal && net.address)))[0] // for each interface we consider only 1 address
|
|
233
248
|
)).flat();
|
|
234
249
|
const e = await api_net_1.externalIp;
|
|
235
|
-
if (e)
|
|
250
|
+
if (e && !ips.includes(e))
|
|
236
251
|
ips.unshift(e);
|
|
237
252
|
return v4first(ips)
|
|
238
253
|
.filter((x, i, a) => a.length > 1 || !x.startsWith('169.254')); // 169.254 = dhcp failure on the interface, but keep it if it's our only one
|
package/src/log.js
CHANGED
|
@@ -33,7 +33,6 @@ const config_1 = require("./config");
|
|
|
33
33
|
const fs_1 = require("fs");
|
|
34
34
|
const util = __importStar(require("util"));
|
|
35
35
|
const promises_1 = require("fs/promises");
|
|
36
|
-
const const_1 = require("./const");
|
|
37
36
|
const lodash_1 = __importDefault(require("lodash"));
|
|
38
37
|
const util_files_1 = require("./util-files");
|
|
39
38
|
const perm_1 = require("./perm");
|
|
@@ -104,9 +103,9 @@ const logMw = async (ctx, next) => {
|
|
|
104
103
|
if (rotate && last) { // rotation enabled and a file exists?
|
|
105
104
|
const passed = Number(now) - Number(last)
|
|
106
105
|
- 3600000; // be pessimistic and count a possible DST change
|
|
107
|
-
if (rotate === 'm' && (passed >= 31 *
|
|
108
|
-
|| rotate === 'd' && (passed >=
|
|
109
|
-
|| rotate === 'w' && (passed >= 7 *
|
|
106
|
+
if (rotate === 'm' && (passed >= 31 * misc_1.DAY || now.getMonth() !== last.getMonth())
|
|
107
|
+
|| rotate === 'd' && (passed >= misc_1.DAY || now.getDate() !== last.getDate()) // checking passed will solve the case when the day of the month is the same but a month has passed
|
|
108
|
+
|| rotate === 'w' && (passed >= 7 * misc_1.DAY || now.getDay() < last.getDay())) {
|
|
110
109
|
stream.end();
|
|
111
110
|
const postfix = last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate());
|
|
112
111
|
try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
|
|
@@ -126,7 +125,7 @@ const logMw = async (ctx, next) => {
|
|
|
126
125
|
const user = (0, perm_1.getCurrentUsername)(ctx);
|
|
127
126
|
const length = (_c = ctx.state.length) !== null && _c !== void 0 ? _c : ctx.length;
|
|
128
127
|
const uri = ctx.originalUrl;
|
|
129
|
-
const extra = ctx.state.includesLastByte && ctx.vfsNode && { dl: 1 }
|
|
128
|
+
const extra = ctx.state.includesLastByte && ctx.vfsNode && ctx.res.finished && { dl: 1 }
|
|
130
129
|
|| ctx.state.uploadPath && { ul: ctx.state.uploadPath, size: ctx.state.uploadSize }
|
|
131
130
|
|| undefined;
|
|
132
131
|
events_1.default.emit(logger.name, Object.assign(lodash_1.default.pick(ctx, ['ip', 'method', 'status']), { length, user, ts: now, uri, extra }));
|
package/src/middlewares.js
CHANGED
|
@@ -32,7 +32,7 @@ const config_1 = require("./config");
|
|
|
32
32
|
const lang_1 = require("./lang");
|
|
33
33
|
const forceHttps = (0, config_1.defineConfig)('force_https', true);
|
|
34
34
|
const ignoreProxies = (0, config_1.defineConfig)('ignore_proxies', false);
|
|
35
|
-
exports.sessionDuration = (0, config_1.defineConfig)('session_duration', Number(process.env.SESSION_DURATION) ||
|
|
35
|
+
exports.sessionDuration = (0, config_1.defineConfig)('session_duration', Number(process.env.SESSION_DURATION) || misc_1.DAY / 1000, v => v * 1000);
|
|
36
36
|
exports.gzipper = (0, koa_compress_1.default)({
|
|
37
37
|
threshold: 2048,
|
|
38
38
|
gzip: { flush: zlib_1.constants.Z_SYNC_FLUSH },
|
|
@@ -213,7 +213,7 @@ const someSecurity = async (ctx, next) => {
|
|
|
213
213
|
exports.someSecurity = someSecurity;
|
|
214
214
|
// limited to http proxies
|
|
215
215
|
function getProxyDetected() {
|
|
216
|
-
if ((proxyDetected === null || proxyDetected === void 0 ? void 0 : proxyDetected.state.when) < Date.now() -
|
|
216
|
+
if ((proxyDetected === null || proxyDetected === void 0 ? void 0 : proxyDetected.state.when) < Date.now() - misc_1.DAY)
|
|
217
217
|
proxyDetected = undefined;
|
|
218
218
|
return !ignoreProxies.get() && proxyDetected
|
|
219
219
|
&& { from: proxyDetected.ip, for: proxyDetected.get('X-Forwarded-For') };
|