hfs 3.2.0-alpha1.1 → 3.2.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.
Files changed (179) hide show
  1. package/admin/assets/{af-RjyQ-neT.js → af-CV3suok4.js} +1 -1
  2. package/admin/assets/{am-PptHpglb.js → am-DNvTMYJ-.js} +1 -1
  3. package/admin/assets/{ar-Du3syDxa.js → ar-BGIEjplY.js} +1 -1
  4. package/admin/assets/{ar-dz-CiziRh1x.js → ar-dz-Ilq3cpjW.js} +1 -1
  5. package/admin/assets/{ar-iq-hCKg0CRP.js → ar-iq-DjXqm3R3.js} +1 -1
  6. package/admin/assets/{ar-kw-COYePt-p.js → ar-kw-4nugD0ui.js} +1 -1
  7. package/admin/assets/{ar-ly-CY5yETqb.js → ar-ly-DvrpBGjb.js} +1 -1
  8. package/admin/assets/{ar-ma-DczXi97r.js → ar-ma-ClnDMaR_.js} +1 -1
  9. package/admin/assets/{ar-sa-Bu7sTKzl.js → ar-sa-yyW34p_k.js} +1 -1
  10. package/admin/assets/{ar-tn-BrG0r6qZ.js → ar-tn-Dkn_F6cH.js} +1 -1
  11. package/admin/assets/{az-DYRCrdk0.js → az-BGQIRIME.js} +1 -1
  12. package/admin/assets/{be-DObSs5HX.js → be-CgBB_Tl_.js} +1 -1
  13. package/admin/assets/{bg-BDBkMe50.js → bg-DQ2NnjXf.js} +1 -1
  14. package/admin/assets/{bi-CApXFwc6.js → bi-C76InPXJ.js} +1 -1
  15. package/admin/assets/{bm-CjxCV8Lr.js → bm-CEihEziX.js} +1 -1
  16. package/admin/assets/{bn-DzWZbDi3.js → bn-CxCmr3_d.js} +1 -1
  17. package/admin/assets/{bn-bd-ijdPRJbb.js → bn-bd-hXn474is.js} +1 -1
  18. package/admin/assets/{bo-BDDfj5XH.js → bo-23wkHeLT.js} +1 -1
  19. package/admin/assets/{br-DRHEjait.js → br-DwfJ5fK1.js} +1 -1
  20. package/admin/assets/{bs-B0Zv2Agm.js → bs-pRqCggJt.js} +1 -1
  21. package/admin/assets/{ca-DuZAUqU_.js → ca-DgXusSYE.js} +1 -1
  22. package/admin/assets/{cs-DVkylosR.js → cs-CDakLSd0.js} +1 -1
  23. package/admin/assets/{cv-8Is1Fns9.js → cv-LGuw2gsy.js} +1 -1
  24. package/admin/assets/{cy-Bk30Or_y.js → cy--OuAdcJb.js} +1 -1
  25. package/admin/assets/{da-C41DIdZK.js → da-BY4-Oo6j.js} +1 -1
  26. package/admin/assets/{de--qvLJgIE.js → de-DhZFVtF3.js} +1 -1
  27. package/admin/assets/{de-at-D5vCBlsw.js → de-at-Dyk0Il-9.js} +1 -1
  28. package/admin/assets/{de-ch-Oh2cO23c.js → de-ch-C9HgqgBG.js} +1 -1
  29. package/admin/assets/{dv-4ZO2KNbw.js → dv-BnHXOAHb.js} +1 -1
  30. package/admin/assets/{el-CobOUqyu.js → el-DKZwotls.js} +1 -1
  31. package/admin/assets/{en-C9afClpS.js → en-Cfkmutmh.js} +1 -1
  32. package/admin/assets/{en-au-C_QMghEl.js → en-au-B1uLNVRS.js} +1 -1
  33. package/admin/assets/{en-ca-C80KILwc.js → en-ca-CPQ8JLrR.js} +1 -1
  34. package/admin/assets/{en-gb-BwQ0asAI.js → en-gb-BBXjeXVa.js} +1 -1
  35. package/admin/assets/{en-ie-ConCcQkq.js → en-ie-wbMYxxCu.js} +1 -1
  36. package/admin/assets/{en-il-CAOzD4DD.js → en-il-BqiJQjg0.js} +1 -1
  37. package/admin/assets/{en-in-87gardaO.js → en-in-Cox_dy1Y.js} +1 -1
  38. package/admin/assets/{en-nz-uMTjgkDP.js → en-nz-D2denFwV.js} +1 -1
  39. package/admin/assets/{en-sg-BlbiLv4L.js → en-sg-CPMvhL_f.js} +1 -1
  40. package/admin/assets/{en-tt-BXXmBax2.js → en-tt-DTr6jsXn.js} +1 -1
  41. package/admin/assets/{eo-BBL7o-0W.js → eo-Dz5-PLu9.js} +1 -1
  42. package/admin/assets/{es-DuNej-79.js → es-Ctd1zmr5.js} +1 -1
  43. package/admin/assets/{es-do-bZWKFwTE.js → es-do-C1siaHVl.js} +1 -1
  44. package/admin/assets/{es-mx-C2Lfb86F.js → es-mx-xN8Flblv.js} +1 -1
  45. package/admin/assets/{es-pr-wU3Du8m-.js → es-pr-CdDKn0Nn.js} +1 -1
  46. package/admin/assets/{es-us-qcU-zRiw.js → es-us-DrylVZZL.js} +1 -1
  47. package/admin/assets/{et-B-4PjN_n.js → et-Bm2qrgIv.js} +1 -1
  48. package/admin/assets/{eu-D0pNDZ9i.js → eu-B8Npz2me.js} +1 -1
  49. package/admin/assets/{fa-R_zmYBGc.js → fa-Ce478gVV.js} +1 -1
  50. package/admin/assets/{fi-CWYS_0Hg.js → fi-DmQCLn1D.js} +1 -1
  51. package/admin/assets/{fo-aJYpcnWS.js → fo-ty-wZYb3.js} +1 -1
  52. package/admin/assets/{fr-BHFrWfwB.js → fr-CPfNx_dE.js} +1 -1
  53. package/admin/assets/{fr-ca-ihwmO47n.js → fr-ca-DUuUTvVd.js} +1 -1
  54. package/admin/assets/{fr-ch-Cb8XbRfE.js → fr-ch-DN707oy8.js} +1 -1
  55. package/admin/assets/{fy-DT9BUc6t.js → fy-DU59ExaB.js} +1 -1
  56. package/admin/assets/{ga-B8p4naXJ.js → ga-VymLo-KV.js} +1 -1
  57. package/admin/assets/{gd-Dzrl6rPc.js → gd-OonH0A5f.js} +1 -1
  58. package/admin/assets/{gl-bN2tXl9d.js → gl-BINpSrwL.js} +1 -1
  59. package/admin/assets/{gom-latn-DUK8PgIJ.js → gom-latn-CDC4oAAB.js} +1 -1
  60. package/admin/assets/{gu-CxAIP8fw.js → gu-EP-tB-Px.js} +1 -1
  61. package/admin/assets/{he-Bm9XJVAj.js → he-BWkV26db.js} +1 -1
  62. package/admin/assets/{hi-CHik9Qxx.js → hi-CV9_k_t0.js} +1 -1
  63. package/admin/assets/{hr-Dn57_dqN.js → hr-BXciZ7cA.js} +1 -1
  64. package/admin/assets/{ht-BRS-FrJq.js → ht-CzKVwgZT.js} +1 -1
  65. package/admin/assets/{hu-hVaGcZ-U.js → hu-TogNJljg.js} +1 -1
  66. package/admin/assets/{hy-am-heLxXhpz.js → hy-am-B5AuRs7s.js} +1 -1
  67. package/admin/assets/{id-B6AkO4h8.js → id-Cz1E3N6j.js} +1 -1
  68. package/admin/assets/{index-Buyl8rI8.js → index-HuChhgks.js} +1 -1
  69. package/admin/assets/{index-WXxQwyJV.js → index-vGnRRM8b.js} +104 -104
  70. package/admin/assets/{is-BUADPoni.js → is-5T5BMRUs.js} +1 -1
  71. package/admin/assets/{it-Ce3gssOP.js → it-BVocdL64.js} +1 -1
  72. package/admin/assets/{it-ch-CJzj2czr.js → it-ch-g40Nau9_.js} +1 -1
  73. package/admin/assets/{ja-DykMIu-9.js → ja-B20OfIuQ.js} +1 -1
  74. package/admin/assets/{jv-Cum6DLwa.js → jv-BHABcRuK.js} +1 -1
  75. package/admin/assets/{ka-foCPVFxw.js → ka-n41vrxKm.js} +1 -1
  76. package/admin/assets/{kk-CMNu1ysC.js → kk-Wbgu12fS.js} +1 -1
  77. package/admin/assets/{km-BfMisGaD.js → km-CKhKBloy.js} +1 -1
  78. package/admin/assets/{kn-R_9S5KJz.js → kn-CGuQntHB.js} +1 -1
  79. package/admin/assets/{ko-DYRND-mM.js → ko-CezL4UDJ.js} +1 -1
  80. package/admin/assets/{ku-CNZ_weGy.js → ku-BxSpmtCp.js} +1 -1
  81. package/admin/assets/{ky-BhTAWL4I.js → ky-C0SyLnPR.js} +1 -1
  82. package/admin/assets/{lb-CxWhQxwF.js → lb-Y7KwDWXU.js} +1 -1
  83. package/admin/assets/{lo-DBL_XLkE.js → lo-BSFQGbEr.js} +1 -1
  84. package/admin/assets/{lt-BakZVm4p.js → lt-hJzN-eOW.js} +1 -1
  85. package/admin/assets/{lv-CCn-slXG.js → lv-C_oDREN9.js} +1 -1
  86. package/admin/assets/{me-xPEA4qjE.js → me-BEBNCwj6.js} +1 -1
  87. package/admin/assets/{mi-6cCs2Mzx.js → mi-CxoHQu_r.js} +1 -1
  88. package/admin/assets/{mk-CwWbHJPS.js → mk-CMde5Lyr.js} +1 -1
  89. package/admin/assets/{ml-Dp7Ab1SV.js → ml-Bb1prImZ.js} +1 -1
  90. package/admin/assets/{mn-ClotGWAe.js → mn-Cbig8iIC.js} +1 -1
  91. package/admin/assets/{mr-GiM-paTF.js → mr-C2OpIZbx.js} +1 -1
  92. package/admin/assets/{ms-BiZ1_eVR.js → ms-DEYcAuJ5.js} +1 -1
  93. package/admin/assets/{ms-my-oevI0fOH.js → ms-my-B3WZsidY.js} +1 -1
  94. package/admin/assets/{mt-BaIHeOl1.js → mt-qLOys0a8.js} +1 -1
  95. package/admin/assets/{my-Cg5Fc_1j.js → my-DRfLOxB3.js} +1 -1
  96. package/admin/assets/{nb-ZPODjT6R.js → nb-CgMDn1Zp.js} +1 -1
  97. package/admin/assets/{ne-C0ti-ZX2.js → ne-D_QxFLXg.js} +1 -1
  98. package/admin/assets/{nl-DP2PkkGx.js → nl-ByoNxF5J.js} +1 -1
  99. package/admin/assets/{nl-be-VPC0QyaW.js → nl-be-i74dNB1Q.js} +1 -1
  100. package/admin/assets/{nn-NLidKyJd.js → nn-CXXinLMW.js} +1 -1
  101. package/admin/assets/{oc-lnc-Ddj9_PnD.js → oc-lnc-COJUbhtG.js} +1 -1
  102. package/admin/assets/{pa-in-DSAWffhb.js → pa-in-CS1YXP4F.js} +1 -1
  103. package/admin/assets/{pl-Yojex5aS.js → pl-C8IUihXp.js} +1 -1
  104. package/admin/assets/{pt-BQMs2la2.js → pt-Bb-_2exC.js} +1 -1
  105. package/admin/assets/{pt-br-DVeeNZu8.js → pt-br-CsJVJZP0.js} +1 -1
  106. package/admin/assets/{rn-CKkTrK3f.js → rn-Bnqkr3Ve.js} +1 -1
  107. package/admin/assets/{ro-BbgGULSZ.js → ro-F686nvw6.js} +1 -1
  108. package/admin/assets/{ru-mAZX3DTa.js → ru-De6t8zEr.js} +1 -1
  109. package/admin/assets/{rw-CYErOxKd.js → rw-dgQlpJrj.js} +1 -1
  110. package/admin/assets/{sd-kfPTqkSy.js → sd-D0kCoi0j.js} +1 -1
  111. package/admin/assets/{se-D0bN3rDS.js → se-CsHkKyBJ.js} +1 -1
  112. package/admin/assets/{sha512-99nhg44S.js → sha512-CwhW8y7u.js} +1 -1
  113. package/admin/assets/{si-COjb_4Hy.js → si-BCtFhatF.js} +1 -1
  114. package/admin/assets/{sk-Co95XNOo.js → sk-HU0zEULD.js} +1 -1
  115. package/admin/assets/{sl-DUXa5wTF.js → sl-pswBfYoS.js} +1 -1
  116. package/admin/assets/{sq-B-KU0nWK.js → sq-CBhNzauG.js} +1 -1
  117. package/admin/assets/{sr-Cb_b-zPG.js → sr-Ba_jL5gy.js} +1 -1
  118. package/admin/assets/{sr-cyrl-csuTDIB6.js → sr-cyrl-Cu3CgIND.js} +1 -1
  119. package/admin/assets/{ss-BDnE-cG6.js → ss-CgQ9MxWY.js} +1 -1
  120. package/admin/assets/{sv-CzWdOHcE.js → sv-D-BB39HA.js} +1 -1
  121. package/admin/assets/{sv-fi-IK8oi5nB.js → sv-fi-B-o1NvLZ.js} +1 -1
  122. package/admin/assets/{sw-fPHo5hof.js → sw-BLvaZOOT.js} +1 -1
  123. package/admin/assets/{ta-TteN0nyU.js → ta-DuDUH7cy.js} +1 -1
  124. package/admin/assets/{te-2gsQb1fF.js → te-CEN7Z7sm.js} +1 -1
  125. package/admin/assets/{tet-ClTpDYJv.js → tet-BQlE9nRY.js} +1 -1
  126. package/admin/assets/{tg-Bel7Uc6z.js → tg-D_Ri8xyt.js} +1 -1
  127. package/admin/assets/{th-pDQttM9V.js → th-Cni5syVw.js} +1 -1
  128. package/admin/assets/{tk-CxoKYZH6.js → tk-DpSrobWM.js} +1 -1
  129. package/admin/assets/{tl-ph-D9htRcOQ.js → tl-ph-DZSUk-Vh.js} +1 -1
  130. package/admin/assets/{tlh-7UqvDBxU.js → tlh-BbZfw6gL.js} +1 -1
  131. package/admin/assets/{tr-NjJrq1iC.js → tr-CDpse5eg.js} +1 -1
  132. package/admin/assets/{tzl-CzGaXn8M.js → tzl-Cw8O9VLh.js} +1 -1
  133. package/admin/assets/{tzm-BiuscZFr.js → tzm-MQi7pP8P.js} +1 -1
  134. package/admin/assets/{tzm-latn-IeWkCgWf.js → tzm-latn-B0fBCe7c.js} +1 -1
  135. package/admin/assets/{ug-cn-5LK64x5v.js → ug-cn-CryBI7iC.js} +1 -1
  136. package/admin/assets/{uk-CzacUaQe.js → uk-ehtRqDfR.js} +1 -1
  137. package/admin/assets/{ur-BRV7z4Gu.js → ur-DLUufiXP.js} +1 -1
  138. package/admin/assets/{uz-C4G77QOI.js → uz-BGWw0Xm-.js} +1 -1
  139. package/admin/assets/{uz-latn-CQFrzZ6F.js → uz-latn-_11BzAge.js} +1 -1
  140. package/admin/assets/{vi-DEFXdlJi.js → vi-B1XZb9GR.js} +1 -1
  141. package/admin/assets/{x-pseudo-sWj5RKxR.js → x-pseudo-DbSvrJEU.js} +1 -1
  142. package/admin/assets/{yo-CBDjk96e.js → yo-enfGQNrL.js} +1 -1
  143. package/admin/assets/{zh-CQtX_fkD.js → zh-SltlpEBf.js} +1 -1
  144. package/admin/assets/{zh-cn-D6dF0jKM.js → zh-cn-C5GmIAnm.js} +1 -1
  145. package/admin/assets/{zh-hk-DY-Jaqdg.js → zh-hk-DHOlvPn8.js} +1 -1
  146. package/admin/assets/{zh-tw-BdmVby0R.js → zh-tw-Bk63QN63.js} +1 -1
  147. package/admin/index.html +1 -1
  148. package/frontend/assets/index-legacy-CU5HXdA4.js +9 -0
  149. package/frontend/assets/{index-legacy-CQovmh_0.js → index-legacy-FmhiYDuG.js} +1 -1
  150. package/frontend/assets/{sha512-legacy-CXU3efCO.js → sha512-legacy-B8oB-JQf.js} +1 -1
  151. package/frontend/index.html +1 -1
  152. package/npm-shrinkwrap.json +75 -31
  153. package/package.json +12 -12
  154. package/src/adminApis.js +2 -2
  155. package/src/api.get_file_list.js +3 -3
  156. package/src/api.vfs.js +46 -1
  157. package/src/basicWeb.js +1 -1
  158. package/src/comments.js +7 -4
  159. package/src/const.js +1 -1
  160. package/src/cross.js +12 -10
  161. package/src/events.js +2 -3
  162. package/src/expiringCache.js +8 -7
  163. package/src/frontEndApis.js +17 -14
  164. package/src/geo.js +6 -5
  165. package/src/listen.js +3 -2
  166. package/src/middlewares.js +1 -1
  167. package/src/nat.js +4 -1
  168. package/src/perm.js +1 -1
  169. package/src/plugins.js +2 -2
  170. package/src/roots.js +2 -2
  171. package/src/selfCheck.js +2 -1
  172. package/src/serveGuiAndSharedFiles.js +17 -7
  173. package/src/serveGuiFiles.js +3 -3
  174. package/src/update.js +1 -3
  175. package/src/util-files.js +9 -4
  176. package/src/util-http.js +2 -0
  177. package/src/vfs.js +20 -8
  178. package/src/zip.js +1 -1
  179. package/frontend/assets/index-legacy-BtoTkZho.js +0 -9
package/src/api.vfs.js CHANGED
@@ -17,6 +17,7 @@ const util_os_1 = require("./util-os");
17
17
  const listen_1 = require("./listen");
18
18
  const SendList_1 = require("./SendList");
19
19
  const walkDir_1 = require("./walkDir");
20
+ const roots_1 = require("./roots");
20
21
  // to manipulate the tree we need the original node
21
22
  async function urlToNodeOriginal(uri) {
22
23
  const n = await (0, vfs_1.urlToNode)(uri);
@@ -51,7 +52,7 @@ exports.default = {
51
52
  };
52
53
  }
53
54
  },
54
- async set_vfs({ uri, props }) {
55
+ async set_vfs({ uri, props, uriRemaps = {} }) {
55
56
  const n = uri && await urlToNodeOriginal(uri);
56
57
  if (!n)
57
58
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
@@ -67,7 +68,13 @@ exports.default = {
67
68
  simplifyName(n);
68
69
  n.isFolder = undefined; // reset field, it will be set by saveVfs
69
70
  await (0, vfs_1.saveVfs)();
71
+ if (!(0, vfs_1.isRoot)(n)) // not actually used by admin-panel but still
72
+ uriRemaps[uri] = uriForNode(uri, n); // just in case the current node was modified
73
+ await updateRootsForVfsUriRemaps(uriRemaps);
70
74
  return n;
75
+ function uriForNode(uri, n) {
76
+ return (0, misc_1.enforceFinal)('/', (0, path_1.dirname)(uri).replace(/\\/g, '/')) + (0, misc_1.pathEncode)((0, vfs_1.getNodeName)(n)) + ((0, vfs_1.nodeIsFolder)(n) ? '/' : '');
77
+ }
71
78
  },
72
79
  // legacy – not currently used by the UI
73
80
  async move_vfs({ from, parent }) {
@@ -94,6 +101,9 @@ exports.default = {
94
101
  ;
95
102
  (parentNode.children ||= []).push(fromNode);
96
103
  await (0, vfs_1.saveVfs)();
104
+ await updateRootsForVfsUriRemaps({
105
+ [from]: (0, misc_1.enforceFinal)('/', parent) + (0, misc_1.pathEncode)(name) + ((0, vfs_1.nodeIsFolder)(fromNode) ? '/' : '')
106
+ });
97
107
  return {};
98
108
  },
99
109
  // legacy – not currently used by the UI
@@ -271,6 +281,41 @@ function sanitizeVfsProps(props) {
271
281
  : props.children.map(sanitizeVfsProps);
272
282
  return ret;
273
283
  }
284
+ function updateRootsForVfsUriRemaps(uriRemaps = {}) {
285
+ const remaps = Object.entries(uriRemaps)
286
+ .map(([from, to]) => [normalizeVfsId(from), normalizeVfsId(to)])
287
+ .filter(x => x[0] !== x[1])
288
+ .sort(([a], [b]) => b.length - a.length); // longest first because, in case of both parent and child, it's more specific
289
+ let changed = false;
290
+ const updatedRoots = lodash_1.default.mapValues(roots_1.roots.get(), root => {
291
+ if (typeof root !== 'string' || !root)
292
+ return root;
293
+ const normalizedRoot = normalize(root);
294
+ for (const [from, to] of remaps) {
295
+ // roots are stored outside the VFS tree, so rename/move edits need an explicit path remap
296
+ const remappedRoot = replaceUriPrefix(normalizedRoot, from, to);
297
+ if (!remappedRoot)
298
+ continue;
299
+ if (remappedRoot !== root)
300
+ changed = true;
301
+ return remappedRoot;
302
+ }
303
+ return root;
304
+ });
305
+ if (changed)
306
+ roots_1.roots.set(updatedRoots);
307
+ function normalize(uri) {
308
+ return String(uri).replace(/^\/+|^(?!\/)|\/{2,}|\/+$|(?<!\/)$/g, '/');
309
+ }
310
+ function normalizeVfsId(uri) {
311
+ return normalize((0, misc_1.pathDecode)(String(uri))); // admin remaps come from tree ids, while roots are stored as readable VFS paths
312
+ }
313
+ function replaceUriPrefix(uri, oldPrefix, newPrefix) {
314
+ return uri === oldPrefix ? newPrefix
315
+ : uri.startsWith(oldPrefix) ? newPrefix + uri.slice(oldPrefix.length)
316
+ : '';
317
+ }
318
+ }
274
319
  function simplifyName(node) {
275
320
  const { name, ...noName } = node;
276
321
  if ((0, vfs_1.getNodeName)(noName) === name)
package/src/basicWeb.js CHANGED
@@ -41,7 +41,7 @@ function basicWeb(ctx, node) {
41
41
  const stream = (0, misc_1.asyncGeneratorToReadable)((0, misc_1.filterMapGenerator)(walker, async (el) => {
42
42
  const isFolder = (0, vfs_1.nodeIsFolder)(el);
43
43
  const name = (0, vfs_1.getNodeName)(el) + (isFolder ? '/' : '');
44
- return `<li>${a((0, misc_1.pathEncode)(name) + (isFolder && !await (0, vfs_1.hasDefaultFile)(el, ctx) ? force : ''), name)}\n`;
44
+ return `<li>${a((0, misc_1.pathEncode)(name) + (isFolder && !await (0, vfs_1.getDefaultFile)(el, ctx) ? force : ''), name)}\n`;
45
45
  }));
46
46
  ctx.body = stream;
47
47
  stream.push(`<meta name="viewport" content="width=device-width" />`);
package/src/comments.js CHANGED
@@ -16,6 +16,7 @@ const misc_1 = require("./misc");
16
16
  const lodash_1 = __importDefault(require("lodash"));
17
17
  const iconv_lite_1 = __importDefault(require("iconv-lite"));
18
18
  const promises_1 = require("node:fs/promises");
19
+ const expiringCache_1 = require("./expiringCache");
19
20
  exports.DESCRIPT_ION = 'descript.ion';
20
21
  exports.DESCRIPT_ION_ALT = 'DESCRIPT.ION';
21
22
  const commentsStorage = (0, config_1.defineConfig)(cross_1.CFG.comments_storage, '', v => ['', 'attr+ion'].includes(v)); // compiled tell us if we are using descript.ion
@@ -81,13 +82,15 @@ async function filePathHelper(folder) {
81
82
  return await (0, util_files_1.exists)(alt) && !await (0, util_files_1.exists)(main) ? alt : main;
82
83
  }
83
84
  const MULTILINE_SUFFIX = Buffer.from([4, 0xC2]);
85
+ const pathCache = (0, expiringCache_1.expiringCache)(2_000);
86
+ // this can be called many times when listing a folder, and we want to also not check too often as it can be expensive, especially on a networked drive
84
87
  async function readDescriptIon(path) {
85
- // decoding could also be done with native TextDecoder.decode, but we need iconv for the encoding anyway
86
- return (0, util_files_1.parseFile)(await filePathHelper(path), raw => {
87
- // for simplicity we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
88
+ return (0, util_files_1.parseFile)(await pathCache.try(path, filePathHelper), raw => {
89
+ // for simplicity, we "remove" the sequence MULTILINE_SUFFIX before iconv.decode messes it up
88
90
  for (let i = 0; i < raw.length; i++)
89
91
  if (raw[i] === MULTILINE_SUFFIX[0] && raw[i + 1] === MULTILINE_SUFFIX[1] && [undefined, 13, 10].includes(raw[i + 2]))
90
92
  raw[i] = raw[i + 1] = 10;
93
+ // decoding could also be done with native TextDecoder.decode, but we need iconv for the encoding anyway
91
94
  const decoded = iconv_lite_1.default.decode(raw, descriptIonEncoding.get());
92
95
  const ret = new Map(decoded.split('\n').map(line => {
93
96
  const quoted = line[0] === '"' ? 1 : 0;
@@ -98,7 +101,7 @@ async function readDescriptIon(path) {
98
101
  }));
99
102
  ret.delete('');
100
103
  return ret;
101
- });
104
+ }, 2000);
102
105
  }
103
106
  descriptIonEncoding.sub(() => {
104
107
  for (const k of util_files_1.parseFileCache.keys())
package/src/const.js CHANGED
@@ -68,7 +68,7 @@ exports.DEV = process.env.DEV ? 'DEV' : '';
68
68
  exports.ORIGINAL_CWD = process.cwd();
69
69
  exports.HFS_STARTED = new Date();
70
70
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
71
- exports.BUILD_TIMESTAMP = "2026-05-09T20:07:35.794Z";
71
+ exports.BUILD_TIMESTAMP = fs.statSync(PKG_PATH).mtime.toISOString();
72
72
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
73
73
  exports.VERSION = pkg.version;
74
74
  exports.RUNNING_BETA = exports.VERSION.includes('-');
package/src/cross.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.VFS_STORED_KEYS = 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;
20
+ exports.VFS_STORED_KEYS = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ADMIN = 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
21
  exports.isWhoObject = isWhoObject;
22
22
  exports.formatBytes = formatBytes;
23
23
  exports.formatSpeed = formatSpeed;
@@ -25,7 +25,6 @@ exports.prefix = prefix;
25
25
  exports.join = join;
26
26
  exports.wait = wait;
27
27
  exports.haveTimeout = haveTimeout;
28
- exports.objSameKeys = objSameKeys;
29
28
  exports.objFromKeys = objFromKeys;
30
29
  exports.enforceFinal = enforceFinal;
31
30
  exports.removeFinal = removeFinal;
@@ -81,6 +80,7 @@ exports.escapeHTML = escapeHTML;
81
80
  exports.promiseBestEffort = promiseBestEffort;
82
81
  exports.pathEncode = pathEncode;
83
82
  exports.pathDecode = pathDecode;
83
+ exports.pathDecodeSegments = pathDecodeSegments;
84
84
  exports.runAt = runAt;
85
85
  exports.makeMatcher = makeMatcher;
86
86
  exports.matches = matches;
@@ -127,18 +127,19 @@ exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknow
127
127
  'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips',
128
128
  'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads',
129
129
  'force_lang', 'suspend_plugins', 'base_url', 'size_1024', 'disable_custom_html', 'comments_storage',
130
- 'force_webdav_login', 'webdav_initial_auth', 'outbound_proxy', 'mapped_port', 'upnp_enabled']);
130
+ 'force_webdav_login', 'webdav_initial_auth', 'outbound_proxy', 'mapped_port', 'upnp_enabled', 'show_uploader']);
131
131
  exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
132
132
  exports.WHO_ANYONE = true;
133
133
  exports.WHO_NO_ONE = false;
134
134
  exports.WHO_ANY_ACCOUNT = '*';
135
+ exports.WHO_ADMIN = 'admin';
135
136
  exports.defaultPerms = {
136
- can_see: 'can_read',
137
137
  can_read: exports.WHO_ANYONE,
138
+ can_see: 'can_read',
138
139
  can_list: 'can_read',
139
- can_upload: exports.WHO_NO_ONE,
140
- can_delete: exports.WHO_NO_ONE,
141
- can_archive: 'can_read'
140
+ can_archive: 'can_read',
141
+ can_upload: exports.WHO_ADMIN,
142
+ can_delete: exports.WHO_ADMIN,
142
143
  };
143
144
  exports.PERM_KEYS = typedKeys(exports.defaultPerms);
144
145
  exports.VFS_STORED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename',
@@ -186,9 +187,6 @@ function haveTimeout(ms, job, error) {
186
187
  new Promise((_resolve, reject) => h = setTimeout(() => reject(error || Error('timeout')), ms))
187
188
  ]);
188
189
  }
189
- function objSameKeys(src, newValue) {
190
- return Object.fromEntries(Object.entries(src).map(([k, v]) => [k, newValue(v, k)]));
191
- }
192
190
  function objFromKeys(src, getValue) {
193
191
  return Object.fromEntries(src.map(k => [k, getValue(k)]));
194
192
  }
@@ -478,6 +476,10 @@ function pathEncode(s, all = false) {
478
476
  function pathDecode(s) {
479
477
  return decodeURI(s).replace(/%23/g, '#');
480
478
  }
479
+ function pathDecodeSegments(s, map = String) {
480
+ // decode segment by segment so reserved escapes are decoded without turning encoded slashes into separators
481
+ return s.split('/').map(x => map(safeDecodeURIComponent(x)).replaceAll('/', '%2F')).join('/');
482
+ }
481
483
  // run at a specific point in time, also solving the limit of setTimeout, which doesn't work with +32bit delays
482
484
  function runAt(ts, cb) {
483
485
  let cancel = false;
package/src/events.js CHANGED
@@ -5,8 +5,7 @@ exports.BetterEventEmitter = void 0;
5
5
  const LISTENERS_SUFFIX = '\0listeners';
6
6
  class BetterEventEmitter {
7
7
  listeners = new Map();
8
- preventDefault = Symbol();
9
- stop = this.preventDefault; // legacy pre-0.54 (introduced in 0.53)
8
+ stop = Symbol();
10
9
  on(event, listener, { warnAfter = 10, callNow = false } = {}) {
11
10
  if (typeof event === 'string')
12
11
  event = [event];
@@ -89,7 +88,7 @@ class BetterEventEmitter {
89
88
  const asyncRet = await Promise.all(syncRet);
90
89
  return Object.assign(asyncRet, {
91
90
  isDefaultPrevented: () => syncRet.isDefaultPrevented()
92
- || asyncRet.some((r) => r === this.preventDefault)
91
+ || asyncRet.some((r) => r === this.stop)
93
92
  });
94
93
  }
95
94
  }
@@ -6,24 +6,25 @@ function expiringCache(ttlMs) {
6
6
  throw Error('invalid TTL');
7
7
  const o = new Map();
8
8
  return Object.assign(o, {
9
- // creator can return undefined if the value should not be cached. `invalidate` is useful in case you have some custom logic for it, other than ttl.
9
+ invalidate,
10
+ // creator can return undefined if the value should not be cached
10
11
  try(k, creator) {
11
12
  let ret = o.get(k);
12
13
  if (ret === undefined) { // undefined = missing, as we don't accept this value in our cache
13
- ret = creator(invalidate);
14
+ ret = creator(k);
14
15
  if (ret !== undefined) {
15
16
  o.set(k, ret);
16
17
  Promise.resolve(ret).then(v => {
17
18
  if (v === undefined) // even in a promise, we'll consider undefined as a request to cancel the caching
18
- invalidate();
19
+ invalidate(k);
19
20
  }, () => { }) // avoid js warning
20
- .finally(() => setTimeout(invalidate, ttlMs)); // wait for async (in case) before starting the timer
21
- }
22
- function invalidate() {
23
- o.delete(k);
21
+ .finally(() => setTimeout(() => invalidate(k), ttlMs)); // wait for async (in case) before starting the timer
24
22
  }
25
23
  }
26
24
  return ret;
27
25
  },
28
26
  });
27
+ function invalidate(k) {
28
+ o.delete(k);
29
+ }
29
30
  }
@@ -20,11 +20,13 @@ const promises_1 = require("fs/promises");
20
20
  const path_1 = require("path");
21
21
  const upload_1 = require("./upload");
22
22
  const misc_1 = require("./misc");
23
+ const config_1 = require("./config");
23
24
  const comments_1 = require("./comments");
24
25
  const SendList_1 = require("./SendList");
25
26
  const adminApis_1 = require("./adminApis");
26
27
  const lodash_1 = __importDefault(require("lodash"));
27
28
  const partialFolderSize = {};
29
+ const showUploader = (0, config_1.defineConfig)(misc_1.CFG.show_uploader, misc_1.WHO_ADMIN);
28
30
  exports.frontEndApis = {
29
31
  get_file_list: api_get_file_list_1.get_file_list,
30
32
  ...api_auth_1.authApis,
@@ -43,19 +45,20 @@ exports.frontEndApis = {
43
45
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad uris');
44
46
  const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx);
45
47
  return {
46
- details: await Promise.all(uris.map(async (uri) => {
47
- if (typeof uri !== 'string')
48
- return false; // false means error
49
- const node = await (0, vfs_1.urlToNode)(uri, ctx);
50
- if (!node || !(0, vfs_1.hasPermission)(node, 'can_see', ctx))
51
- return false;
52
- let upload = node.source && await (0, upload_1.getUploadMeta)(node.source).catch(() => undefined);
53
- if (!upload)
54
- return;
55
- if (!isAdmin)
56
- upload = lodash_1.default.omit(upload, 'ip');
57
- return { upload };
58
- }))
48
+ details: (0, vfs_1.simpleWhoToError)(showUploader.get(), ctx) ? [] // return early because at the moment we only have the uploader
49
+ : await Promise.all(uris.map(async (uri) => {
50
+ if (typeof uri !== 'string')
51
+ return false; // false means error
52
+ const node = await (0, vfs_1.urlToNode)(uri, ctx);
53
+ if (!node || !(0, vfs_1.hasPermission)(node, 'can_see', ctx))
54
+ return false;
55
+ let upload = node.source && await (0, upload_1.getUploadMeta)(node.source).catch(() => undefined);
56
+ if (!upload)
57
+ return;
58
+ if (!isAdmin)
59
+ upload = lodash_1.default.omit(upload, 'ip');
60
+ return { upload };
61
+ }))
59
62
  };
60
63
  },
61
64
  async create_folder({ uri, name }, ctx) {
@@ -126,7 +129,7 @@ exports.frontEndApis = {
126
129
  await (0, comments_1.setCommentFor)(node.source, comment);
127
130
  return {};
128
131
  },
129
- async get_folder_size_partial({ id }, ctx) {
132
+ async get_folder_size_partial({ id }) {
130
133
  (0, misc_1.apiAssertTypes)({ string: { id } });
131
134
  return partialFolderSize[id] || new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
132
135
  },
package/src/geo.js CHANGED
@@ -38,8 +38,8 @@ function isOpen() {
38
38
  async function checkFiles() {
39
39
  if (!enabled.get())
40
40
  return;
41
- const ZIP_FILE = 'IP2LOCATION-LITE-DB1.IPV6.BIN';
42
- const URL = `https://download.ip2location.com/lite/${ZIP_FILE}.ZIP`;
41
+ const BIN_FILE = 'IP2LOCATION-LITE-DB1.IPV6.BIN';
42
+ const URL = `https://download.ip2location.com/lite/${BIN_FILE}.ZIP`;
43
43
  const LOCAL_FILE = 'geo_ip.bin';
44
44
  const TEMP = LOCAL_FILE + '.downloading';
45
45
  const { mtime = 0 } = await (0, misc_1.statWithTimeout)(LOCAL_FILE).catch(() => ({ mtime: 0 }));
@@ -49,11 +49,12 @@ async function checkFiles() {
49
49
  try {
50
50
  const req = await (0, misc_1.httpStream)(URL);
51
51
  console.log(`Downloading ${name}`);
52
- await (0, misc_1.unzip)(req, path => path.toUpperCase().endsWith(ZIP_FILE) && TEMP);
53
- await (0, misc_1.statWithTimeout)(TEMP); // check existence
52
+ await (0, misc_1.unzip)(req, path => path.toUpperCase().endsWith(BIN_FILE) && TEMP); // give a temp name
53
+ const s = await (0, misc_1.statWithTimeout)(TEMP); // check existence
54
+ if (s.size < 1E6)
55
+ throw `Bad size for geo_ip: ${s.size}`;
54
56
  if (isOpen())
55
57
  ip2location.close();
56
- await (0, promises_1.unlink)(LOCAL_FILE).catch(() => { });
57
58
  await (0, promises_1.rename)(TEMP, LOCAL_FILE);
58
59
  exports.ip2country.cache.clear?.();
59
60
  console.log(`${name} download completed`);
package/src/listen.js CHANGED
@@ -137,7 +137,7 @@ function getCertObject() {
137
137
  return;
138
138
  const all = new crypto_1.X509Certificate(c);
139
139
  const some = lodash_1.default.pick(all, ['subject', 'issuer', 'validFrom', 'validTo']);
140
- const ret = (0, misc_1.objSameKeys)(some, v => v?.includes('=') ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
140
+ const ret = lodash_1.default.mapValues(some, v => v?.includes('=') ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
141
141
  return Object.assign(ret, { altNames: all.subjectAltName?.replace(/DNS:/g, '').split(/, */) });
142
142
  }
143
143
  const considerHttps = (0, misc_1.debounceAsync)(async () => {
@@ -298,7 +298,8 @@ function stopServer(srv) {
298
298
  console.debug("Failed to stop server", String(err));
299
299
  resolve(err);
300
300
  });
301
- srv.closeAllConnections();
301
+ if (first_1.quitting)
302
+ srv.closeAllConnections();
302
303
  });
303
304
  }
304
305
  function getCurrentPort(srv) {
@@ -162,7 +162,7 @@ exports.paramsDecoder = paramsDecoder;
162
162
  let internalSessionMw;
163
163
  let options;
164
164
  events_1.default.once('app', () => // wait for app to be defined
165
- internalSessionMw = (0, koa_session_1.default)(options = { signed: true, rolling: true, sameSite: 'lax' }, index_1.app));
165
+ internalSessionMw = (0, koa_session_1.default)(options = { signed: true, renew: true, sameSite: 'lax' }, index_1.app));
166
166
  const sessionMiddleware = (ctx, next) => {
167
167
  options.key = 'hfs_' + ctx.protocol;
168
168
  return internalSessionMw(ctx, next);
package/src/nat.js CHANGED
@@ -76,7 +76,10 @@ exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
76
76
  throw "no good";
77
77
  return validIps;
78
78
  }))));
79
- return exports.defaultBaseUrl.publicIps = lodash_1.default.uniq(ips.flat());
79
+ const ret = exports.defaultBaseUrl.publicIps = lodash_1.default.uniq(ips.flat());
80
+ if (!ret.length) // don't keep empty results for long
81
+ setTimeout(() => exports.getPublicIps.clearRetain(), 5_000);
82
+ return ret;
80
83
  }, { retain: 10 * cross_1.MINUTE });
81
84
  exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
82
85
  const upnp = await exports.upnpEnabled.getWhenReady() ? getUpnpClient() : null;
package/src/perm.js CHANGED
@@ -81,7 +81,7 @@ async function updateAccount(account, change) {
81
81
  const u = normalizeUsername(change.username || '');
82
82
  if (u && u !== usernameWas && getAccount(u))
83
83
  throw "username already exists";
84
- Object.assign(account, (0, misc_1.objSameKeys)(change, x => x || undefined));
84
+ Object.assign(account, lodash_1.default.mapValues(change, x => x || undefined));
85
85
  }
86
86
  for (const [k, v] of (0, misc_1.typedEntries)(account))
87
87
  if (!v)
package/src/plugins.js CHANGED
@@ -158,7 +158,7 @@ async function initPlugin(pl, morePassedToInit) {
158
158
  const controlledEvents = Object.create(events_1.default, (0, misc_1.objFromKeys)(['on', 'once', 'multi'], k => ({
159
159
  value() {
160
160
  if (k === 'multi')
161
- arguments[0] = (0, misc_1.objSameKeys)(arguments[0], trap);
161
+ arguments[0] = lodash_1.default.mapValues(arguments[0], trap);
162
162
  else
163
163
  arguments[1] = trap(arguments[1]);
164
164
  const ret = events_1.default[k](...arguments);
@@ -582,7 +582,7 @@ function watchPlugin(id, path) {
582
582
  getConfig(cfgKey) {
583
583
  const cur = exports.pluginsConfig.get()?.[id];
584
584
  return cfgKey ? cur?.[cfgKey] ?? pluginData.config?.[cfgKey]?.defaultValue
585
- : lodash_1.default.defaults(cur, (0, misc_1.objSameKeys)(pluginData.config, x => x.defaultValue));
585
+ : lodash_1.default.defaults(cur, lodash_1.default.mapValues(pluginData.config, x => x.defaultValue));
586
586
  },
587
587
  setConfig: (cfgKey, value) => setPluginConfig(id, { [cfgKey]: value }),
588
588
  subscribeConfig(cfgKey, cb) {
package/src/roots.js CHANGED
@@ -32,7 +32,7 @@ const rootsMiddleware = (ctx, next) => (() => {
32
32
  }
33
33
  if (lodash_1.default.isEmpty(exports.roots.get()))
34
34
  return;
35
- const root = ctx.state.root = exports.roots.compiled()?.(ctx.host);
35
+ const root = ctx.state.root = exports.roots.compiled()(ctx.host);
36
36
  if (!ctx.state.skipFilters && forceAddress.get()
37
37
  && root === undefined && !(0, misc_1.isLocalHost)(ctx) && ctx.host !== listen_1.baseUrl.compiled())
38
38
  return (0, connections_1.disconnect)(ctx, forceAddress.key()); // returning truthy will not call next
@@ -46,7 +46,7 @@ const rootsMiddleware = (ctx, next) => (() => {
46
46
  return;
47
47
  for (const [k, v] of Object.entries(params))
48
48
  if (k.startsWith('uri'))
49
- params[k] = Array.isArray(v) ? v.map(cb) : cb(v);
49
+ params[k] = Array.isArray(v) ? v.map(cb) : lodash_1.default.isString(v) ? cb(v) : v;
50
50
  }
51
51
  })() || next();
52
52
  exports.rootsMiddleware = rootsMiddleware;
package/src/selfCheck.js CHANGED
@@ -38,7 +38,8 @@ async function selfCheck(url) {
38
38
  console.debug(svc);
39
39
  body = applySymbols(body);
40
40
  serviceUrl = applySymbols(serviceUrl);
41
- const res = await (0, cross_1.haveTimeout)(6_000, (0, util_http_1.httpString)(serviceUrl, { family, ...rest, body }));
41
+ const timeout = 8_000;
42
+ const res = await (0, cross_1.haveTimeout)(timeout, (0, util_http_1.httpString)(serviceUrl, { family, timeout, ...rest, body }));
42
43
  const success = new RegExp(regexpSuccess).test(res);
43
44
  const failure = new RegExp(regexpFailure).test(res);
44
45
  if (success === failure)
@@ -21,6 +21,7 @@ const serveGuiFiles_1 = require("./serveGuiFiles");
21
21
  const koa_mount_1 = __importDefault(require("koa-mount"));
22
22
  const listen_1 = require("./listen");
23
23
  const misc_1 = require("./misc");
24
+ const roots_1 = require("./roots");
24
25
  const xxhashjs_1 = __importDefault(require("xxhashjs"));
25
26
  const fs_1 = __importDefault(require("fs"));
26
27
  const promises_1 = require("fs/promises");
@@ -119,11 +120,10 @@ const serveSharedFiles = async (ctx, next) => {
119
120
  return ctx.status = cross_const_1.HTTP_SERVER_ERROR;
120
121
  }
121
122
  }
122
- if (node.default && path.endsWith('/') && !get) { // final/ needed on browser to make resource urls correctly with html pages
123
- const found = await (0, vfs_1.urlToNode)(node.default, ctx, node);
124
- if (found && /\.html?/i.test(node.default))
123
+ if (path.endsWith('/') && !get) { // final slash needed on browsers to make resource urls working with html pages
124
+ const found = await (0, vfs_1.getDefaultFile)(node, ctx);
125
+ if (found && /\.html?/i.test((0, vfs_1.getNodeName)(node = found)))
125
126
  ctx.state.considerAsGui = true;
126
- node = found ?? node;
127
127
  }
128
128
  if (get === 'icon')
129
129
  return (0, serveFile_1.serveFile)(ctx, node.icon || '|'); // pipe to cause not-found
@@ -160,9 +160,19 @@ async function sendFolderList(node, ctx) {
160
160
  ctx.type = 'text';
161
161
  if (prepend === undefined || prepend === '*') { // * = force auto-detection even if we have baseUrl set
162
162
  const { URL } = ctx;
163
- const base = prepend === undefined && listen_1.baseUrl.get()
164
- || URL.protocol + '//' + URL.host + ctx.state.revProxyPath;
165
- prepend = base + (0, misc_1.pathEncode)(decodeURI(ctx.path)); // redo the encoding our way, keeping unicode chars unchanged
163
+ const requestBase = URL.protocol + '//' + URL.host + ctx.state.revProxyPath;
164
+ const configuredBaseUrl = prepend === undefined && listen_1.baseUrl.get();
165
+ const configuredRoot = configuredBaseUrl && roots_1.roots.compiled()(listen_1.baseUrl.compiled() || '');
166
+ const pathInConfiguredRoot = configuredRoot && (configuredRoot === '/' ? ctx.path
167
+ : ctx.path.startsWith(configuredRoot) ? ctx.path.slice(configuredRoot.length - 1)
168
+ : ctx.path === configuredRoot.slice(0, -1) ? '/'
169
+ : false);
170
+ // base_url may expose a host-rooted home; use it only for VFS paths inside that host root
171
+ const [base, path] = !configuredBaseUrl ? [requestBase, ctx.path]
172
+ : pathInConfiguredRoot === false ? [requestBase, ctx.path]
173
+ : [configuredBaseUrl, pathInConfiguredRoot || ctx.path];
174
+ // redo the encoding our way, keeping unicode chars unchanged. decode each segment separately because decodeURI preserves reserved escapes like %3A, which pathEncode would double-encode
175
+ prepend = base + (0, misc_1.pathDecodeSegments)(path, misc_1.pathEncode);
166
176
  }
167
177
  const walker = (0, vfs_1.walkNode)(node, { ctx, depth: depth === '*' ? Infinity : Number(depth), parallelizeRecursion: false }); // parallelization produces out-of-order results, and we don't want it like that here
168
178
  ctx.body = (0, misc_1.asyncGeneratorToReadable)((0, misc_1.filterMapGenerator)(walker, async (el) => {
@@ -39,7 +39,7 @@ function serveStatic(uri) {
39
39
  return ctx.status = const_1.HTTP_METHOD_NOT_ALLOWED;
40
40
  const serveApp = shouldServeApp(ctx);
41
41
  const fullPath = (0, path_1.join)(__dirname, '..', folder, serveApp ? '/index.html' : ctx.path);
42
- const content = await (0, misc_1.parseFile)(fullPath, raw => serveApp || !raw.length ? raw : adjustBundlerLinks(ctx, uri, raw))
42
+ const content = await (0, misc_1.parseFile)(fullPath, raw => serveApp || !raw.length ? raw : adjustBundlerLinks(ctx, uri, raw), 1000)
43
43
  .catch(e => {
44
44
  if (e?.code !== 'ENOENT') // not supposed to happen, and yet a user reported a strange behavior
45
45
  console.error(`serveStatic/parseFile: ${String(e)}`);
@@ -118,7 +118,7 @@ async function treatIndex(ctx, filesUri, body) {
118
118
  ${(0, customHtml_1.getSection)('htmlHead')}`}
119
119
  `;
120
120
  function iconsToObj(icons, pre = '') {
121
- return icons && (0, misc_1.objSameKeys)(icons, (v, k) => ctx.state.revProxyPath + const_1.ICONS_URI + pre + k);
121
+ return icons && lodash_1.default.mapValues(icons, (v, k) => ctx.state.revProxyPath + const_1.ICONS_URI + pre + k);
122
122
  }
123
123
  if (isBody && isOpen)
124
124
  return `${all}
@@ -155,7 +155,7 @@ async function treatIndex(ctx, filesUri, body) {
155
155
  v = ctx.state.revProxyPath + v;
156
156
  }
157
157
  else if (type === 'array' && Array.isArray(v))
158
- v = v.map(x => (0, misc_1.objSameKeys)(x, (xv, xk) => adjustValueByConfig(xv, cfg.fields[xk])));
158
+ v = v.map(x => lodash_1.default.mapValues(x, (xv, xk) => adjustValueByConfig(xv, cfg.fields[xk])));
159
159
  return v;
160
160
  }
161
161
  }
package/src/update.js CHANGED
@@ -128,9 +128,7 @@ async function update(tagOrUrl = '') {
128
128
  throw "No update has been found";
129
129
  const plat = '-' + (0, misc_1.xlate)(process.platform, { win32: 'windows', darwin: 'mac' });
130
130
  const assetSearch = `${plat}-${process.arch}`;
131
- const legacyAssetSearch = `${plat}${(0, misc_1.prefix)('-', (0, misc_1.xlate)(process.arch, { x64: '', arm64: 'arm' }))}.zip`; // legacy pre-0.53.0-rc16
132
- const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'))
133
- || update.assets.find((x) => x.name.endsWith(legacyAssetSearch));
131
+ const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'));
134
132
  if (!asset)
135
133
  throw `Asset not found: ${assetSearch}`;
136
134
  url = asset.browser_download_url;
package/src/util-files.js CHANGED
@@ -182,19 +182,24 @@ function exists(path) {
182
182
  }
183
183
  // parse a file, caching unless timestamp has changed
184
184
  exports.parseFileCache = new Map();
185
- async function loadFileCached(path, loader) {
185
+ async function loadFileCached(path, loader, minInterval = 0) {
186
186
  const cached = exports.parseFileCache.get(path);
187
+ const now = Date.now();
188
+ if (cached && now - cached.lastCheck < minInterval)
189
+ return cached.parsed;
187
190
  const ts = await statWithTimeout(path).then(x => x.mtime, e => {
188
191
  if (e?.message !== 'timeout')
189
192
  throw e;
190
193
  return cached?.ts || new Date(0); // on timeout (e.g. thread pool saturated), serve cache if any, or attempt the loader
191
194
  });
195
+ if (cached)
196
+ cached.lastCheck = now;
192
197
  if (cached && Number(ts) === Number(cached.ts))
193
198
  return cached.parsed;
194
199
  const parsed = loader(path);
195
- exports.parseFileCache.set(path, { ts, parsed });
200
+ exports.parseFileCache.set(path, { ts, parsed, lastCheck: now });
196
201
  return parsed;
197
202
  }
198
- async function parseFile(path, parse) {
199
- return loadFileCached(path, () => (0, promises_1.readFile)(path).then(parse));
203
+ async function parseFile(path, parse, skipStatIfFresherThan = 0) {
204
+ return loadFileCached(path, () => (0, promises_1.readFile)(path).then(parse), skipStatIfFresherThan);
200
205
  }
package/src/util-http.js CHANGED
@@ -137,6 +137,8 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
137
137
  e.cause ??= req; // enrich the error
138
138
  reject(e);
139
139
  });
140
+ if (options.timeout) // node only emits the timeout event, so destroy the request to unblock callers waiting for the body
141
+ req.setTimeout(options.timeout, () => req.destroy(Object.assign(Error('timeout'), { code: 'ETIMEDOUT' })));
140
142
  if (body && body instanceof node_stream_1.Readable)
141
143
  body.pipe(req).on('end', () => req.end());
142
144
  else