hfs 0.55.0-alpha2 → 0.55.0-beta4

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.
@@ -1,4 +1,4 @@
1
- System.register(["./index-legacy-XBchFTt7.js"],(function(h,t){"use strict";var i,s;return{setters:[function(h){i=h.g,s=h.c}],execute:function(){function t(h,t){for(var i=function(){var i=t[s];if("string"!=typeof i&&!Array.isArray(i)){var r=function(t){if("default"!==t&&!(t in h)){var s=Object.getOwnPropertyDescriptor(i,t);s&&Object.defineProperty(h,t,s.get?s:{enumerable:!0,get:function(){return i[t]}})}};for(var e in i)r(e)}},s=0;s<t.length;s++)i();return Object.freeze(Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}))}var r={exports:{}};
1
+ System.register(["./index-legacy-BoMy1vEB.js"],(function(h,t){"use strict";var i,s;return{setters:[function(h){i=h.g,s=h.c}],execute:function(){function t(h,t){for(var i=function(){var i=t[s];if("string"!=typeof i&&!Array.isArray(i)){var r=function(t){if("default"!==t&&!(t in h)){var s=Object.getOwnPropertyDescriptor(i,t);s&&Object.defineProperty(h,t,s.get?s:{enumerable:!0,get:function(){return i[t]}})}};for(var e in i)r(e)}},s=0;s<t.length;s++)i();return Object.freeze(Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}))}var r={exports:{}};
2
2
  /*
3
3
  * [js-sha512]{@link https://github.com/emn178/js-sha512}
4
4
  *
@@ -10,6 +10,6 @@
10
10
  <style class="removeAtBoot">@media (prefers-color-scheme: dark) { html { background-color: #000; color: #888; } }</style>
11
11
  <div id="root">Wait for loading or <a href="?get=basic">use basic interface now</a></div>
12
12
  <script crossorigin id="vite-legacy-polyfill" src="/assets/polyfills-legacy-DMrMt_pQ.js"></script>
13
- <script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-XBchFTt7.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
13
+ <script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-BoMy1vEB.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
14
14
  </body>
15
15
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.55.0-alpha2",
3
+ "version": "0.55.0-beta4",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -72,7 +72,7 @@
72
72
  "dependencies": {
73
73
  "@gregoranders/csv": "^0.0.13",
74
74
  "@koa/router": "^13.0.1",
75
- "@node-rs/crc32": "^1.10.3",
75
+ "@node-rs/crc32": "^1.10.6",
76
76
  "@rejetto/kvstorage": "^0.13.0",
77
77
  "acme-client": "^5.4.0",
78
78
  "buffer-crc32": "^1.0.0",
@@ -16,7 +16,17 @@ function prepareAccount(ac) {
16
16
  username: ac.username,
17
17
  hasPassword: (0, perm_1.accountHasPassword)(ac),
18
18
  adminActualAccess: (0, perm_1.accountCanLoginAdmin)(ac),
19
+ canLogin: (0, perm_1.accountHasPassword)(ac) ? (0, perm_1.accountCanLogin)(ac) : undefined,
19
20
  invalidated: auth_1.invalidateSessionBefore.get(ac.username),
21
+ members: (0, misc_1.with_)(Object.values(perm_1.accountsConfig.get()), accounts => {
22
+ const ret = [];
23
+ let news = [ac.username];
24
+ while (news.length) {
25
+ news = accounts.filter(a => { var _a; return (_a = a.belongs) === null || _a === void 0 ? void 0 : _a.some(x => news.includes(x)); }).map(x => x.username);
26
+ ret.push(...news);
27
+ }
28
+ return ret.sort();
29
+ })
20
30
  };
21
31
  }
22
32
  exports.default = {
@@ -14,6 +14,7 @@ const promises_1 = require("fs/promises");
14
14
  const github_1 = require("./github");
15
15
  const const_1 = require("./const");
16
16
  const SendList_1 = require("./SendList");
17
+ const immer_1 = __importDefault(require("immer"));
17
18
  const apis = {
18
19
  get_plugins({}, ctx) {
19
20
  const list = new SendList_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize, false), ...(0, plugins_1.getAvailablePlugins)().map(serialize)] });
@@ -168,10 +169,13 @@ const apis = {
168
169
  };
169
170
  exports.default = apis;
170
171
  function serialize(p) {
171
- const o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
172
+ let o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
172
173
  : { ...p }; // _.defaults mutates object, and we don't want that
173
174
  if (typeof o.repo === 'object') // custom repo
174
175
  o.repo = o.repo.web;
176
+ o = (0, immer_1.default)(o, (o) => {
177
+ lodash_1.default.each(o.config, x => x.showIf && (x.showIf = String(x.showIf)));
178
+ });
175
179
  return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
176
180
  }
177
181
  async function checkDependencies(plugin) {
package/src/auth.js CHANGED
@@ -9,6 +9,7 @@ const cross_const_1 = require("./cross-const");
9
9
  const tssrp6a_1 = require("tssrp6a");
10
10
  const srp_1 = require("./srp");
11
11
  const cross_1 = require("./cross");
12
+ const expiringCache_1 = require("./expiringCache");
12
13
  const node_crypto_1 = require("node:crypto");
13
14
  const events_1 = __importDefault(require("./events"));
14
15
  const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
@@ -23,17 +24,16 @@ async function srpServerStep1(account) {
23
24
  return { srpServer, salt, pubKey: String(srpServer.B) }; // cast to string cause bigint can't be jsonized
24
25
  }
25
26
  exports.srpServerStep1 = srpServerStep1;
26
- const cache = {};
27
+ const cache = (0, expiringCache_1.expiringCache)(60000);
27
28
  async function srpCheck(username, password) {
28
29
  const account = (0, perm_1.getAccount)(username);
29
30
  if (!(account === null || account === void 0 ? void 0 : account.srp) || !password)
30
31
  return;
31
32
  const k = (0, node_crypto_1.createHash)('sha256').update(username + password + account.srp).digest("hex");
32
- const good = await (0, cross_1.getOrSet)(cache, k, async () => {
33
+ const good = await cache.try(k, async () => {
33
34
  const { srpServer, salt, pubKey } = await srpServerStep1(account);
34
35
  const client = await (0, srp_1.srpClientPart)(username, password, salt, pubKey);
35
- setTimeout(() => delete cache[k], 60000);
36
- return srpServer.step2(client.A, client.M1).then(() => 1, () => 0);
36
+ return srpServer.step2(client.A, client.M1).then(() => true, () => false);
37
37
  });
38
38
  return good ? account : undefined;
39
39
  }
package/src/config.js CHANGED
@@ -1,5 +1,28 @@
1
1
  "use strict";
2
2
  // This file is part of HFS - Copyright 2021-2023, 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
+ };
3
26
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
27
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
28
  };
@@ -14,6 +37,8 @@ const fs_1 = require("fs");
14
37
  const path_1 = require("path");
15
38
  const events_1 = __importDefault(require("./events"));
16
39
  const promises_1 = require("fs/promises");
40
+ const immer_1 = __importStar(require("immer"));
41
+ (0, immer_1.setAutoFreeze)(false); // we still want to mess with objects later (eg: account.belongs)
17
42
  // keep definition of config properties
18
43
  const configProps = {};
19
44
  let started = false; // this will tell the difference for subscribeConfig()s that are called before or after config is loaded
@@ -78,7 +103,7 @@ function defineConfig(k, defaultValue, compiler) {
78
103
  },
79
104
  set(v) {
80
105
  if (typeof v === 'function')
81
- this.set(v(this.get()));
106
+ this.set((0, immer_1.default)(this.get(), v));
82
107
  else
83
108
  setConfig1(k, v);
84
109
  },
package/src/consoleLog.js CHANGED
@@ -6,16 +6,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.consoleLog = void 0;
7
7
  const events_1 = __importDefault(require("./events"));
8
8
  const cross_1 = require("./cross");
9
+ const fs_1 = require("fs");
10
+ const const_1 = require("./const");
9
11
  exports.consoleLog = [];
12
+ const f = const_1.argv.consoleFile ? (0, fs_1.createWriteStream)(const_1.argv.consoleFile, 'utf-8') : null;
10
13
  for (const k of ['log', 'warn', 'error']) {
11
14
  const original = console[k];
12
15
  console[k] = (...args) => {
13
16
  const ts = new Date();
14
- const rec = { ts, k, msg: args.join(' ') };
17
+ const msg = args.join(' ');
18
+ const rec = { ts, k, msg };
15
19
  exports.consoleLog.push(rec);
16
20
  if (exports.consoleLog.length > 100000) // limit to avoid infinite space
17
21
  exports.consoleLog.splice(0, 1000);
18
22
  events_1.default.emit('console', rec);
23
+ f === null || f === void 0 ? void 0 : f.write(`${(0, cross_1.formatTimestamp)(ts)} [${k}] ${msg}\n`);
19
24
  return original((0, cross_1.formatTime)(ts), ...args); // bundled nodejs doesn't have locales (and apparently uses en-US)
20
25
  };
21
26
  }
package/src/const.js CHANGED
@@ -30,7 +30,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
30
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.CONFIG_FILE = exports.MIME_AUTO = exports.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HFS_REPO_BRANCH = exports.RUNNING_BETA = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.ARGS_FILE = exports.argv = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = void 0;
33
+ exports.CONFIG_FILE = exports.MIME_AUTO = exports.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HFS_REPO_BRANCH = exports.RUNNING_BETA = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.ARGS_FILE = exports.argv = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = void 0;
34
34
  const minimist_1 = __importDefault(require("minimist"));
35
35
  const fs = __importStar(require("fs"));
36
36
  const os_1 = require("os");
@@ -38,9 +38,8 @@ const lodash_1 = __importDefault(require("lodash"));
38
38
  const path_1 = require("path");
39
39
  const cross_1 = require("./cross");
40
40
  __exportStar(require("./cross-const"), exports);
41
- exports.API_VERSION = 10.1;
41
+ exports.API_VERSION = 10.3;
42
42
  exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
43
- exports.HFS_REPO = 'rejetto/hfs';
44
43
  exports.argv = (0, minimist_1.default)(process.argv.slice(2));
45
44
  // you can add arguments with this file, currently used for the update process on mac/linux
46
45
  exports.ARGS_FILE = (0, path_1.join)((0, os_1.homedir)(), 'hfs-args');
@@ -55,7 +54,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
55
54
  exports.ORIGINAL_CWD = process.cwd();
56
55
  exports.HFS_STARTED = new Date();
57
56
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
58
- exports.BUILD_TIMESTAMP = "2024-12-06T09:04:29.634Z";
57
+ exports.BUILD_TIMESTAMP = "2024-12-19T15:58:01.475Z";
59
58
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
60
59
  exports.VERSION = pkg.version;
61
60
  exports.RUNNING_BETA = exports.VERSION.includes('-');
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
3
+ exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.HFS_REPO = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
4
4
  exports.SPECIAL_URI = '/~/';
5
5
  exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
6
6
  exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
@@ -9,6 +9,7 @@ exports.PLUGINS_PUB_URI = exports.SPECIAL_URI + 'plugins/';
9
9
  exports.PORT_DISABLED = -1;
10
10
  exports.NBSP = '\xA0';
11
11
  exports.PLUGIN_CUSTOM_REST_PREFIX = '_';
12
+ exports.HFS_REPO = 'rejetto/hfs';
12
13
  exports.HTTP_OK = 200;
13
14
  exports.HTTP_NO_CONTENT = 204;
14
15
  exports.HTTP_PARTIAL_CONTENT = 206;
package/src/cross.js CHANGED
@@ -18,14 +18,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.waitFor = exports.newObj = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.dirname = exports.basename = exports.pendingPromise = exports._dbg = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.stringAfter = exports.splitAt = exports.strinsert = exports.removeStarting = exports.enforceStarting = exports.enforceFinal = exports.objFromKeys = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.join = exports.prefix = exports.formatSpeed = exports.formatBytes = exports.isWhoObject = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.LIST = exports.CFG = exports.THEME_OPTIONS = exports.SORT_BY_OPTIONS = exports.FRONTEND_OPTIONS = exports.MAX_TILE_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = exports.WEBSITE = void 0;
21
- exports.shortenAgent = exports.popKey = exports.safeDecodeURIComponent = exports.callable = exports.mapFilter = exports.inCommon = exports.replace = exports.matches = exports.makeMatcher = exports.runAt = exports.pathEncode = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.isIpLan = exports.isIpLocalHost = exports.xlate = exports.isEqualLax = exports.isTimestampString = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.isNumeric = exports.formatDate = exports.formatTime = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = void 0;
21
+ exports.shortenAgent = exports.popKey = exports.safeDecodeURIComponent = exports.callable = exports.mapFilter = exports.inCommon = exports.replace = exports.matches = exports.makeMatcher = exports.runAt = exports.pathEncode = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.isIpLan = exports.isIpLocalHost = exports.normalizeHost = exports.xlate = exports.isEqualLax = exports.isTimestampString = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.isNumeric = exports.formatDate = exports.formatTime = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = void 0;
22
22
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
23
23
  // all content here is shared between client and server
24
24
  const lodash_1 = __importDefault(require("lodash"));
25
- const picomatch_1 = __importDefault(require("picomatch/lib/picomatch")); // point directly to the browser-compatible source
25
+ const picomatch_1 = __importDefault(require("picomatch/lib/picomatch"));
26
+ const cross_const_1 = require("./cross-const"); // point directly to the browser-compatible source
26
27
  __exportStar(require("./cross-const"), exports);
27
28
  exports.WEBSITE = 'https://rejetto.com/hfs/';
28
- exports.REPO_URL = 'https://github.com/rejetto/hfs/';
29
+ exports.REPO_URL = `https://github.com/${cross_const_1.HFS_REPO}/`;
29
30
  exports.WIKI_URL = exports.REPO_URL + 'wiki/';
30
31
  exports.MINUTE = 60000;
31
32
  exports.HOUR = 60 * exports.MINUTE;
@@ -38,15 +39,17 @@ exports.FRONTEND_OPTIONS = {
38
39
  invert_order: false,
39
40
  folders_first: true,
40
41
  sort_numerics: false,
42
+ title_with_path: true,
41
43
  theme: '',
42
44
  auto_play_seconds: 5,
45
+ disableTranslation: false,
43
46
  };
44
47
  exports.SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time'];
45
48
  exports.THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' };
46
49
  exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown', 'dynamic_dns_url',
47
50
  'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips',
48
51
  'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads',
49
- 'allow_session_ip_change', 'force_lang', 'suspend_plugins']);
52
+ 'allow_session_ip_change', 'force_lang', 'suspend_plugins', 'base_url', 'size_1024']);
50
53
  exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
51
54
  exports.WHO_ANYONE = true;
52
55
  exports.WHO_NO_ONE = false;
@@ -68,9 +71,11 @@ function isWhoObject(v) {
68
71
  }
69
72
  exports.isWhoObject = isWhoObject;
70
73
  const MULTIPLIERS = ['', 'K', 'M', 'G', 'T'];
71
- function formatBytes(n, { post = 'B', k = 1024, digits = NaN, sep = ' ' } = {}) {
74
+ function formatBytes(n, { post = 'B', k = 0, digits = NaN, sep = ' ' } = {}) {
75
+ var _a;
72
76
  if (isNaN(Number(n)) || n < 0)
73
77
  return '';
78
+ k || (k = (_a = formatBytes.k) !== null && _a !== void 0 ? _a : 1024); // default value
74
79
  const i = n && Math.min(MULTIPLIERS.length - 1, Math.floor(Math.log2(n) / Math.log2(k)));
75
80
  n /= k ** i;
76
81
  const nAsString = i && !isNaN(digits) ? n.toFixed(digits)
@@ -79,8 +84,7 @@ function formatBytes(n, { post = 'B', k = 1024, digits = NaN, sep = ' ' } = {})
79
84
  } // formatBytes
80
85
  exports.formatBytes = formatBytes;
81
86
  function formatSpeed(n, options = {}) {
82
- return formatBytes(n, { post: 'B/s', k: 1000, ...options })
83
- .replace('K', 'k'); // ref https://en.wikipedia.org/wiki/Data-rate_units
87
+ return formatBytes(n, { post: 'B/s', ...options });
84
88
  }
85
89
  exports.formatSpeed = formatSpeed;
86
90
  function prefix(pre, v, post = '') {
@@ -386,6 +390,10 @@ function xlate(input, table) {
386
390
  return (_a = table[input]) !== null && _a !== void 0 ? _a : input;
387
391
  }
388
392
  exports.xlate = xlate;
393
+ function normalizeHost(host) {
394
+ return host[0] === '[' ? host.slice(1, host.indexOf(']')) : host === null || host === void 0 ? void 0 : host.split(':')[0];
395
+ }
396
+ exports.normalizeHost = normalizeHost;
389
397
  function isIpLocalHost(ip) {
390
398
  return ip === '::1' || ip.endsWith('127.0.0.1');
391
399
  }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.expiringCache = void 0;
4
+ function expiringCache(ttl) {
5
+ const o = new Map();
6
+ return Object.assign(o, {
7
+ try(k, creator) {
8
+ let ret = o.get(k);
9
+ if (ret === undefined) {
10
+ ret = creator();
11
+ o.set(k, ret);
12
+ Promise.resolve(ret).then(() => // in case of async, wait for it to be done before starting the timer
13
+ setTimeout(() => o.delete(k), ttl));
14
+ }
15
+ return ret;
16
+ },
17
+ });
18
+ }
19
+ exports.expiringCache = expiringCache;
package/src/i18n.js ADDED
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.i18nFromTranslations = void 0;
5
+ const valtio_1 = require("valtio");
6
+ const utils_1 = require("valtio/utils");
7
+ function i18nFromTranslations(translations, embedded = 'en') {
8
+ const state = (0, valtio_1.proxy)({
9
+ embedded,
10
+ disabled: false,
11
+ translations, // all dictionaries
12
+ });
13
+ const searchLangs = [];
14
+ (0, utils_1.watch)(get => {
15
+ const snapshot = get(state);
16
+ searchLangs.splice(0, Infinity, 'all', ...Object.keys(snapshot.translations), snapshot.embedded); // replace completely
17
+ });
18
+ const warns = new Set(); // avoid duplicates
19
+ function getLangs() {
20
+ return Object.keys(state.translations);
21
+ }
22
+ // If one of the keys is an "id", that should be the first. If one of the keys should work as a fallback, that should be the last. Use 'fallback' parameter if you don't want the fallback to work as a key.
23
+ function t(keyOrTpl, params, fallback) {
24
+ var _a, _b;
25
+ if (!keyOrTpl)
26
+ return '';
27
+ // memoize?
28
+ const keys = isTemplateStringsArray(keyOrTpl) ? [(fallback !== null && fallback !== void 0 ? fallback : (fallback = keyOrTpl[0]))]
29
+ : Array.isArray(keyOrTpl) ? keyOrTpl : [keyOrTpl];
30
+ if (typeof params === 'string' && !fallback) {
31
+ fallback = params;
32
+ params = null;
33
+ }
34
+ let found;
35
+ let selectedLang = ''; // keep track of where we find the translation
36
+ const { embedded } = state;
37
+ const langs = Object.keys(state.translations);
38
+ if (!state.disabled)
39
+ for (const key of keys) {
40
+ for (const lang of searchLangs)
41
+ if (found = (_b = (_a = state.translations[selectedLang = lang]) === null || _a === void 0 ? void 0 : _a.translate) === null || _b === void 0 ? void 0 : _b[key])
42
+ break;
43
+ if (!warns.has(key) && langs.length && langs[0] !== embedded) {
44
+ warns.add(key);
45
+ console.debug("miss i18n:", key);
46
+ }
47
+ }
48
+ if (!found) {
49
+ found = fallback || keys[keys.length - 1];
50
+ selectedLang = embedded;
51
+ }
52
+ return Array.from(tokenizer(found)).map(([s, inside]) => {
53
+ if (!inside)
54
+ return s;
55
+ const [k, cmd, rest] = s.split(',').map(x => x.trim());
56
+ if (!params)
57
+ throw "missing params on " + keys[0];
58
+ const v = k && params[k];
59
+ if (cmd === 'plural' && rest)
60
+ return plural(v, rest);
61
+ return v || v === 0 ? v : '';
62
+ }).join('');
63
+ function plural(v, rest) {
64
+ const plural = !Intl.PluralRules ? 'other'
65
+ : new Intl.PluralRules(selectedLang || embedded).select(Number(v));
66
+ let other = '';
67
+ let pickNext = false;
68
+ let collectOther = false;
69
+ for (const [s, inside] of tokenizer(rest)) {
70
+ if (pickNext)
71
+ return pick(s);
72
+ if (collectOther) {
73
+ other = s;
74
+ collectOther = false;
75
+ }
76
+ if (inside)
77
+ continue;
78
+ const selectors = s.trim().split(/\s+/);
79
+ pickNext = selectors.some(sel => sel[0] === '=' && v === Number(sel.slice(1))
80
+ || sel === plural);
81
+ collectOther = !pickNext && selectors.includes('other');
82
+ }
83
+ return pick(other);
84
+ function pick(s) {
85
+ return s.replace('#', String(v));
86
+ }
87
+ }
88
+ }
89
+ function* tokenizer(s) {
90
+ let ofs = 0;
91
+ while (1) {
92
+ const open = s.indexOf('{', ofs);
93
+ if (open < 0)
94
+ break;
95
+ yield [s.slice(ofs, open), false];
96
+ let stack = 1;
97
+ ofs = open + 1;
98
+ while (stack && ofs < s.length) {
99
+ if (s[ofs] === '{')
100
+ stack++;
101
+ else if (s[ofs] === '}')
102
+ stack--;
103
+ ofs++;
104
+ }
105
+ if (stack)
106
+ return console.debug('tokenizer: unclosed'); // invalid, abort
107
+ yield [s.slice(open + 1, ofs - 1), true];
108
+ }
109
+ yield [s.slice(ofs), false];
110
+ }
111
+ function isTemplateStringsArray(x) {
112
+ return (x === null || x === void 0 ? void 0 : x.raw) && Array.isArray(x);
113
+ }
114
+ return {
115
+ t,
116
+ state,
117
+ getLangs,
118
+ i18nWrapperProps: () => ({ lang: getLangs()[0] || state.embedded }),
119
+ useI18N() {
120
+ (0, valtio_1.useSnapshot)(state);
121
+ return { t };
122
+ }
123
+ };
124
+ }
125
+ exports.i18nFromTranslations = i18nFromTranslations;
package/src/lang.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getLangData = exports.file2code = exports.code2file = void 0;
7
7
  const misc_1 = require("./misc");
8
+ const expiringCache_1 = require("./expiringCache");
8
9
  const promises_1 = require("fs/promises");
9
10
  const config_1 = require("./config");
10
11
  const watchLoad_1 = require("./watchLoad");
@@ -23,42 +24,49 @@ function file2code(fn) {
23
24
  return fn.replace(PREFIX, '').replace(SUFFIX, '');
24
25
  }
25
26
  exports.file2code = file2code;
26
- async function getLangData(ctx) {
27
- var _a;
28
- const param = String(ctx.query.lang || '');
29
- if (!param && forceLangData)
30
- return forceLangData;
31
- const ret = {};
32
- const csv = param || ctx.get('Accept-Language') || '';
33
- const langs = (0, misc_1.wantArray)(csv.split(',').map(x => x.toLowerCase()));
34
- let i = 0;
35
- while (i < langs.length) {
36
- let k = langs[i] || ''; // shut up ts
37
- if (!k || k === EMBEDDED_LANGUAGE)
38
- break;
39
- const fn = code2file(k);
40
- try {
41
- ret[k] = JSON.parse(await (0, promises_1.readFile)(fn, 'utf8'));
42
- }
43
- catch (_b) {
44
- if ((0, misc_1.hasProp)(embedded_1.default, k))
45
- ret[k] = embedded_1.default[k];
46
- else {
47
- do {
48
- k = k.substring(0, k.lastIndexOf('-'));
49
- } while (k && langs.includes(k));
50
- if (k) {
51
- langs[i] = k; // overwrite and retry
52
- continue;
27
+ const cache = (0, expiringCache_1.expiringCache)(3000); // 3 seconds for both a good dx and acceptable performance
28
+ async function getLangData(ctxOrLangCsv) {
29
+ if (typeof ctxOrLangCsv !== 'string') {
30
+ const ctx = ctxOrLangCsv;
31
+ const param = String(ctx.query.lang || '');
32
+ if (!param && forceLangData)
33
+ return forceLangData;
34
+ ctxOrLangCsv = param || ctx.get('Accept-Language') || '';
35
+ }
36
+ const csv = ctxOrLangCsv.toLowerCase();
37
+ return cache.try(csv, async () => {
38
+ var _a;
39
+ const langs = csv.split(',');
40
+ const ret = {};
41
+ let i = 0;
42
+ while (i < langs.length) {
43
+ let k = langs[i] || ''; // shut up ts
44
+ if (!k || k === EMBEDDED_LANGUAGE)
45
+ break;
46
+ const fn = code2file(k);
47
+ try {
48
+ ret[k] = JSON.parse(await (0, promises_1.readFile)(fn, 'utf8'));
49
+ } // allow external files to override embedded translations
50
+ catch (_b) {
51
+ if ((0, misc_1.hasProp)(embedded_1.default, k))
52
+ ret[k] = lodash_1.default.cloneDeep(embedded_1.default[k]);
53
+ else {
54
+ do {
55
+ k = k.substring(0, k.lastIndexOf('-'));
56
+ } while (k && langs.includes(k));
57
+ if (k) {
58
+ langs[i] = k; // overwrite and retry
59
+ continue;
60
+ }
53
61
  }
54
62
  }
63
+ const fromPlugins = (0, misc_1.onlyTruthy)(await Promise.all((0, plugins_1.mapPlugins)(async (pl) => { var _a; return (_a = (0, misc_1.tryJson)(await (0, promises_1.readFile)((0, path_1.join)(pl.folder, fn), 'utf8').catch(() => ''))) === null || _a === void 0 ? void 0 : _a.translate; })));
64
+ if (fromPlugins.length)
65
+ lodash_1.default.defaults((_a = (ret[k] || (ret[k] = {}))).translate || (_a.translate = {}), ...fromPlugins); // be sure we have an entry for k
66
+ i++;
55
67
  }
56
- const fromPlugins = (0, misc_1.onlyTruthy)(await Promise.all((0, plugins_1.mapPlugins)(async (pl) => { var _a; return (_a = (0, misc_1.tryJson)(await (0, promises_1.readFile)((0, path_1.join)(pl.folder, fn), 'utf8').catch(() => ''))) === null || _a === void 0 ? void 0 : _a.translate; })));
57
- if (fromPlugins.length)
58
- lodash_1.default.defaults((_a = (ret[k] || (ret[k] = {}))).translate || (_a.translate = {}), ...fromPlugins); // be sure we have an entry for k
59
- i++;
60
- }
61
- return ret;
68
+ return ret;
69
+ });
62
70
  }
63
71
  exports.getLangData = getLangData;
64
72
  let forceLangData;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Pultsari & Win_7",
3
- "version": 1.21,
4
- "hfs_version": "0.54.0",
3
+ "version": 1.3,
4
+ "hfs_version": "0.55.0",
5
5
  "translate": {
6
6
  "Select": "Valitse",
7
7
  "n_files": "{n,plural,one{# tiedosto} other{# tiedostoa}}",
@@ -167,6 +167,8 @@
167
167
  "Logged in": "Kirjauduttu sisään",
168
168
  "Logged out": "Kirjauduttu ulos",
169
169
  "Cancel": "Peruuta",
170
- "allow_session_ip_change": "Salli IP vaihto tämän istunnon aikana"
170
+ "allow_session_ip_change": "Salli IP vaihto tämän istunnon aikana",
171
+ "focus_hint": "Kirjoittamalla näppäimistölläsi, voit etsiä ja korostaa elementtejä listalta",
172
+ "copy_links": "Kopioi linkit"
171
173
  }
172
174
  }
@@ -156,6 +156,7 @@
156
156
  "Logged out": "Arrivederci",
157
157
  "Cancel": "Annulla",
158
158
  "allow_session_ip_change": "Permetti cambio IP in questa sessione",
159
- "focus_hint": "Scrivendo sulla tastiera cerchi tra gli elementi della lista."
159
+ "focus_hint": "Scrivendo sulla tastiera cerchi tra gli elementi della lista.",
160
+ "copy_links": "Copia i link"
160
161
  }
161
162
  }