hfs 0.55.0-alpha2 → 0.55.0-beta3

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-CBj6uwzd.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-CBj6uwzd.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-beta3",
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/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.2;
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-14T14:24:01.619Z";
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;
@@ -40,13 +41,14 @@ exports.FRONTEND_OPTIONS = {
40
41
  sort_numerics: false,
41
42
  theme: '',
42
43
  auto_play_seconds: 5,
44
+ disableTranslation: false,
43
45
  };
44
46
  exports.SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time'];
45
47
  exports.THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' };
46
48
  exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown', 'dynamic_dns_url',
47
49
  'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips',
48
50
  'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads',
49
- 'allow_session_ip_change', 'force_lang', 'suspend_plugins']);
51
+ 'allow_session_ip_change', 'force_lang', 'suspend_plugins', 'base_url', 'size_1024']);
50
52
  exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
51
53
  exports.WHO_ANYONE = true;
52
54
  exports.WHO_NO_ONE = false;
@@ -68,9 +70,11 @@ function isWhoObject(v) {
68
70
  }
69
71
  exports.isWhoObject = isWhoObject;
70
72
  const MULTIPLIERS = ['', 'K', 'M', 'G', 'T'];
71
- function formatBytes(n, { post = 'B', k = 1024, digits = NaN, sep = ' ' } = {}) {
73
+ function formatBytes(n, { post = 'B', k = 0, digits = NaN, sep = ' ' } = {}) {
74
+ var _a;
72
75
  if (isNaN(Number(n)) || n < 0)
73
76
  return '';
77
+ k || (k = (_a = formatBytes.k) !== null && _a !== void 0 ? _a : 1024); // default value
74
78
  const i = n && Math.min(MULTIPLIERS.length - 1, Math.floor(Math.log2(n) / Math.log2(k)));
75
79
  n /= k ** i;
76
80
  const nAsString = i && !isNaN(digits) ? n.toFixed(digits)
@@ -79,8 +83,7 @@ function formatBytes(n, { post = 'B', k = 1024, digits = NaN, sep = ' ' } = {})
79
83
  } // formatBytes
80
84
  exports.formatBytes = formatBytes;
81
85
  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
86
+ return formatBytes(n, { post: 'B/s', ...options });
84
87
  }
85
88
  exports.formatSpeed = formatSpeed;
86
89
  function prefix(pre, v, post = '') {
@@ -386,6 +389,10 @@ function xlate(input, table) {
386
389
  return (_a = table[input]) !== null && _a !== void 0 ? _a : input;
387
390
  }
388
391
  exports.xlate = xlate;
392
+ function normalizeHost(host) {
393
+ return host[0] === '[' ? host.slice(1, host.indexOf(']')) : host === null || host === void 0 ? void 0 : host.split(':')[0];
394
+ }
395
+ exports.normalizeHost = normalizeHost;
389
396
  function isIpLocalHost(ip) {
390
397
  return ip === '::1' || ip.endsWith('127.0.0.1');
391
398
  }
@@ -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
- "author": "Ovidiu",
2
+ "author": "<0x1g3n>",
3
3
  "version": 1.0,
4
- "hfs_version": "0.53.0",
4
+ "hfs_version": "0.54.0",
5
5
  "translate": {
6
6
  "Select": "Selectează",
7
7
  "n_files": "{n,plural,one{# fișier} other{# fișiere}}",
@@ -10,12 +10,12 @@
10
10
  "select_count": "{n,plural, one{# selectat} other{# selectate}}",
11
11
  "filter_placeholder": "Tastează aici pentru a filtra lista de mai jos (caută după cuvinte cheie)",
12
12
  "Select some files": "Selectează câteva fișiere",
13
- "zip_checkboxes": "Folosește căsuțele de selectare pentru a alege fișierele, apoi poți folosi Zip din nou",
14
- "zip_tooltip_selected": "Descarcă elementele selectate ca un singur fișier zip",
15
- "zip_tooltip_whole": "Descarcă întreaga listă (nefiltrată) ca un singur fișier zip. Dacă selectezi câteva elemente, doar acelea vor fi descărcate.",
16
- "zip_confirm_search": "Ești sigur că vrei să descarci TOATE rezultatele acestei căutări sub formă de arhivă ZIP?",
17
- "zip_confirm_folder": "Ești sigur că vrei să descarci ÎNTREGUL folder sub formă de arhivă ZIP?",
18
- "select_tooltip": "Selecția se aplică pentru \"Zip\" și \"Șterge\" (când sunt disponibile), dar poți, de asemenea, filtra lista",
13
+ "zip_checkboxes": "Folosește căsuțele de selectare pentru a alege fișierele, apoi poți folosi ZIP din nou",
14
+ "zip_tooltip_selected": "Descarcă elementele selectate sub un singur fișier .ZIP",
15
+ "zip_tooltip_whole": "Descarcă întreaga listă (nefiltrată) ca un singur fișier .ZIP. Dacă selectezi câteva elemente, doar acelea vor fi descărcate.",
16
+ "zip_confirm_search": "Ești sigur că vrei să descarci TOATE rezultatele acestei căutări sub formă de arhivă .ZIP?",
17
+ "zip_confirm_folder": "Ești sigur că vrei să descarci ÎNTREGUL folder sub formă de arhivă .ZIP?",
18
+ "select_tooltip": "Selecția se aplică pentru \"ZIP\" și \"Șterge\" (când sunt disponibile), dar poți, de asemenea, filtra lista",
19
19
  "delete_hint": "Pentru a șterge, prima dată fă clic pe Selectează",
20
20
  "delete_confirm": "Ștergi {n,plural, one{# element} other{# elemente}}?",
21
21
  "delete_completed": "Ștergere: {n} completată",
@@ -24,7 +24,7 @@
24
24
  "Delete": "Șterge",
25
25
  "Options": "Opțiuni",
26
26
  "Search": "Căutare",
27
- "Zip": "Zip",
27
+ "Zip": "Arhivă ZIP",
28
28
  "search_msg": "Caută în acest folder și în sub-foldere",
29
29
  "Searching": "Căutare",
30
30
  "Searched": "Căutat",
@@ -33,13 +33,13 @@
33
33
  "stopped_before": "Oprit înainte de a găsi ceva",
34
34
  "empty_list": "Lista este goală. Nu este nimic aici",
35
35
  "filter_none": "Niciun rezultat pentru filtrul aplicat",
36
- "Admin-panel": "Panoul de administrare",
36
+ "Admin-panel": "Panoul Administratorului",
37
37
  "Login": "Autentificare",
38
- "Username": "Nume utilizator",
38
+ "Username": "Nume de utilizator",
39
39
  "Password": "Parolă",
40
40
  "login_untrusted": "Autentificare anulată: identitatea serverului nu poate fi de încredere",
41
- "login_bad_credentials": "Numele de utilizator sau parola sunt incorecte. Te rugăm să re-efectuezi logarea.",
42
- "login_bad_cookies": "Cookies nefuncționale - autentificare eșuată",
41
+ "login_bad_credentials": "Numele de utilizator sau parola sunt incorecte. Te rog să re-efectuezi logarea.",
42
+ "login_bad_cookies": "Cookie-uri nefuncționale - autentificare eșuată",
43
43
  "User panel": "Panoul utilizatorului",
44
44
  "Change password": "Schimbă parola",
45
45
  "enter_pass": "Introdu noua parolă",
@@ -65,8 +65,8 @@
65
65
  "Enter folder name": "Introdu numele folderului",
66
66
  "Successfully created": "Creat cu succes",
67
67
  "enter_folder": "Intră în folder",
68
- "folder_exists": "Există deja un folderul cu același nume",
69
- "Sort by:": "Sortați după: {by}",
68
+ "folder_exists": "Există deja un folder cu același nume",
69
+ "Sort by:": "Sortează după: {by}",
70
70
  "name": "nume",
71
71
  "extension": "extensie",
72
72
  "size": "dimensiune",
@@ -78,7 +78,7 @@
78
78
  "auto": "automat",
79
79
  "light": "luminos",
80
80
  "dark": "întunecat",
81
- "parent folder": "Folder sursă",
81
+ "parent folder": "Folder sursă (rădăcină)",
82
82
  "home": "acasă",
83
83
  "Continue": "Continuă",
84
84
  "Confirm": "Confirmă",
@@ -127,7 +127,7 @@
127
127
  "in_queue": "{n} în așteptare",
128
128
  "enter_comment": "Comentariu pentru {name}",
129
129
  "Comment": "Comentariu",
130
- "upload_dd_hint": "Poți încărca fișiere făcând drag&drop pe lista de fișiere",
130
+ "upload_dd_hint": "Poți încărca fișiere făcând drag&drop în lista cu fișierele deja existente",
131
131
  "Upload not available": "Încărcarea nu este disponibilă",
132
132
  "Cut": "Decupează",
133
133
  "n_items": "{n,plural, one{# element} other{# elemente}}",
@@ -161,6 +161,8 @@
161
161
  "Show details": "Arată detalii",
162
162
  "upload_conflict": "deja există",
163
163
  "Logged in": "Autentificat cu succes",
164
- "Logged out": "Deconectat cu succes"
164
+ "Logged out": "Deconectat cu succes",
165
+ "Cancel": "Anulează",
166
+ "allow_session_ip_change": "Permite schimbarea adresei IP pe durata sesiunilor de logare"
165
167
  }
166
168
  }