hfs 0.37.0 → 0.38.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/README.md +5 -6
- package/admin/assets/index-c7c100fa.js +509 -0
- package/admin/assets/index-fe2c1463.css +1 -0
- package/{frontend/assets/sha512-065c5d7c.js → admin/assets/sha512-7ecaaeed.js} +1 -1
- package/admin/index.html +4 -5
- package/frontend/assets/index-4b763f31.js +94 -0
- package/frontend/assets/index-6d4e81f7.css +1 -0
- package/{admin/assets/sha512-9a451918.js → frontend/assets/sha512-57b80c96.js} +1 -1
- package/frontend/index.html +3 -7
- package/package.json +3 -2
- package/plugins/download-counter/public/style.css +1 -1
- package/src/adminApis.js +23 -20
- package/src/api.monitor.js +3 -1
- package/src/api.plugins.js +1 -1
- package/src/api.vfs.js +1 -1
- package/src/config.js +2 -2
- package/src/connections.js +1 -0
- package/src/const.js +2 -2
- package/src/customHtml.js +54 -0
- package/src/frontEndApis.js +2 -5
- package/src/github.js +1 -1
- package/src/listen.js +15 -5
- package/src/middlewares.js +1 -1
- package/src/misc.js +29 -5
- package/src/serveGuiFiles.js +52 -13
- package/src/throttler.js +6 -5
- package/src/upload.js +23 -1
- package/admin/assets/index-3867a36f.js +0 -415
- package/admin/assets/index-a21d9024.css +0 -1
- package/frontend/assets/index-216f7ea9.css +0 -1
- package/frontend/assets/index-420a7286.js +0 -85
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@charset "UTF-8";:root{height:100dvh;--bg: #fff;--text: #555;--ghost-contrast: #8882;--faint-contrast: #8884;--mild-contrast: #8886;--good-contrast: #000a;--button-bg: #68a;--button-text: #eaeaea;--focus-color: #468;--separator: " \2013 "}:root .theme-dark{--bg: #000;--text: #999;--good-contrast: #fffa;--button-bg: #345;--button-text: #999;color-scheme:dark}:root .theme-dark a{color:#8ac}:root .theme-dark .dialog-closer{background:#633}:root .theme-dark .dialog-icon{color:#ccc}:root .theme-dark .dialog-icon .icon{color:#aaa;margin-left:-1px;font-size:95%}:root .theme-dark .dialog-backdrop{background:rgba(51,51,51,.7333333333)}:root .theme-dark .error-msg{color:#b88;background-color:#623}:root .theme-dark button.toggled{color:#eee}body{background:var(--bg);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,button,select,input{font-size:12pt}#root{max-width:50em;margin:auto;min-height:100vh;display:flex;flex-direction:column}body,input{color:var(--text)}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}input:not([type=checkbox]),select{padding:.3em .4em;border-radius:.5em;background:var(--bg);border-color:var(--mild-contrast);color:var(--good-contrast);max-width:100%;box-sizing:border-box;width:100%}input[type=checkbox]{transform:scale(1.7);accent-color:var(--button-bg)}label input[type=checkbox]{margin-right:.8em}select{text-align:center}.hidden{display:none!important}.icon{font-size:1.2em}.icon.mirror:before{transform:scaleX(-1)}a{text-decoration:none;color:#57a}button{background-color:var(--button-bg);color:var(--button-text);padding:.5em 1em;border:transparent;text-decoration:none;border-radius:.3em;vertical-align:middle;cursor:pointer}button.toggled{color:#fff;text-shadow:0 0 3px #fff}button:focus-visible,.breadcrumb:focus-visible{outline:3px solid var(--focus-color)}a>button{width:100%}input:focus-visible,select:focus-visible,ul a:focus-visible{border-radius:.3em;border-color:transparent;outline:2px solid var(--focus-color)}.error-msg{background-color:#faa;color:#833;padding:.5em 1em}header{position:sticky;top:0;background:var(--bg);padding:.2em;z-index:1}.before-sliding{width:0!important;flex:0!important;margin:0!important;height:0!important;padding:0!important;overflow:hidden!important;transition:all .5s}.show-sliding{transition:all .5s;overflow:clip;flex:1;white-space:nowrap}.ani-working{animation:1s blink infinite}@keyframes blink{0%{opacity:1}50%{opacity:.2}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.spinner,.icon.spinner:before{animation:1.5s spin infinite linear}.icon.emoji.spinner{display:inline-block}.breadcrumb{padding:.1em .6em .2em;line-height:1.8em;border-radius:.7em;background-color:var(--button-bg);color:var(--button-text);border-top:1px solid #666;margin-right:-.1em}.breadcrumb:nth-child(-n+3) .icon{padding:0 .2em}#folder-stats,#filter-bar>span{font-size:90%}#folder-stats{margin:.4em 0 0 .5em;float:right}#folder-stats .icon{margin-right:.3em}#filter{flex:1;box-sizing:border-box}#filter-bar{display:flex;align-items:center;gap:.8em;margin:.2em 0 0;padding:2px 0 1px 11px;height:1.8em}#filter-bar input[type=checkbox]{margin-top:.5em}#filter-bar span:empty{display:none}ul.dir{flex:1;padding:0;margin:0;clear:both}ul.dir>p{text-align:center}ul.dir li{display:block;list-style-type:none;padding:.3em .3em .4em;border-bottom:1px solid var(--faint-contrast)}ul.dir li:nth-of-type(odd){background-color:var(--ghost-contrast)}ul.dir li input[type=checkbox]{margin:0 .8em}ul.dir li a{word-break:break-word;padding-right:.3em}ul.dir li a .icon{margin-right:.3em}ul.dir li a.container-folder:hover{text-decoration:underline}ul.dir li .entry-props{float:right;font-size:90%;margin-left:4px;margin-top:.3em;font-variant-numeric:tabular-nums}ul.dir li .entry-props .entry-size-unit{margin-left:.3em}ul.dir li>div:last-of-type{clear:both}ul.dir li.page-separator{margin-top:1em;position:relative}ul.dir li.page-separator:before{content:attr(label);position:absolute;top:-1.8em;font-size:smaller;margin-left:calc(50% - 1em);opacity:.9}#menu-bar{display:flex;justify-content:space-evenly;flex-wrap:wrap}#menu-bar>*{flex:1;margin:.1em}#menu-bar button{padding-left:0;padding-right:0}#searched{margin:.2em}#user-panel{display:flex;flex-direction:column;gap:1em}#user-panel a>button{width:100%}button label{cursor:inherit;margin-left:.5em}.dialog-backdrop.working{font-size:5em;animation:1s fade-in}.dialog-content{padding:.2em}.dialog-alert .dialog-content{text-align:center}.dialog-alert .dialog-content p{text-align:left;display:inline-block}.dialog{min-width:11em;--color: var(--button-bg)}#paging{position:sticky;bottom:0;display:flex;gap:.1em;background:var(--bg);padding:0 .2em .2em}#paging>button{z-index:1}#paging button{box-shadow:0 0 .3em .3em #0003}#paging #paging-middle{padding:0 .5em;margin:0 -.3em;display:flex;gap:.5em;flex:1;overflow-x:auto}#paging #paging-middle>button{flex:1;padding-top:0;padding-bottom:0}#paging button{background:var(--button-bg);text-align:center;white-space:nowrap;padding:.5em}.upload-progress:before{content:var(--separator)}.entry-size:after{content:var(--separator)}.upload-progress{min-width:4em;display:inline-block;margin-left:.5em}.upload-list td:nth-child(1){width:0}.upload-list td:nth-child(2){text-align:right;width:0;white-space:nowrap;padding-left:.5em}.upload-list td:nth-child(3){padding:.2em .5em;word-break:break-word}.dialog-login form{display:flex;flex-direction:column;gap:1.2em}.dialog-login label{display:block;margin-bottom:.5em;margin-left:.1em}@media (min-width: 42em){body{scrollbar-width:thin;scrollbar-color:var(--button-bg) var(--ghost-contrast)}body::-webkit-scrollbar{width:12px}body::-webkit-scrollbar-track{background:var(--ghost-contrast)}body::-webkit-scrollbar-thumb{background-color:var(--button-bg);border-radius:20px;border:1px solid var(--ghost-contrast)}}@media (max-width: 42em){:root{--ghost-contrast: #8883}body,button,select{font-size:14pt}#menu-bar button label,#filter-bar button label{display:none}#filter-bar{margin-top:.4em}#filter-bar button{width:17.6vw;height:2.3em}.breadcrumb{word-break:break-all}.breadcrumb .icon{font-size:24px}}.dialog-backdrop{position:fixed;inset:0;background:#888a;display:flex;justify-content:center;align-items:center;z-index:1000}.dialog{background:#fff;background:var(--bg);padding:max(.5em,1vw);border-radius:1em;position:relative;margin:0 3vw;overflow:hidden;max-height:calc(100vh - 2em);display:flex;justify-content:center}.dialog-icon{color:#fff;background-color:var(--color);position:absolute;top:0;width:1.8em;height:1.7em;text-align:center;border-radius:.8em 0}.dialog-title{margin-top:-.4em;font-size:110%;position:absolute}.dialog-icon~.dialog-title{text-align:center}.dialog-closer{border-radius:0 .8em;right:0;padding:0;background-color:#c88}.dialog-icon~.dialog-content{margin-top:2em}.dialog-type{left:0;top:0;overflow:hidden;line-height:1.7em;opacity:.8}.dialog-content{overflow:auto;max-height:calc(100vh - 4.5em)}.dialog-content p{white-space:pre-wrap;margin:.5em 0}.dialog-confirm .dialog-content button{margin-top:1em}.dialog-alert-info{--color: #282 }.dialog-alert-warning{--color: #c91 }.dialog-alert-error{--color: #822}@media (max-width: 42em){.dialog-icon{font-size:120%}.dialog-icon~.dialog-content{margin-top:2.5em}.dialog-title{margin-top:-.2em}}.dialog-prompt label{display:block;margin-bottom:.5em;margin-left:.1em}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as SF}from"./index-
|
|
1
|
+
import{c as SF}from"./index-4b763f31.js";function OF(iF,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 iF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(iF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(iF,Symbol.toStringTag,{value:"Module"}))}var EF={},UF={get exports(){return EF},set exports(iF){EF=iF}};/*
|
|
2
2
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
3
3
|
*
|
|
4
4
|
* @version 0.8.0
|
package/frontend/index.html
CHANGED
|
@@ -4,16 +4,12 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
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
|
-
|
|
8
|
-
<script
|
|
9
|
-
<
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-420a7286.js"></script>
|
|
11
|
-
<link rel="stylesheet" href="/assets/index-216f7ea9.css">
|
|
7
|
+
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-4b763f31.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="/assets/index-6d4e81f7.css">
|
|
12
10
|
</head>
|
|
13
11
|
<body>
|
|
14
12
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
15
|
-
|
|
16
13
|
<div id="root"></div>
|
|
17
|
-
<div hidden>_HFS_PLUGINS_</div>
|
|
18
14
|
</body>
|
|
19
15
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.2",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"dist-modules": "cp package*.json dist && cd dist && npm ci --omit=dev && npm run dist-crclib && rm package-lock.json && cd .. && node prune_modules",
|
|
29
29
|
"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 ",
|
|
30
30
|
"dist-win": "cp package*.json dist && cd dist && npm ci --omit=dev && npm i -f --no-save --omit=dev @node-rs/crc32-win32 && pkg . -C gzip -t node16-win-x64",
|
|
31
|
+
"dist-mac": "cp package*.json dist && cd dist && npm ci --omit=dev && pkg . -C gzip -t node16-macos-arm64",
|
|
31
32
|
"dist-node": "npm run dist-modules && cd dist && zip hfs-node.zip -r * -x *.zip *.exe hfs-* *.log logs"
|
|
32
33
|
},
|
|
33
34
|
"engines": {
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
61
62
|
"@koa/router": "^10.1.1",
|
|
62
|
-
"@node-rs/crc32": "^1.
|
|
63
|
+
"@node-rs/crc32": "^1.6.0",
|
|
63
64
|
"basic-auth": "^2.0.1",
|
|
64
65
|
"buffer-crc32": "^0.2.13",
|
|
65
66
|
"cidr-tools": "^4.3.0",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.download-counter::after { content:
|
|
1
|
+
.download-counter::after { content: var(--separator) }
|
package/src/adminApis.js
CHANGED
|
@@ -39,7 +39,6 @@ const api_monitor_1 = __importDefault(require("./api.monitor"));
|
|
|
39
39
|
const api_lang_1 = __importDefault(require("./api.lang"));
|
|
40
40
|
const connections_1 = require("./connections");
|
|
41
41
|
const misc_1 = require("./misc");
|
|
42
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
43
42
|
const events_1 = __importDefault(require("./events"));
|
|
44
43
|
const perm_1 = require("./perm");
|
|
45
44
|
const middlewares_1 = require("./middlewares");
|
|
@@ -49,6 +48,7 @@ const readline = __importStar(require("readline"));
|
|
|
49
48
|
const log_1 = require("./log");
|
|
50
49
|
const child_process_1 = require("child_process");
|
|
51
50
|
const util_1 = require("util");
|
|
51
|
+
const customHtml_1 = require("./customHtml");
|
|
52
52
|
exports.adminApis = {
|
|
53
53
|
...api_vfs_1.default,
|
|
54
54
|
...api_accounts_1.default,
|
|
@@ -56,44 +56,47 @@ exports.adminApis = {
|
|
|
56
56
|
...api_monitor_1.default,
|
|
57
57
|
...api_lang_1.default,
|
|
58
58
|
async set_config({ values: v }) {
|
|
59
|
-
var _a
|
|
59
|
+
var _a;
|
|
60
60
|
if (v) {
|
|
61
|
-
const st = (0, listen_1.getStatus)();
|
|
62
|
-
const noHttp = ((_a = v.port) !== null && _a !== void 0 ? _a : listen_1.portCfg.get()) < 0 || !st.httpSrv.listening;
|
|
63
|
-
const noHttps = ((_b = v.https_port) !== null && _b !== void 0 ? _b : listen_1.httpsPortCfg.get()) < 0 || !st.httpsSrv.listening;
|
|
64
|
-
if (noHttp && noHttps)
|
|
65
|
-
return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN, "You cannot switch off both http and https ports");
|
|
66
61
|
await (0, config_1.setConfig)(v);
|
|
62
|
+
if (v.port === 0 || v.https_port === 0)
|
|
63
|
+
return (_a = await (0, misc_1.waitFor)(async () => {
|
|
64
|
+
const st = await (0, listen_1.getServerStatus)();
|
|
65
|
+
// wait for all random ports to be done, so we communicate new numbers
|
|
66
|
+
if ((v.port !== 0 || st.http.listening)
|
|
67
|
+
&& (v.https_port !== 0 || st.https.listening))
|
|
68
|
+
return st;
|
|
69
|
+
}, { timeout: 1000 })) !== null && _a !== void 0 ? _a : new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, "something went wrong changing ports");
|
|
67
70
|
}
|
|
68
71
|
return {};
|
|
69
72
|
},
|
|
70
73
|
get_config: config_1.getWholeConfig,
|
|
74
|
+
get_custom_html() {
|
|
75
|
+
return {
|
|
76
|
+
sections: Object.fromEntries([
|
|
77
|
+
...customHtml_1.customHtmlSections.map(k => [k, '']),
|
|
78
|
+
...customHtml_1.customHtmlState.sections
|
|
79
|
+
])
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
async set_custom_html({ sections }) {
|
|
83
|
+
await (0, customHtml_1.saveCustomHtml)(sections);
|
|
84
|
+
return {};
|
|
85
|
+
},
|
|
71
86
|
async get_status() {
|
|
72
|
-
const st = (0, listen_1.getStatus)();
|
|
73
87
|
return {
|
|
74
88
|
started: const_1.HFS_STARTED,
|
|
75
89
|
build: const_1.BUILD_TIMESTAMP,
|
|
76
90
|
version: const_1.VERSION,
|
|
77
91
|
apiVersion: const_1.API_VERSION,
|
|
78
92
|
compatibleApiVersion: const_1.COMPATIBLE_API_VERSION,
|
|
79
|
-
|
|
80
|
-
https: await serverStatus(st.httpsSrv, listen_1.httpsPortCfg.get()),
|
|
93
|
+
...await (0, listen_1.getServerStatus)(),
|
|
81
94
|
urls: (0, listen_1.getUrls)(),
|
|
82
95
|
proxyDetected: (0, middlewares_1.getProxyDetected)(),
|
|
83
96
|
frpDetected: exports.localhostAdmin.get() && !(0, middlewares_1.getProxyDetected)()
|
|
84
97
|
&& (0, connections_1.getConnections)().every(misc_1.isLocalHost)
|
|
85
98
|
&& await frpDebounced(),
|
|
86
99
|
};
|
|
87
|
-
async function serverStatus(h, configuredPort) {
|
|
88
|
-
var _a;
|
|
89
|
-
const busy = await h.busy;
|
|
90
|
-
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.
|
|
91
|
-
return {
|
|
92
|
-
...lodash_1.default.pick(h, ['listening', 'error']),
|
|
93
|
-
busy,
|
|
94
|
-
port: ((_a = h === null || h === void 0 ? void 0 : h.address()) === null || _a === void 0 ? void 0 : _a.port) || configuredPort,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
100
|
},
|
|
98
101
|
async save_pem({ cert, private_key, name = 'self' }) {
|
|
99
102
|
if (!cert || !private_key)
|
package/src/api.monitor.js
CHANGED
|
@@ -79,7 +79,9 @@ const apis = {
|
|
|
79
79
|
user: (0, perm_1.getCurrentUsername)(ctx),
|
|
80
80
|
agent: getBrowser(ctx.get('user-agent')),
|
|
81
81
|
archive: ctx.state.archive,
|
|
82
|
-
|
|
82
|
+
upload: ctx.state.uploadProgress,
|
|
83
|
+
path: ctx.state.uploadPath
|
|
84
|
+
|| (ctx.fileSource || ctx.state.archive) && ctx.path // only uploads and downloads
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
87
|
},
|
package/src/api.plugins.js
CHANGED
|
@@ -62,7 +62,7 @@ const apis = {
|
|
|
62
62
|
return {
|
|
63
63
|
enabled: plugins_1.enablePlugins.get().includes(id),
|
|
64
64
|
config: {
|
|
65
|
-
...(0, misc_1.
|
|
65
|
+
...(0, misc_1.newObj)((0, plugins_1.getPluginConfigFields)(id) || {}, v => v === null || v === void 0 ? void 0 : v.defaultValue),
|
|
66
66
|
...plugins_1.pluginsConfig.get()[id]
|
|
67
67
|
}
|
|
68
68
|
};
|
package/src/api.vfs.js
CHANGED
|
@@ -73,7 +73,7 @@ const apis = {
|
|
|
73
73
|
if ((_a = parent === null || parent === void 0 ? void 0 : parent.children) === null || _a === void 0 ? void 0 : _a.find(x => (0, vfs_1.getNodeName)(x) === props.name))
|
|
74
74
|
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, 'name already present');
|
|
75
75
|
}
|
|
76
|
-
props = (0, misc_1.
|
|
76
|
+
props = (0, misc_1.newObj)(props, v => v === null ? undefined : v); // null is a way to serialize undefined, that will restore default values
|
|
77
77
|
if (props.masks && typeof props.masks !== 'object')
|
|
78
78
|
delete props.masks;
|
|
79
79
|
Object.assign(n, props);
|
package/src/config.js
CHANGED
|
@@ -101,7 +101,7 @@ function getConfig(k) {
|
|
|
101
101
|
}
|
|
102
102
|
exports.getConfig = getConfig;
|
|
103
103
|
function getWholeConfig({ omit, only }) {
|
|
104
|
-
const defs = (0, misc_1.
|
|
104
|
+
const defs = (0, misc_1.newObj)(configProps, x => x.defaultValue);
|
|
105
105
|
let copy = lodash_1.default.defaults({}, state, defs);
|
|
106
106
|
if (omit === null || omit === void 0 ? void 0 : omit.length)
|
|
107
107
|
copy = lodash_1.default.omit(copy, omit);
|
|
@@ -113,7 +113,7 @@ exports.getWholeConfig = getWholeConfig;
|
|
|
113
113
|
// pass a value to `save` to force saving decision, or leave undefined for auto. Passing false will also reset previously loaded configs.
|
|
114
114
|
function setConfig(newCfg, save) {
|
|
115
115
|
if (!started) { // first time we consider also CLI args
|
|
116
|
-
const argCfg = lodash_1.default.pickBy((0, misc_1.
|
|
116
|
+
const argCfg = lodash_1.default.pickBy((0, misc_1.newObj)(configProps, (x, k) => const_1.argv[k]), x => x !== undefined);
|
|
117
117
|
if (!lodash_1.default.isEmpty(argCfg)) {
|
|
118
118
|
(0, exports.saveConfigAsap)().then(); // don't set `save` argument, as it would interfere below at check `save===false`
|
|
119
119
|
Object.assign(newCfg, argCfg);
|
package/src/connections.js
CHANGED
package/src/const.js
CHANGED
|
@@ -38,12 +38,12 @@ 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-
|
|
41
|
+
exports.BUILD_TIMESTAMP = "2023-03-07T12:16:23.728Z";
|
|
42
42
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
43
43
|
exports.VERSION = pkg.version;
|
|
44
44
|
exports.DAY = 86400000;
|
|
45
45
|
exports.SESSION_DURATION = exports.DAY;
|
|
46
|
-
exports.API_VERSION =
|
|
46
|
+
exports.API_VERSION = 6; // config.frontend
|
|
47
47
|
exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise is made equal to API_VERSION
|
|
48
48
|
exports.SPECIAL_URI = '/~/';
|
|
49
49
|
exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.saveCustomHtml = exports.getSection = exports.customHtmlState = exports.customHtmlSections = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const events_1 = __importDefault(require("./events"));
|
|
9
|
+
const misc_1 = require("./misc");
|
|
10
|
+
const frontEndApis_1 = require("./frontEndApis");
|
|
11
|
+
const watchLoad_1 = require("./watchLoad");
|
|
12
|
+
const valtio_1 = require("valtio");
|
|
13
|
+
const promises_1 = require("fs/promises");
|
|
14
|
+
exports.customHtmlSections = ['top', 'bottom', 'beforeHeader', 'afterHeader',
|
|
15
|
+
'afterMenuBar', 'afterEntryName', 'afterList'];
|
|
16
|
+
exports.customHtmlState = (0, valtio_1.proxy)({
|
|
17
|
+
sections: new Map()
|
|
18
|
+
});
|
|
19
|
+
const FILE = 'custom.html';
|
|
20
|
+
if (!(0, fs_1.existsSync)(FILE))
|
|
21
|
+
events_1.default.once('config ready', () => {
|
|
22
|
+
const legacy = (0, misc_1.prefix)('[beforeHeader]\n', frontEndApis_1.customHeader.get());
|
|
23
|
+
(0, fs_1.writeFileSync)(FILE, legacy);
|
|
24
|
+
frontEndApis_1.customHeader.set(undefined); // get rid of it
|
|
25
|
+
});
|
|
26
|
+
(0, watchLoad_1.watchLoad)(FILE, data => {
|
|
27
|
+
var _a;
|
|
28
|
+
const re = /^\[(\w+)] *$/gm;
|
|
29
|
+
exports.customHtmlState.sections.clear();
|
|
30
|
+
if (!data)
|
|
31
|
+
return;
|
|
32
|
+
let name = 'top';
|
|
33
|
+
do {
|
|
34
|
+
let last = re.lastIndex;
|
|
35
|
+
const match = re.exec(data);
|
|
36
|
+
const content = data.slice(last, !match ? undefined : re.lastIndex - (((_a = match === null || match === void 0 ? void 0 : match[0]) === null || _a === void 0 ? void 0 : _a.length) || 0)).trim();
|
|
37
|
+
if (content)
|
|
38
|
+
exports.customHtmlState.sections.set(name, content);
|
|
39
|
+
name = match === null || match === void 0 ? void 0 : match[1];
|
|
40
|
+
} while (name);
|
|
41
|
+
});
|
|
42
|
+
function getSection(name) {
|
|
43
|
+
return exports.customHtmlState.sections.get(name) || '';
|
|
44
|
+
}
|
|
45
|
+
exports.getSection = getSection;
|
|
46
|
+
async function saveCustomHtml(sections) {
|
|
47
|
+
const text = Object.entries(sections).filter(([k, v]) => v === null || v === void 0 ? void 0 : v.trim()).map(([k, v]) => `[${k}]\n${v}\n\n`).join('');
|
|
48
|
+
await (0, promises_1.writeFile)(FILE, text);
|
|
49
|
+
exports.customHtmlState.sections.clear();
|
|
50
|
+
for (const [k, v] of Object.entries(sections))
|
|
51
|
+
if (v)
|
|
52
|
+
exports.customHtmlState.sections.set(k, v);
|
|
53
|
+
}
|
|
54
|
+
exports.saveCustomHtml = saveCustomHtml;
|
package/src/frontEndApis.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.notifyClient = exports.frontEndApis = void 0;
|
|
30
|
+
exports.notifyClient = exports.frontEndApis = exports.customHeader = void 0;
|
|
31
31
|
const apiMiddleware_1 = require("./apiMiddleware");
|
|
32
32
|
const api_file_list_1 = require("./api.file_list");
|
|
33
33
|
const api_auth = __importStar(require("./api.auth"));
|
|
@@ -39,13 +39,10 @@ const vfs_1 = require("./vfs");
|
|
|
39
39
|
const promises_1 = require("fs/promises");
|
|
40
40
|
const path_1 = require("path");
|
|
41
41
|
const misc_1 = require("./misc");
|
|
42
|
-
|
|
42
|
+
exports.customHeader = (0, config_1.defineConfig)('custom_header');
|
|
43
43
|
exports.frontEndApis = {
|
|
44
44
|
file_list: api_file_list_1.file_list,
|
|
45
45
|
...api_auth,
|
|
46
|
-
config() {
|
|
47
|
-
return Object.fromEntries([customHeader].map(x => [x.key(), x.get()]));
|
|
48
|
-
},
|
|
49
46
|
get_notifications({ channel }, ctx) {
|
|
50
47
|
apiAssertTypes({ string: { channel } });
|
|
51
48
|
const list = new apiMiddleware_1.SendListReadable();
|
package/src/github.js
CHANGED
|
@@ -75,7 +75,7 @@ async function apiGithub(uri) {
|
|
|
75
75
|
throw res.statusCode;
|
|
76
76
|
return JSON.parse(res.body);
|
|
77
77
|
}
|
|
78
|
-
async function* searchPlugins(text) {
|
|
78
|
+
async function* searchPlugins(text = '') {
|
|
79
79
|
var _a;
|
|
80
80
|
const res = await apiGithub('search/repositories?q=topic:hfs-plugin+' + encodeURI(text));
|
|
81
81
|
for (const it of res.items) {
|
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.
|
|
30
|
+
exports.getUrls = exports.getServerStatus = exports.httpsPortCfg = exports.openAdmin = exports.portCfg = void 0;
|
|
31
31
|
const http = __importStar(require("http"));
|
|
32
32
|
const config_1 = require("./config");
|
|
33
33
|
const index_1 = require("./index");
|
|
@@ -191,13 +191,23 @@ function stopServer(srv) {
|
|
|
191
191
|
});
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
|
-
function
|
|
194
|
+
async function getServerStatus() {
|
|
195
195
|
return {
|
|
196
|
-
httpSrv,
|
|
197
|
-
httpsSrv,
|
|
196
|
+
http: await serverStatus(httpSrv, exports.portCfg.get()),
|
|
197
|
+
https: await serverStatus(httpsSrv, exports.httpsPortCfg.get()),
|
|
198
198
|
};
|
|
199
|
+
async function serverStatus(h, configuredPort) {
|
|
200
|
+
var _a;
|
|
201
|
+
const busy = await h.busy;
|
|
202
|
+
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.
|
|
203
|
+
return {
|
|
204
|
+
...lodash_1.default.pick(h, ['listening', 'error']),
|
|
205
|
+
busy,
|
|
206
|
+
port: ((_a = h === null || h === void 0 ? void 0 : h.address()) === null || _a === void 0 ? void 0 : _a.port) || configuredPort,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
199
209
|
}
|
|
200
|
-
exports.
|
|
210
|
+
exports.getServerStatus = getServerStatus;
|
|
201
211
|
const ignore = /^(lo|.*loopback.*|virtualbox.*|.*\(wsl\).*|llw\d|awdl\d|utun\d|anpi\d)$/i; // avoid giving too much information
|
|
202
212
|
function getUrls() {
|
|
203
213
|
return Object.fromEntries((0, misc_1.onlyTruthy)([httpSrv, httpsSrv].map(srv => {
|
package/src/middlewares.js
CHANGED
|
@@ -198,7 +198,7 @@ async function srpCheck(username, password) {
|
|
|
198
198
|
const paramsDecoder = async (ctx, next) => {
|
|
199
199
|
ctx.params = ctx.method === 'POST' && ctx.originalUrl.startsWith(const_1.API_URI)
|
|
200
200
|
? (0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req))
|
|
201
|
-
: (0, misc_1.
|
|
201
|
+
: (0, misc_1.newObj)(ctx.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
|
|
202
202
|
await next();
|
|
203
203
|
};
|
|
204
204
|
exports.paramsDecoder = paramsDecoder;
|
package/src/misc.js
CHANGED
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.try_ = exports.stream2string = exports.tryJson = exports.same = exports.isLocalHost = exports.with_ = exports.typedKeys = exports.objRenameKey = exports.onOff = exports.pendingPromise = exports.onlyTruthy = exports.truthy = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.randomId = exports.getOrSet = exports.wantArray = exports.wait = exports.
|
|
21
|
+
exports.try_ = exports.stream2string = exports.tryJson = exports.same = exports.isLocalHost = exports.with_ = exports.typedKeys = exports.objRenameKey = exports.onOff = exports.pendingPromise = exports.onlyTruthy = exports.truthy = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.randomId = exports.getOrSet = exports.wantArray = exports.waitFor = exports.wait = exports.newObj = exports.setHidden = exports.prefix = exports.enforceFinal = exports.debounceAsync = void 0;
|
|
22
22
|
const path_1 = require("path");
|
|
23
23
|
const lodash_1 = __importDefault(require("lodash"));
|
|
24
24
|
const assert_1 = __importDefault(require("assert"));
|
|
@@ -36,21 +36,45 @@ function prefix(pre, v, post = '') {
|
|
|
36
36
|
}
|
|
37
37
|
exports.prefix = prefix;
|
|
38
38
|
function setHidden(dest, src) {
|
|
39
|
-
return Object.defineProperties(dest,
|
|
39
|
+
return Object.defineProperties(dest, newObj(src, value => ({
|
|
40
40
|
enumerable: false,
|
|
41
41
|
writable: true,
|
|
42
42
|
value,
|
|
43
43
|
})));
|
|
44
44
|
}
|
|
45
45
|
exports.setHidden = setHidden;
|
|
46
|
-
function
|
|
47
|
-
|
|
46
|
+
function newObj(src, newValue) {
|
|
47
|
+
if (!src)
|
|
48
|
+
return {};
|
|
49
|
+
const pairs = Object.entries(src).map(([k, v]) => {
|
|
50
|
+
if (typeof k === 'symbol')
|
|
51
|
+
return;
|
|
52
|
+
let _k = k;
|
|
53
|
+
const newV = newValue(v, k, (newK) => {
|
|
54
|
+
_k = newK;
|
|
55
|
+
return true; // for convenient expression concatenation
|
|
56
|
+
});
|
|
57
|
+
return _k !== undefined && [_k, newV];
|
|
58
|
+
});
|
|
59
|
+
return Object.fromEntries(onlyTruthy(pairs));
|
|
48
60
|
}
|
|
49
|
-
exports.
|
|
61
|
+
exports.newObj = newObj;
|
|
50
62
|
function wait(ms) {
|
|
51
63
|
return new Promise(res => setTimeout(res, ms));
|
|
52
64
|
}
|
|
53
65
|
exports.wait = wait;
|
|
66
|
+
async function waitFor(cb, { interval = 200, timeout = Infinity } = {}) {
|
|
67
|
+
const started = Date.now();
|
|
68
|
+
while (1) {
|
|
69
|
+
const res = await cb();
|
|
70
|
+
if (res)
|
|
71
|
+
return res;
|
|
72
|
+
if (Date.now() - started >= timeout)
|
|
73
|
+
return;
|
|
74
|
+
await wait(interval);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.waitFor = waitFor;
|
|
54
78
|
function wantArray(x) {
|
|
55
79
|
return x == null ? [] : Array.isArray(x) ? x : [x];
|
|
56
80
|
}
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -37,11 +37,15 @@ const apiMiddleware_1 = require("./apiMiddleware");
|
|
|
37
37
|
const path_1 = require("path");
|
|
38
38
|
const misc_1 = require("./misc");
|
|
39
39
|
const adminApis_1 = require("./adminApis");
|
|
40
|
+
const valtio_1 = require("valtio");
|
|
41
|
+
const customHtml_1 = require("./customHtml");
|
|
42
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
40
43
|
// in case of dev env we have our static files within the 'dist' folder'
|
|
41
44
|
const DEV_STATIC = process.env.DEV ? 'dist/' : '';
|
|
42
45
|
function serveStatic(uri) {
|
|
43
46
|
const folder = uri.slice(2, -1); // we know folder is very similar to uri
|
|
44
|
-
|
|
47
|
+
let cache = {};
|
|
48
|
+
(0, valtio_1.subscribe)(customHtml_1.customHtmlState, () => cache = {}); // reset cache at every change
|
|
45
49
|
return async (ctx, next) => {
|
|
46
50
|
if (ctx.method === 'OPTIONS') {
|
|
47
51
|
ctx.status = const_1.HTTP_NO_CONTENT;
|
|
@@ -79,13 +83,54 @@ function adjustBundlerLinks(path, uri, data) {
|
|
|
79
83
|
async function treatIndex(ctx, body, filesUri) {
|
|
80
84
|
const session = await (0, api_auth_1.refresh_session)({}, ctx);
|
|
81
85
|
ctx.set('etag', '');
|
|
82
|
-
|
|
86
|
+
const isFrontend = filesUri === const_1.FRONTEND_URI;
|
|
87
|
+
const css = (0, plugins_1.mapPlugins)((plug, k) => { var _a; return (_a = (isFrontend ? plug.frontend_css : null)) === null || _a === void 0 ? void 0 : _a.map(f => const_1.PLUGINS_PUB_URI + k + '/' + f); }).flat().filter(Boolean);
|
|
88
|
+
const js = (0, plugins_1.mapPlugins)((plug, k) => { var _a; return (_a = (isFrontend ? plug.frontend_js : null)) === null || _a === void 0 ? void 0 : _a.map(f => const_1.PLUGINS_PUB_URI + k + '/' + f); }).flat().filter(Boolean);
|
|
89
|
+
// expose plugins' configs that are declared with 'frontend' attribute
|
|
90
|
+
const plugins = Object.fromEntries((0, misc_1.onlyTruthy)((0, plugins_1.mapPlugins)((pl, name) => {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
let configs = (0, misc_1.newObj)((0, plugins_1.getPluginConfigFields)(name), (v, k, skip) => {
|
|
93
|
+
var _a, _b, _c, _d, _e;
|
|
94
|
+
return !v.frontend ? skip() :
|
|
95
|
+
((_c = (_b = (_a = plugins_1.pluginsConfig.get()) === null || _a === void 0 ? void 0 : _a[name]) === null || _b === void 0 ? void 0 : _b[k]) !== null && _c !== void 0 ? _c : (_e = (_d = pl.getData().config) === null || _d === void 0 ? void 0 : _d[k]) === null || _e === void 0 ? void 0 : _e.defaultValue);
|
|
96
|
+
});
|
|
97
|
+
configs = ((_b = (_a = (0, plugins_1.getPluginInfo)(name)).onFrontendConfig) === null || _b === void 0 ? void 0 : _b.call(_a, configs)) || configs;
|
|
98
|
+
return !lodash_1.default.isEmpty(configs) && [name, configs];
|
|
99
|
+
})));
|
|
100
|
+
let ret = body
|
|
83
101
|
.replace(/((?:src|href) *= *['"])\/?(?![a-z]+:\/\/)/g, '$1' + filesUri)
|
|
84
|
-
.replace('
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
.replace('</head>', () => `
|
|
103
|
+
${!isFrontend ? '' : `
|
|
104
|
+
<title>${adminApis_1.title.get()}</title>
|
|
105
|
+
<link rel="icon" href="${adminApis_1.favicon.get() ? '/favicon.ico' : 'data:;'}" />
|
|
106
|
+
`}
|
|
107
|
+
<script>
|
|
108
|
+
HFS = ${JSON.stringify({
|
|
109
|
+
VERSION: const_1.VERSION,
|
|
110
|
+
API_VERSION: const_1.API_VERSION,
|
|
111
|
+
session: session instanceof apiMiddleware_1.ApiError ? null : session,
|
|
112
|
+
plugins,
|
|
113
|
+
customHtml: lodash_1.default.omit(Object.fromEntries(customHtml_1.customHtmlState.sections), ['top', 'bottom']), // excluding sections we apply in this phase
|
|
114
|
+
}, null, 4)}
|
|
115
|
+
document.documentElement.setAttribute('ver', '${const_1.VERSION.split('-')[0] /*for style selectors*/}')
|
|
116
|
+
</script>
|
|
117
|
+
<style>
|
|
118
|
+
:root {
|
|
119
|
+
${lodash_1.default.map(plugins, (configs, pluginName) => lodash_1.default.map(configs, (v, k) => `--${pluginName}-${k}: ${serializeCss(v)};`).join('\n')).join('')}
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
122
|
+
${css.map(uri => `<link rel='stylesheet' type='text/css' href='${uri}'/>`).join('\n')}
|
|
123
|
+
${js.map(uri => `<script defer src='${uri}'></script>`).join('\n')}
|
|
124
|
+
</head>`);
|
|
125
|
+
if (isFrontend)
|
|
126
|
+
ret = ret
|
|
127
|
+
.replace('<body>', '<body>' + (0, customHtml_1.getSection)('top'))
|
|
128
|
+
.replace('</body>', (0, customHtml_1.getSection)('bottom') + '</body>');
|
|
129
|
+
return ret;
|
|
130
|
+
}
|
|
131
|
+
function serializeCss(v) {
|
|
132
|
+
return typeof v === 'string' && /^#[0-9a-fA-F]{3,8}|rgba?\(.+\)$/.test(v) ? v
|
|
133
|
+
: JSON.stringify(v);
|
|
89
134
|
}
|
|
90
135
|
function serveProxied(port, uri) {
|
|
91
136
|
if (!port)
|
|
@@ -104,12 +149,6 @@ function serveProxied(port, uri) {
|
|
|
104
149
|
return proxy.apply(this, arguments);
|
|
105
150
|
};
|
|
106
151
|
}
|
|
107
|
-
function pluginsInjection() {
|
|
108
|
-
const css = (0, plugins_1.mapPlugins)((plug, k) => { var _a; return (_a = plug.frontend_css) === null || _a === void 0 ? void 0 : _a.map(f => const_1.PLUGINS_PUB_URI + k + '/' + f); }).flat().filter(Boolean);
|
|
109
|
-
const js = (0, plugins_1.mapPlugins)((plug, k) => { var _a; return (_a = plug.frontend_js) === null || _a === void 0 ? void 0 : _a.map(f => const_1.PLUGINS_PUB_URI + k + '/' + f); }).flat().filter(Boolean);
|
|
110
|
-
return css.map(uri => `\n<link rel='stylesheet' type='text/css' href='${uri}'/>`).join('')
|
|
111
|
-
+ js.map(uri => `\n<script defer src='${uri}'></script>`).join('');
|
|
112
|
-
}
|
|
113
152
|
function serveGuiFiles(proxyPort, uri) {
|
|
114
153
|
return serveProxied(proxyPort, uri) || serveStatic(uri);
|
|
115
154
|
}
|
package/src/throttler.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.totalInSpeed = exports.totalOutSpeed = exports.totalGot = exports.totalSent = exports.throttler = void 0;
|
|
7
|
+
exports.totalInSpeed = exports.totalOutSpeed = exports.totalGot = exports.totalSent = exports.roundSpeed = exports.throttler = void 0;
|
|
8
8
|
const stream_1 = require("stream");
|
|
9
9
|
const ThrottledStream_1 = require("./ThrottledStream");
|
|
10
10
|
const config_1 = require("./config");
|
|
@@ -39,7 +39,7 @@ const throttler = async (ctx, next) => {
|
|
|
39
39
|
const DELAY = 1000;
|
|
40
40
|
const update = lodash_1.default.debounce(() => {
|
|
41
41
|
const ts = conn[SymThrStr];
|
|
42
|
-
const outSpeed =
|
|
42
|
+
const outSpeed = roundSpeed(ts.getSpeed());
|
|
43
43
|
(0, connections_1.updateConnection)(conn, { outSpeed, sent: ts.getBytesSent() });
|
|
44
44
|
/* in case this stream stands still for a while (before the end), we'll have neither 'sent' or 'close' events,
|
|
45
45
|
* so who will take care to updateConnection? This artificial next-call will ensure just that */
|
|
@@ -67,9 +67,10 @@ const throttler = async (ctx, next) => {
|
|
|
67
67
|
ctx.response.length = bak;
|
|
68
68
|
};
|
|
69
69
|
exports.throttler = throttler;
|
|
70
|
-
function
|
|
70
|
+
function roundSpeed(n) {
|
|
71
71
|
return lodash_1.default.round(n, 1) || lodash_1.default.round(n, 3); // further precision if necessary
|
|
72
72
|
}
|
|
73
|
+
exports.roundSpeed = roundSpeed;
|
|
73
74
|
exports.totalSent = 0;
|
|
74
75
|
exports.totalGot = 0;
|
|
75
76
|
exports.totalOutSpeed = 0;
|
|
@@ -85,7 +86,7 @@ setInterval(() => {
|
|
|
85
86
|
lastSent = exports.totalSent;
|
|
86
87
|
const deltaGotKb = (exports.totalGot - lastGot) / 1000;
|
|
87
88
|
lastGot = exports.totalGot;
|
|
88
|
-
exports.totalOutSpeed =
|
|
89
|
-
exports.totalInSpeed =
|
|
89
|
+
exports.totalOutSpeed = roundSpeed(deltaSentKb / past);
|
|
90
|
+
exports.totalInSpeed = roundSpeed(deltaGotKb / past);
|
|
90
91
|
}, 1000);
|
|
91
92
|
events_1.default.on('connection', (c) => c.socket.on('data', data => exports.totalGot += data.length));
|
package/src/upload.js
CHANGED
|
@@ -12,6 +12,9 @@ const misc_1 = require("./misc");
|
|
|
12
12
|
const frontEndApis_1 = require("./frontEndApis");
|
|
13
13
|
const config_1 = require("./config");
|
|
14
14
|
const util_os_1 = require("./util-os");
|
|
15
|
+
const connections_1 = require("./connections");
|
|
16
|
+
const throttler_1 = require("./throttler");
|
|
17
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
15
18
|
exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after');
|
|
16
19
|
exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
|
|
17
20
|
const dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', false);
|
|
@@ -62,7 +65,8 @@ function uploadWriter(base, path, ctx) {
|
|
|
62
65
|
tempName = resumable;
|
|
63
66
|
}
|
|
64
67
|
cancelDeletion(tempName);
|
|
65
|
-
|
|
68
|
+
trackProgress();
|
|
69
|
+
ret.once('close', () => {
|
|
66
70
|
if (!ctx.req.aborted) {
|
|
67
71
|
let dest = fullPath;
|
|
68
72
|
if (dontOverwriteUploading.get() && fs_1.default.existsSync(dest)) {
|
|
@@ -87,6 +91,24 @@ function uploadWriter(base, path, ctx) {
|
|
|
87
91
|
delayedDelete(tempName, sec);
|
|
88
92
|
});
|
|
89
93
|
return ret;
|
|
94
|
+
function trackProgress() {
|
|
95
|
+
let lastGot = 0;
|
|
96
|
+
let lastGotTime = 0;
|
|
97
|
+
const conn = (0, connections_1.socket2connection)(ctx.socket);
|
|
98
|
+
if (!conn)
|
|
99
|
+
return () => { };
|
|
100
|
+
ctx.state.uploadPath = ctx.path + path;
|
|
101
|
+
(0, connections_1.updateConnection)(conn, { ctx });
|
|
102
|
+
const h = setInterval(() => {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const got = ret.bytesWritten;
|
|
105
|
+
const inSpeed = (0, throttler_1.roundSpeed)((got - lastGot) / (now - lastGotTime));
|
|
106
|
+
lastGot = got;
|
|
107
|
+
lastGotTime = now;
|
|
108
|
+
(0, connections_1.updateConnection)(conn, { inSpeed, got, uploadProgress: lodash_1.default.round(got / reqSize, 3) });
|
|
109
|
+
}, 1000);
|
|
110
|
+
ret.once('close', () => clearInterval(h));
|
|
111
|
+
}
|
|
90
112
|
function delayedDelete(path, secs, cb) {
|
|
91
113
|
clearTimeout(waitingToBeDeleted[path]);
|
|
92
114
|
waitingToBeDeleted[path] = setTimeout(() => {
|