hfs 0.1.6 → 0.26.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/LICENSE.txt +674 -0
- package/README.md +102 -8
- package/admin/assets/index.dcc78777.css +1 -0
- package/admin/assets/index.f056db34.js +282 -0
- package/admin/assets/sha512.3c0e384c.js +8 -0
- package/admin/index.html +17 -0
- package/admin/logo.svg +36 -0
- package/frontend/assets/index.55c710c2.js +85 -0
- package/frontend/assets/index.ee805a6c.css +1 -0
- package/frontend/assets/sha512.634b743e.js +8 -0
- package/frontend/fontello.css +77 -0
- package/frontend/fontello.woff2 +0 -0
- package/frontend/index.html +18 -0
- package/package.json +93 -28
- package/plugins/antibrute/plugin.js +38 -0
- package/plugins/download-counter/plugin.js +47 -0
- package/plugins/download-counter/public/hits.js +5 -0
- package/plugins/updater-disabled/plugin.js +44 -0
- package/plugins/vhosting/plugin.js +42 -0
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +130 -0
- package/src/api.file_list.js +103 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +102 -0
- package/src/api.plugins.js +125 -0
- package/src/api.vfs.js +164 -0
- package/src/apiMiddleware.js +136 -0
- package/src/block.js +33 -0
- package/src/commands.js +105 -0
- package/src/config.js +172 -0
- package/src/connections.js +57 -0
- package/src/const.js +83 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +48 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +102 -0
- package/src/index.js +53 -0
- package/src/listen.js +226 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +154 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +176 -0
- package/src/plugins.js +338 -0
- package/src/serveFile.js +104 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +29 -0
- package/src/throttler.js +91 -0
- package/src/update.js +69 -0
- package/src/util-files.js +141 -0
- package/src/util-generators.js +30 -0
- package/src/util-http.js +30 -0
- package/src/vfs.js +227 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +69 -0
- package/.npmignore +0 -19
- package/admin-server.js +0 -212
- package/cli.js +0 -33
- package/file-server.js +0 -100
- package/lib/common.js +0 -10
- package/lib/extending.js +0 -158
- package/lib/mime.js +0 -19
- package/lib/misc.js +0 -75
- package/lib/serving.js +0 -81
- package/lib/vfs.js +0 -403
- package/main.js +0 -24
- package/note.txt +0 -104
- package/speedtest.js +0 -21
- package/static/backend.css +0 -14
- package/static/backend.html +0 -32
- package/static/backend.js +0 -694
- package/static/extending.js +0 -187
- package/static/frontend.css +0 -29
- package/static/frontend.html +0 -23
- package/static/frontend.js +0 -230
- package/static/icons/files/archive.png +0 -0
- package/static/icons/files/audio.png +0 -0
- package/static/icons/files/file.png +0 -0
- package/static/icons/files/folder.png +0 -0
- package/static/icons/files/image.png +0 -0
- package/static/icons/files/link.png +0 -0
- package/static/icons/files/video.png +0 -0
- package/static/jquery.js +0 -4
- package/static/jquery.rule-1.0.2.js +0 -273
- package/static/misc.js +0 -194
- package/static/tpl.js +0 -17
- package/todo.txt +0 -25
package/src/plugins.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.parsePluginSource = exports.rescan = exports.pluginsConfig = exports.enablePlugins = exports.pluginsWatcher = exports.getAvailablePlugins = exports.Plugin = exports.pluginsMiddleware = exports.getPluginConfigFields = exports.mapPlugins = exports.getPluginInfo = exports.setPluginConfig = exports.enablePlugin = exports.isPluginRunning = exports.DISABLING_POSTFIX = exports.PATH = void 0;
|
|
31
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
32
|
+
const watchLoad_1 = require("./watchLoad");
|
|
33
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
34
|
+
const path_1 = __importStar(require("path"));
|
|
35
|
+
const const_1 = require("./const");
|
|
36
|
+
const Const = __importStar(require("./const"));
|
|
37
|
+
const misc_1 = require("./misc");
|
|
38
|
+
const config_1 = require("./config");
|
|
39
|
+
const serveFile_1 = require("./serveFile");
|
|
40
|
+
const events_1 = __importDefault(require("./events"));
|
|
41
|
+
const promises_1 = require("fs/promises");
|
|
42
|
+
const fs_1 = require("fs");
|
|
43
|
+
const connections_1 = require("./connections");
|
|
44
|
+
exports.PATH = 'plugins';
|
|
45
|
+
exports.DISABLING_POSTFIX = '-disabled';
|
|
46
|
+
const plugins = {};
|
|
47
|
+
function isPluginRunning(id) {
|
|
48
|
+
var _a;
|
|
49
|
+
return (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.started;
|
|
50
|
+
}
|
|
51
|
+
exports.isPluginRunning = isPluginRunning;
|
|
52
|
+
function enablePlugin(id, state = true) {
|
|
53
|
+
exports.enablePlugins.set(arr => arr.includes(id) === state ? arr
|
|
54
|
+
: state ? [...arr, id]
|
|
55
|
+
: arr.filter((x) => x !== id));
|
|
56
|
+
}
|
|
57
|
+
exports.enablePlugin = enablePlugin;
|
|
58
|
+
// nullish values are equivalent to defaultValues
|
|
59
|
+
function setPluginConfig(id, changes) {
|
|
60
|
+
exports.pluginsConfig.set(allConfigs => {
|
|
61
|
+
const fields = getPluginConfigFields(id);
|
|
62
|
+
const oldConfig = allConfigs[id];
|
|
63
|
+
const newConfig = lodash_1.default.pickBy({ ...oldConfig, ...changes }, (v, k) => { var _a; return v != null && !(0, misc_1.same)(v, (_a = fields === null || fields === void 0 ? void 0 : fields[k]) === null || _a === void 0 ? void 0 : _a.defaultValue); });
|
|
64
|
+
return { ...allConfigs, [id]: lodash_1.default.isEmpty(newConfig) ? undefined : newConfig };
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
exports.setPluginConfig = setPluginConfig;
|
|
68
|
+
function getPluginInfo(id) {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
return (_b = (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData()) !== null && _b !== void 0 ? _b : availablePlugins[id];
|
|
71
|
+
}
|
|
72
|
+
exports.getPluginInfo = getPluginInfo;
|
|
73
|
+
function mapPlugins(cb) {
|
|
74
|
+
return lodash_1.default.map(plugins, (pl, plName) => {
|
|
75
|
+
try {
|
|
76
|
+
return cb(pl, plName);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
console.log('plugin error', plName, String(e));
|
|
80
|
+
}
|
|
81
|
+
}).filter(x => x !== undefined);
|
|
82
|
+
}
|
|
83
|
+
exports.mapPlugins = mapPlugins;
|
|
84
|
+
function getPluginConfigFields(id) {
|
|
85
|
+
var _a;
|
|
86
|
+
return (_a = plugins[id]) === null || _a === void 0 ? void 0 : _a.getData().config;
|
|
87
|
+
}
|
|
88
|
+
exports.getPluginConfigFields = getPluginConfigFields;
|
|
89
|
+
function pluginsMiddleware() {
|
|
90
|
+
return async (ctx, next) => {
|
|
91
|
+
var _a;
|
|
92
|
+
const after = [];
|
|
93
|
+
// run middleware plugins
|
|
94
|
+
for (const id in plugins)
|
|
95
|
+
try {
|
|
96
|
+
const pl = plugins[id];
|
|
97
|
+
const res = await ((_a = pl.middleware) === null || _a === void 0 ? void 0 : _a.call(pl, ctx));
|
|
98
|
+
if (res === true)
|
|
99
|
+
ctx.pluginStopped = true;
|
|
100
|
+
if (typeof res === 'function')
|
|
101
|
+
after.push(res);
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
console.log('error middleware plugin', id, String(e));
|
|
105
|
+
console.debug(e);
|
|
106
|
+
}
|
|
107
|
+
// expose public plugins' files
|
|
108
|
+
const { path } = ctx;
|
|
109
|
+
if (!ctx.pluginStopped) {
|
|
110
|
+
if (path.startsWith(const_1.PLUGINS_PUB_URI)) {
|
|
111
|
+
const a = path.substring(const_1.PLUGINS_PUB_URI.length).split('/');
|
|
112
|
+
if (plugins.hasOwnProperty(a[0])) { // do it only if the plugin is loaded
|
|
113
|
+
a.splice(1, 0, 'public');
|
|
114
|
+
await (0, serveFile_1.serveFile)(exports.PATH + '/' + a.join('/'), 'auto')(ctx, next);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
await next();
|
|
118
|
+
}
|
|
119
|
+
for (const f of after)
|
|
120
|
+
await f();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
exports.pluginsMiddleware = pluginsMiddleware;
|
|
124
|
+
class Plugin {
|
|
125
|
+
constructor(id, data, unwatch) {
|
|
126
|
+
var _a, _b;
|
|
127
|
+
this.id = id;
|
|
128
|
+
this.data = data;
|
|
129
|
+
this.unwatch = unwatch;
|
|
130
|
+
this.started = new Date();
|
|
131
|
+
if (!data)
|
|
132
|
+
throw 'invalid data';
|
|
133
|
+
// if a previous instance is present, we are going to overwrite it, but first call its unload callback
|
|
134
|
+
const old = plugins[id];
|
|
135
|
+
try {
|
|
136
|
+
(_b = (_a = old === null || old === void 0 ? void 0 : old.data) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
137
|
+
} // we don't want all the effects of the Plugin.unload
|
|
138
|
+
catch (e) {
|
|
139
|
+
console.debug('error unloading plugin', id, String(e));
|
|
140
|
+
}
|
|
141
|
+
// track this
|
|
142
|
+
const wasStopped = availablePlugins[id];
|
|
143
|
+
if (wasStopped)
|
|
144
|
+
delete availablePlugins[id];
|
|
145
|
+
plugins[id] = this;
|
|
146
|
+
this.data = data = { ...data }; // clone to make object modifiable. Objects coming from import are not.
|
|
147
|
+
// some validation
|
|
148
|
+
for (const k of ['frontend_css', 'frontend_js']) {
|
|
149
|
+
const v = data[k];
|
|
150
|
+
if (typeof v === 'string')
|
|
151
|
+
data[k] = [v];
|
|
152
|
+
else if (v && !Array.isArray(v)) {
|
|
153
|
+
delete data[k];
|
|
154
|
+
console.warn('invalid', k);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
events_1.default.emit(old || wasStopped ? 'pluginStarted' : 'pluginInstalled', this);
|
|
158
|
+
}
|
|
159
|
+
get middleware() {
|
|
160
|
+
var _a;
|
|
161
|
+
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.middleware;
|
|
162
|
+
}
|
|
163
|
+
get frontend_css() {
|
|
164
|
+
var _a;
|
|
165
|
+
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_css;
|
|
166
|
+
}
|
|
167
|
+
get frontend_js() {
|
|
168
|
+
var _a;
|
|
169
|
+
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.frontend_js;
|
|
170
|
+
}
|
|
171
|
+
get onDirEntry() {
|
|
172
|
+
var _a;
|
|
173
|
+
return (_a = this.data) === null || _a === void 0 ? void 0 : _a.onDirEntry;
|
|
174
|
+
}
|
|
175
|
+
getData() {
|
|
176
|
+
return { ...this.data };
|
|
177
|
+
}
|
|
178
|
+
async unload() {
|
|
179
|
+
var _a, _b;
|
|
180
|
+
const { id } = this;
|
|
181
|
+
console.log('unloading plugin', id);
|
|
182
|
+
try {
|
|
183
|
+
await ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
console.debug('error unloading plugin', id, String(e));
|
|
187
|
+
}
|
|
188
|
+
delete plugins[id];
|
|
189
|
+
this.unwatch();
|
|
190
|
+
if (availablePlugins[id])
|
|
191
|
+
events_1.default.emit('pluginStopped', availablePlugins[id]);
|
|
192
|
+
else
|
|
193
|
+
events_1.default.emit('pluginUninstalled', id);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.Plugin = Plugin;
|
|
197
|
+
let availablePlugins = {};
|
|
198
|
+
function getAvailablePlugins() {
|
|
199
|
+
return Object.values(availablePlugins);
|
|
200
|
+
}
|
|
201
|
+
exports.getAvailablePlugins = getAvailablePlugins;
|
|
202
|
+
const rescanAsap = (0, misc_1.debounceAsync)(rescan, 1000);
|
|
203
|
+
if (!(0, fs_1.existsSync)(exports.PATH))
|
|
204
|
+
try {
|
|
205
|
+
(0, fs_1.mkdirSync)(exports.PATH);
|
|
206
|
+
}
|
|
207
|
+
catch (_a) { }
|
|
208
|
+
exports.pluginsWatcher = (0, misc_1.watchDir)(exports.PATH, rescanAsap);
|
|
209
|
+
exports.enablePlugins = (0, config_1.defineConfig)('enable_plugins', ['antibrute']);
|
|
210
|
+
exports.enablePlugins.sub(rescanAsap);
|
|
211
|
+
exports.pluginsConfig = (0, config_1.defineConfig)('plugins_config', {});
|
|
212
|
+
async function rescan() {
|
|
213
|
+
console.debug('scanning plugins');
|
|
214
|
+
const found = [];
|
|
215
|
+
const foundDisabled = {};
|
|
216
|
+
const MASK = (0, path_1.join)(exports.PATH, '*', 'plugin.js');
|
|
217
|
+
for (const f of await (0, fast_glob_1.default)([(0, path_1.join)(const_1.APP_PATH, MASK), MASK])) {
|
|
218
|
+
const id = f.split('/').slice(-2)[0];
|
|
219
|
+
if (id.endsWith(exports.DISABLING_POSTFIX))
|
|
220
|
+
continue;
|
|
221
|
+
if (!exports.enablePlugins.get().includes(id)) {
|
|
222
|
+
try {
|
|
223
|
+
const source = await (0, promises_1.readFile)(f, 'utf8');
|
|
224
|
+
foundDisabled[id] = parsePluginSource(id, source);
|
|
225
|
+
}
|
|
226
|
+
catch (_a) { }
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
found.push(id);
|
|
230
|
+
if (plugins[id]) // already loaded
|
|
231
|
+
continue;
|
|
232
|
+
const module = path_1.default.resolve(f);
|
|
233
|
+
const { unwatch } = (0, watchLoad_1.watchLoad)(f, async () => {
|
|
234
|
+
try {
|
|
235
|
+
const reloading = plugins[id];
|
|
236
|
+
console.log(reloading ? "reloading plugin" : "loading plugin", id);
|
|
237
|
+
const { init, ...data } = await Promise.resolve().then(() => __importStar(require(module)));
|
|
238
|
+
delete data.default;
|
|
239
|
+
deleteModule(require.resolve(module)); // avoid caching at next import
|
|
240
|
+
calculateBadApi(data);
|
|
241
|
+
if (data.badApi)
|
|
242
|
+
console.log("plugin", id, data.badApi);
|
|
243
|
+
const res = await (init === null || init === void 0 ? void 0 : init.call(null, {
|
|
244
|
+
srcDir: __dirname,
|
|
245
|
+
const: Const,
|
|
246
|
+
require,
|
|
247
|
+
getConnections: connections_1.getConnections,
|
|
248
|
+
events: events_1.default,
|
|
249
|
+
log(...args) {
|
|
250
|
+
console.log('plugin', id, ':', ...args);
|
|
251
|
+
},
|
|
252
|
+
getConfig: (cfgKey) => { var _a, _b, _c, _d, _e; return (_c = (_b = (_a = exports.pluginsConfig.get()) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b[cfgKey]) !== null && _c !== void 0 ? _c : (_e = (_d = data.config) === null || _d === void 0 ? void 0 : _d[cfgKey]) === null || _e === void 0 ? void 0 : _e.defaultValue; },
|
|
253
|
+
setConfig: (cfgKey, value) => setPluginConfig(id, { [cfgKey]: value }),
|
|
254
|
+
subscribeConfig(cfgKey, cb) {
|
|
255
|
+
let last = this.getConfig(cfgKey);
|
|
256
|
+
cb(last);
|
|
257
|
+
return exports.pluginsConfig.sub(() => {
|
|
258
|
+
const now = this.getConfig(cfgKey);
|
|
259
|
+
if (!(0, misc_1.same)(now, last))
|
|
260
|
+
cb(last = now);
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
getHfsConfig: config_1.getConfig,
|
|
264
|
+
}));
|
|
265
|
+
Object.assign(data, res);
|
|
266
|
+
new Plugin(id, data, unwatch);
|
|
267
|
+
if (reloading)
|
|
268
|
+
events_1.default.emit('pluginUpdated', getPluginInfo(id));
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
console.log("plugin error:", e);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
for (const id in foundDisabled) {
|
|
276
|
+
const p = foundDisabled[id];
|
|
277
|
+
const a = availablePlugins[id];
|
|
278
|
+
if ((0, misc_1.same)(a, p))
|
|
279
|
+
continue;
|
|
280
|
+
availablePlugins[id] = p;
|
|
281
|
+
if (a)
|
|
282
|
+
events_1.default.emit('pluginUpdated', p);
|
|
283
|
+
else if (!plugins[id])
|
|
284
|
+
events_1.default.emit('pluginInstalled', p);
|
|
285
|
+
}
|
|
286
|
+
for (const id in availablePlugins)
|
|
287
|
+
if (!foundDisabled[id] && !found.includes(id) && !plugins[id]) {
|
|
288
|
+
delete availablePlugins[id];
|
|
289
|
+
events_1.default.emit('pluginUninstalled', id);
|
|
290
|
+
}
|
|
291
|
+
for (const id in plugins)
|
|
292
|
+
if (!found.includes(id))
|
|
293
|
+
await plugins[id].unload();
|
|
294
|
+
}
|
|
295
|
+
exports.rescan = rescan;
|
|
296
|
+
function deleteModule(id) {
|
|
297
|
+
var _a;
|
|
298
|
+
const { cache } = require;
|
|
299
|
+
// build reversed map of dependencies
|
|
300
|
+
const requiredBy = { '.': ['.'] }; // don't touch main entry
|
|
301
|
+
for (const k in cache)
|
|
302
|
+
if (k !== id)
|
|
303
|
+
for (const child of (0, misc_1.wantArray)((_a = cache[k]) === null || _a === void 0 ? void 0 : _a.children))
|
|
304
|
+
(0, misc_1.getOrSet)(requiredBy, child.id, () => []).push(k);
|
|
305
|
+
const deleted = [];
|
|
306
|
+
recur(id);
|
|
307
|
+
function recur(id) {
|
|
308
|
+
let mod = cache[id];
|
|
309
|
+
if (!mod)
|
|
310
|
+
return;
|
|
311
|
+
delete cache[id];
|
|
312
|
+
deleted.push(id);
|
|
313
|
+
for (const child of mod.children)
|
|
314
|
+
if (!lodash_1.default.difference(requiredBy[child.id], deleted).length)
|
|
315
|
+
recur(child.id);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
(0, misc_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
|
|
319
|
+
function parsePluginSource(id, source) {
|
|
320
|
+
var _a, _b, _c, _d, _e, _f;
|
|
321
|
+
const pl = { id };
|
|
322
|
+
pl.description = (0, misc_1.tryJson)((_a = /exports.description *= *(".*")/.exec(source)) === null || _a === void 0 ? void 0 : _a[1]);
|
|
323
|
+
pl.repo = (_b = /exports.repo *= *"(.*)"/.exec(source)) === null || _b === void 0 ? void 0 : _b[1];
|
|
324
|
+
pl.version = (_d = Number((_c = /exports.version *= *(\d*\.?\d+)/.exec(source)) === null || _c === void 0 ? void 0 : _c[1])) !== null && _d !== void 0 ? _d : undefined;
|
|
325
|
+
pl.apiRequired = (_f = (0, misc_1.tryJson)((_e = /exports.apiRequired *= *([ \d.,[\]]+)/.exec(source)) === null || _e === void 0 ? void 0 : _e[1])) !== null && _f !== void 0 ? _f : undefined;
|
|
326
|
+
if (Array.isArray(pl.apiRequired) && (pl.apiRequired.length !== 2 || !pl.apiRequired.every(lodash_1.default.isFinite))) // validate [from,to] form
|
|
327
|
+
pl.apiRequired = undefined;
|
|
328
|
+
calculateBadApi(pl);
|
|
329
|
+
return pl;
|
|
330
|
+
}
|
|
331
|
+
exports.parsePluginSource = parsePluginSource;
|
|
332
|
+
function calculateBadApi(data) {
|
|
333
|
+
const r = data.apiRequired;
|
|
334
|
+
const [min, max] = Array.isArray(r) ? r : [r, r]; // normalize data type
|
|
335
|
+
data.badApi = min > const_1.API_VERSION ? "may not work correctly as it is designed for a newer version of HFS - check for updates"
|
|
336
|
+
: max < const_1.COMPATIBLE_API_VERSION ? "may not work correctly as it is designed for an older version of HFS - check for updates"
|
|
337
|
+
: undefined;
|
|
338
|
+
}
|
package/src/serveFile.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getRange = exports.serveFile = exports.serveFileNode = void 0;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const const_1 = require("./const");
|
|
10
|
+
const vfs_1 = require("./vfs");
|
|
11
|
+
const mime_types_1 = __importDefault(require("mime-types"));
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
const micromatch_1 = require("micromatch");
|
|
14
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const util_1 = require("util");
|
|
17
|
+
const allowedReferer = (0, config_1.defineConfig)('allowed_referer', '');
|
|
18
|
+
function serveFileNode(node) {
|
|
19
|
+
const { source, mime } = node;
|
|
20
|
+
const name = (0, vfs_1.getNodeName)(node);
|
|
21
|
+
const mimeString = typeof mime === 'string' ? mime
|
|
22
|
+
: lodash_1.default.find(mime, (val, mask) => (0, micromatch_1.isMatch)(name, mask));
|
|
23
|
+
return (ctx, next) => {
|
|
24
|
+
var _a;
|
|
25
|
+
const allowed = allowedReferer.get();
|
|
26
|
+
if (allowed) {
|
|
27
|
+
const ref = (_a = /\/\/([^:/]+)/.exec(ctx.get('referer'))) === null || _a === void 0 ? void 0 : _a[1]; // extract host from url
|
|
28
|
+
if (ref && ref !== host() // automatic accept if referer is basically the hosting domain
|
|
29
|
+
&& !(0, micromatch_1.isMatch)(ref, allowed))
|
|
30
|
+
return ctx.status = const_1.FORBIDDEN;
|
|
31
|
+
function host() {
|
|
32
|
+
const s = ctx.get('host');
|
|
33
|
+
return s[0] === '[' ? s.slice(1, s.indexOf(']')) : s === null || s === void 0 ? void 0 : s.split(':')[0];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
ctx.vfsNode = node; // useful to tell service files from files shared by the user
|
|
37
|
+
return serveFile(source || '', mimeString)(ctx, next);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
exports.serveFileNode = serveFileNode;
|
|
41
|
+
const mimeCfg = (0, config_1.defineConfig)('mime', { '*': 'auto' });
|
|
42
|
+
function serveFile(source, mime, content) {
|
|
43
|
+
return async (ctx) => {
|
|
44
|
+
if (!source)
|
|
45
|
+
return;
|
|
46
|
+
const fn = path_1.default.basename(source);
|
|
47
|
+
mime = mime !== null && mime !== void 0 ? mime : lodash_1.default.find(mimeCfg.get(), (v, k) => k > '' && (0, micromatch_1.isMatch)(fn, k)); // isMatch throws on an empty string
|
|
48
|
+
if (mime === vfs_1.MIME_AUTO)
|
|
49
|
+
mime = mime_types_1.default.lookup(source) || '';
|
|
50
|
+
if (mime)
|
|
51
|
+
ctx.type = mime;
|
|
52
|
+
if (ctx.method === 'OPTIONS') {
|
|
53
|
+
ctx.status = const_1.NO_CONTENT;
|
|
54
|
+
ctx.set({ Allow: 'OPTIONS, GET, HEAD' });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (ctx.method !== 'GET')
|
|
58
|
+
return ctx.status = const_1.METHOD_NOT_ALLOWED;
|
|
59
|
+
try {
|
|
60
|
+
const stats = await (0, util_1.promisify)(fs_1.stat)(source); // using fs's function instead of fs/promises, because only the former is supported by pkg
|
|
61
|
+
ctx.set('Last-Modified', stats.mtime.toUTCString());
|
|
62
|
+
ctx.fileSource = source;
|
|
63
|
+
ctx.status = 200;
|
|
64
|
+
if (ctx.fresh)
|
|
65
|
+
return ctx.status = 304;
|
|
66
|
+
if (content !== undefined)
|
|
67
|
+
return ctx.body = content;
|
|
68
|
+
const range = getRange(ctx, stats.size);
|
|
69
|
+
ctx.body = (0, fs_1.createReadStream)(source, range);
|
|
70
|
+
}
|
|
71
|
+
catch (_a) {
|
|
72
|
+
return ctx.status = 404;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
exports.serveFile = serveFile;
|
|
77
|
+
function getRange(ctx, totalSize) {
|
|
78
|
+
ctx.set('Accept-Ranges', 'bytes');
|
|
79
|
+
const { range } = ctx.request.header;
|
|
80
|
+
if (!range) {
|
|
81
|
+
ctx.response.length = totalSize;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const ranges = range.split('=')[1];
|
|
85
|
+
if (ranges.includes(','))
|
|
86
|
+
return ctx.throw(400, 'multi-range not supported');
|
|
87
|
+
let bytes = ranges === null || ranges === void 0 ? void 0 : ranges.split('-');
|
|
88
|
+
if (!(bytes === null || bytes === void 0 ? void 0 : bytes.length))
|
|
89
|
+
return ctx.throw(400, 'bad range');
|
|
90
|
+
const max = totalSize - 1;
|
|
91
|
+
const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize - Number(bytes[1])); // a negative start is relative to the end
|
|
92
|
+
const end = bytes[0] ? Number(bytes[1] || max) : max; // NaN in case we are asked for last N bytes without knowing max
|
|
93
|
+
if (isNaN(end) || end > max || start > max) {
|
|
94
|
+
ctx.status = 416;
|
|
95
|
+
ctx.set('Content-Range', `bytes ${totalSize}`);
|
|
96
|
+
ctx.body = 'Requested Range Not Satisfiable';
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
ctx.status = 206;
|
|
100
|
+
ctx.set('Content-Range', `bytes ${start}-${end}/${isNaN(totalSize) ? '*' : totalSize}`);
|
|
101
|
+
ctx.response.length = end - start + 1;
|
|
102
|
+
return { start, end };
|
|
103
|
+
}
|
|
104
|
+
exports.getRange = getRange;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.serveGuiFiles = void 0;
|
|
31
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
32
|
+
const const_1 = require("./const");
|
|
33
|
+
const serveFile_1 = require("./serveFile");
|
|
34
|
+
const plugins_1 = require("./plugins");
|
|
35
|
+
const api_auth_1 = require("./api.auth");
|
|
36
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
37
|
+
const path_1 = require("path");
|
|
38
|
+
const misc_1 = require("./misc");
|
|
39
|
+
// in case of dev env we have our static files within the 'dist' folder'
|
|
40
|
+
const DEV_STATIC = process.env.DEV ? 'dist/' : '';
|
|
41
|
+
function serveStatic(uri) {
|
|
42
|
+
const folder = uri.slice(2, -1); // we know folder is very similar to uri
|
|
43
|
+
const cache = {};
|
|
44
|
+
return async (ctx, next) => {
|
|
45
|
+
if (ctx.method === 'OPTIONS') {
|
|
46
|
+
ctx.status = const_1.NO_CONTENT;
|
|
47
|
+
ctx.set({ Allow: 'OPTIONS, GET' });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (ctx.method !== 'GET')
|
|
51
|
+
return ctx.status = const_1.METHOD_NOT_ALLOWED;
|
|
52
|
+
const serveApp = shouldServeApp(ctx);
|
|
53
|
+
const fullPath = (0, path_1.join)(__dirname, '..', DEV_STATIC, folder, serveApp ? '/index.html' : ctx.path);
|
|
54
|
+
const content = await (0, misc_1.getOrSet)(cache, ctx.path, async () => {
|
|
55
|
+
const data = await promises_1.default.readFile(fullPath).catch(() => null);
|
|
56
|
+
return serveApp || !data ? data
|
|
57
|
+
: adjustBundlerLinks(ctx.path, uri, data);
|
|
58
|
+
});
|
|
59
|
+
if (content === null)
|
|
60
|
+
return ctx.status = 404;
|
|
61
|
+
if (!serveApp)
|
|
62
|
+
return (0, serveFile_1.serveFile)(fullPath, 'auto', content)(ctx, next);
|
|
63
|
+
// we don't cache the index as it's small and may prevent plugins change to apply
|
|
64
|
+
ctx.body = await treatIndex(ctx, String(content), uri);
|
|
65
|
+
ctx.type = 'html';
|
|
66
|
+
ctx.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function shouldServeApp(ctx) {
|
|
70
|
+
var _a;
|
|
71
|
+
return (_a = ctx.state).serveApp || (_a.serveApp = ctx.path.endsWith('/'));
|
|
72
|
+
}
|
|
73
|
+
function adjustBundlerLinks(path, uri, data) {
|
|
74
|
+
const ext = (0, path_1.extname)(path);
|
|
75
|
+
return ext && !ext.match(/\.(css|html|js|ts|scss)/) ? data
|
|
76
|
+
: String(data).replace(/((?:import | from )['"])\//g, `$1${uri}`);
|
|
77
|
+
}
|
|
78
|
+
async function treatIndex(ctx, body, filesUri) {
|
|
79
|
+
const session = await (0, api_auth_1.refresh_session)({}, ctx);
|
|
80
|
+
ctx.set('etag', '');
|
|
81
|
+
return body
|
|
82
|
+
.replace(/((?:src|href) *= *['"])\/?(?![a-z]+:\/\/)/g, '$1' + filesUri)
|
|
83
|
+
.replace('_HFS_SESSION_', session instanceof apiMiddleware_1.ApiError ? 'null' : JSON.stringify(session))
|
|
84
|
+
// replacing this text allow us to avoid injecting in frontends that don't support plugins. Don't use a <--comment--> or it will be removed by webpack
|
|
85
|
+
.replace('_HFS_PLUGINS_', pluginsInjection);
|
|
86
|
+
}
|
|
87
|
+
function serveProxied(port, uri) {
|
|
88
|
+
if (!port)
|
|
89
|
+
return;
|
|
90
|
+
console.debug('proxied on port', port);
|
|
91
|
+
let proxy;
|
|
92
|
+
Promise.resolve().then(() => __importStar(require('koa-better-http-proxy'))).then(lib => // dynamic import to avoid having this in final distribution
|
|
93
|
+
proxy = lib.default('127.0.0.1:' + port, {
|
|
94
|
+
proxyReqPathResolver: (ctx) => shouldServeApp(ctx) ? '/' : ctx.path,
|
|
95
|
+
userResDecorator(res, data, ctx) {
|
|
96
|
+
return shouldServeApp(ctx) ? treatIndex(ctx, String(data), uri)
|
|
97
|
+
: adjustBundlerLinks(ctx.path, uri, data);
|
|
98
|
+
}
|
|
99
|
+
}));
|
|
100
|
+
return function () {
|
|
101
|
+
return proxy.apply(this, arguments);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function pluginsInjection() {
|
|
105
|
+
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);
|
|
106
|
+
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);
|
|
107
|
+
return css.map(uri => `\n<link rel='stylesheet' type='text/css' href='${uri}'/>`).join('')
|
|
108
|
+
+ js.map(uri => `\n<script defer src='${uri}'></script>`).join('');
|
|
109
|
+
}
|
|
110
|
+
function serveGuiFiles(proxyPort, uri) {
|
|
111
|
+
return serveProxied(proxyPort, uri) || serveStatic(uri);
|
|
112
|
+
}
|
|
113
|
+
exports.serveGuiFiles = serveGuiFiles;
|
package/src/sse.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
function createSSE(ctx) {
|
|
6
|
+
const { socket } = ctx.req;
|
|
7
|
+
socket.setTimeout(0);
|
|
8
|
+
socket.setNoDelay(true);
|
|
9
|
+
socket.setKeepAlive(true);
|
|
10
|
+
ctx.set({
|
|
11
|
+
'Content-Type': 'text/event-stream',
|
|
12
|
+
'Cache-Control': 'no-cache',
|
|
13
|
+
'Connection': 'keep-alive',
|
|
14
|
+
'X-Accel-Buffering': 'no', // avoid buffering when reverse-proxied through nginx
|
|
15
|
+
});
|
|
16
|
+
ctx.status = 200;
|
|
17
|
+
return ctx.body = new stream_1.Transform({
|
|
18
|
+
objectMode: true,
|
|
19
|
+
transform(chunk, encoding, cb) {
|
|
20
|
+
this.push(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
21
|
+
cb();
|
|
22
|
+
},
|
|
23
|
+
flush(cb) {
|
|
24
|
+
this.push('data:\n\n');
|
|
25
|
+
cb();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
exports.default = createSSE;
|
package/src/throttler.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.totalInSpeed = exports.totalOutSpeed = exports.totalGot = exports.totalSent = exports.throttler = void 0;
|
|
8
|
+
const stream_1 = require("stream");
|
|
9
|
+
const ThrottledStream_1 = require("./ThrottledStream");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const misc_1 = require("./misc");
|
|
12
|
+
const connections_1 = require("./connections");
|
|
13
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
14
|
+
const events_1 = __importDefault(require("./events"));
|
|
15
|
+
const mainThrottleGroup = new ThrottledStream_1.ThrottleGroup(Infinity);
|
|
16
|
+
(0, config_1.defineConfig)('max_kbps', Infinity).sub(v => mainThrottleGroup.updateLimit(v));
|
|
17
|
+
const ip2group = {};
|
|
18
|
+
const SymThrStr = Symbol('stream');
|
|
19
|
+
const SymTimeout = Symbol('timeout');
|
|
20
|
+
const maxKbpsPerIp = (0, config_1.defineConfig)('max_kbps_per_ip', Infinity);
|
|
21
|
+
const throttler = async (ctx, next) => {
|
|
22
|
+
await next();
|
|
23
|
+
const { body } = ctx;
|
|
24
|
+
if (!body || !(body instanceof stream_1.Readable))
|
|
25
|
+
return;
|
|
26
|
+
// we wrap the stream also for unlimited connections to get speed and other features
|
|
27
|
+
const ipGroup = (0, misc_1.getOrSet)(ip2group, ctx.ip, () => {
|
|
28
|
+
var _a;
|
|
29
|
+
const doLimit = ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.ignore_limits) || (0, misc_1.isLocalHost)(ctx) ? undefined : true;
|
|
30
|
+
const group = new ThrottledStream_1.ThrottleGroup(Infinity, doLimit && mainThrottleGroup);
|
|
31
|
+
const unsub = doLimit && maxKbpsPerIp.sub(v => group.updateLimit(v));
|
|
32
|
+
return { group, count: 0, destroy: unsub };
|
|
33
|
+
});
|
|
34
|
+
const conn = ctx.state.connection;
|
|
35
|
+
if (!conn)
|
|
36
|
+
throw 'assert throttler connection';
|
|
37
|
+
const ts = conn[SymThrStr] = new ThrottledStream_1.ThrottledStream(ipGroup.group, conn[SymThrStr]);
|
|
38
|
+
let closed = false;
|
|
39
|
+
const DELAY = 1000;
|
|
40
|
+
const update = lodash_1.default.debounce(() => {
|
|
41
|
+
const ts = conn[SymThrStr];
|
|
42
|
+
const outSpeed = roundKb(ts.getSpeed());
|
|
43
|
+
(0, connections_1.updateConnection)(conn, { outSpeed, sent: ts.getBytesSent() });
|
|
44
|
+
/* in case this stream stands still for a while (before the end), we'll have neither 'sent' or 'close' events,
|
|
45
|
+
* so who will take care to updateConnection? This artificial next-call will ensure just that */
|
|
46
|
+
clearTimeout(conn[SymTimeout]);
|
|
47
|
+
if (outSpeed || !closed)
|
|
48
|
+
conn[SymTimeout] = setTimeout(update, DELAY);
|
|
49
|
+
}, DELAY, { maxWait: DELAY });
|
|
50
|
+
ts.on('sent', (n) => {
|
|
51
|
+
exports.totalSent += n;
|
|
52
|
+
update();
|
|
53
|
+
});
|
|
54
|
+
++ipGroup.count;
|
|
55
|
+
ts.on('close', () => {
|
|
56
|
+
var _a;
|
|
57
|
+
update.flush();
|
|
58
|
+
closed = true;
|
|
59
|
+
if (--ipGroup.count)
|
|
60
|
+
return; // any left?
|
|
61
|
+
(_a = ipGroup.destroy) === null || _a === void 0 ? void 0 : _a.call(ipGroup);
|
|
62
|
+
delete ip2group[ctx.ip];
|
|
63
|
+
});
|
|
64
|
+
const bak = ctx.response.length; // preserve
|
|
65
|
+
ctx.body = ctx.body.pipe(ts);
|
|
66
|
+
if (bak)
|
|
67
|
+
ctx.response.length = bak;
|
|
68
|
+
};
|
|
69
|
+
exports.throttler = throttler;
|
|
70
|
+
function roundKb(n) {
|
|
71
|
+
return lodash_1.default.round(n, 1) || lodash_1.default.round(n, 3); // further precision if necessary
|
|
72
|
+
}
|
|
73
|
+
exports.totalSent = 0;
|
|
74
|
+
exports.totalGot = 0;
|
|
75
|
+
exports.totalOutSpeed = 0;
|
|
76
|
+
exports.totalInSpeed = 0;
|
|
77
|
+
let lastSent = exports.totalSent;
|
|
78
|
+
let lastGot = exports.totalGot;
|
|
79
|
+
let last = Date.now();
|
|
80
|
+
setInterval(() => {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const past = (now - last) / 1000; // seconds
|
|
83
|
+
last = now;
|
|
84
|
+
const deltaSentKb = (exports.totalSent - lastSent) / 1000;
|
|
85
|
+
lastSent = exports.totalSent;
|
|
86
|
+
const deltaGotKb = (exports.totalGot - lastGot) / 1000;
|
|
87
|
+
lastGot = exports.totalGot;
|
|
88
|
+
exports.totalOutSpeed = roundKb(deltaSentKb / past);
|
|
89
|
+
exports.totalInSpeed = roundKb(deltaGotKb / past);
|
|
90
|
+
}, 1000);
|
|
91
|
+
events_1.default.on('connection', (c) => c.socket.on('data', data => exports.totalGot += data.length));
|