hfs 3.1.5 → 3.2.0-beta2

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 (224) hide show
  1. package/admin/assets/af-DWEYq388.js +1 -0
  2. package/admin/assets/am-DgLbAgj6.js +1 -0
  3. package/admin/assets/ar-DgEWkO74.js +1 -0
  4. package/admin/assets/ar-dz-DHq--Sr8.js +1 -0
  5. package/admin/assets/ar-iq-D1r3nsb9.js +1 -0
  6. package/admin/assets/ar-kw-C85fGHwp.js +1 -0
  7. package/admin/assets/ar-ly-Bq6pjGjs.js +1 -0
  8. package/admin/assets/ar-ma-SGvmh6Mj.js +1 -0
  9. package/admin/assets/ar-sa-Bv8hiFi6.js +1 -0
  10. package/admin/assets/ar-tn-Bdvo77v3.js +1 -0
  11. package/admin/assets/az-7fPndoov.js +1 -0
  12. package/admin/assets/be-wjXeIeAK.js +1 -0
  13. package/admin/assets/bg-DZwBvjzH.js +1 -0
  14. package/admin/assets/bi-qCtxvMhO.js +1 -0
  15. package/admin/assets/bm-BVPWvreb.js +1 -0
  16. package/admin/assets/bn-CdWvye7d.js +1 -0
  17. package/admin/assets/bn-bd-B5-3blmz.js +1 -0
  18. package/admin/assets/bo-BEVcgyN2.js +1 -0
  19. package/admin/assets/br-DGQD6fFs.js +1 -0
  20. package/admin/assets/bs-So4MoRua.js +1 -0
  21. package/admin/assets/ca-Bk5ytG4g.js +1 -0
  22. package/admin/assets/cs-C4NU8eW-.js +1 -0
  23. package/admin/assets/cv-Ct-s-zrW.js +1 -0
  24. package/admin/assets/cy-ojtDPj8_.js +1 -0
  25. package/admin/assets/da-CYGin6vm.js +1 -0
  26. package/admin/assets/de-BEbJ01zH.js +1 -0
  27. package/admin/assets/de-at-C7UwE1rJ.js +1 -0
  28. package/admin/assets/de-ch-CsZ7YQc_.js +1 -0
  29. package/admin/assets/dv-DaVliwLd.js +1 -0
  30. package/admin/assets/el-DM_KqKEP.js +1 -0
  31. package/admin/assets/en-DedtOfaf.js +1 -0
  32. package/admin/assets/en-au-52Bzk5D9.js +1 -0
  33. package/admin/assets/en-ca-3pzEPK2N.js +1 -0
  34. package/admin/assets/en-gb-BrwDQS2G.js +1 -0
  35. package/admin/assets/en-ie-BUXSHrkL.js +1 -0
  36. package/admin/assets/en-il-a22drDCn.js +1 -0
  37. package/admin/assets/en-in-BUjecjkp.js +1 -0
  38. package/admin/assets/en-nz-Bbo7tnB_.js +1 -0
  39. package/admin/assets/en-sg-CZVDddmd.js +1 -0
  40. package/admin/assets/en-tt-DmSGwRia.js +1 -0
  41. package/admin/assets/eo-B71nkHZU.js +1 -0
  42. package/admin/assets/es-Dk6VCuuk.js +1 -0
  43. package/admin/assets/es-do-DMErY8ol.js +1 -0
  44. package/admin/assets/es-mx-BMRmqa3u.js +1 -0
  45. package/admin/assets/es-pr-CtBQz48p.js +1 -0
  46. package/admin/assets/es-us-CrDl5pnO.js +1 -0
  47. package/admin/assets/et-CO9OHqio.js +1 -0
  48. package/admin/assets/eu-Bip44atW.js +1 -0
  49. package/admin/assets/fa-CHbJ_dTM.js +1 -0
  50. package/admin/assets/fi-DKfoLmaQ.js +1 -0
  51. package/admin/assets/fo-DG1kOEfw.js +1 -0
  52. package/admin/assets/fr-DV73GZR4.js +1 -0
  53. package/admin/assets/fr-ca-BK-RoZiC.js +1 -0
  54. package/admin/assets/fr-ch-DjqEC5E_.js +1 -0
  55. package/admin/assets/fy-znrRQdeC.js +1 -0
  56. package/admin/assets/ga-BlZeKu0N.js +1 -0
  57. package/admin/assets/gd-BmrycMnC.js +1 -0
  58. package/admin/assets/gl-CuT8e5mi.js +1 -0
  59. package/admin/assets/gom-latn-BSWVd0A6.js +1 -0
  60. package/admin/assets/gu-BHK6LfvD.js +1 -0
  61. package/admin/assets/he-DPoTUevR.js +1 -0
  62. package/admin/assets/hi-BRuLafoW.js +1 -0
  63. package/admin/assets/hr-Bzge-10P.js +1 -0
  64. package/admin/assets/ht-Ck9BCna1.js +1 -0
  65. package/admin/assets/hu-CzqqbYmU.js +1 -0
  66. package/admin/assets/hy-am-C-eV4E8v.js +1 -0
  67. package/admin/assets/id-Dv8GZQvB.js +1 -0
  68. package/admin/assets/{index-DTxjaflW.js → index-BPIX0qPj.js} +1 -1
  69. package/admin/assets/index-CFWd-FDo.css +1 -0
  70. package/admin/assets/index-D3HviM6x.js +889 -0
  71. package/admin/assets/is-CK6VY3M_.js +1 -0
  72. package/admin/assets/it-1gtki4a5.js +1 -0
  73. package/admin/assets/it-ch-C0Mj3-pC.js +1 -0
  74. package/admin/assets/ja-Dl3AfnM1.js +1 -0
  75. package/admin/assets/jv-CznX-tGV.js +1 -0
  76. package/admin/assets/ka-BNjZxCug.js +1 -0
  77. package/admin/assets/kk-80J_xldf.js +1 -0
  78. package/admin/assets/km-CuWChDRB.js +1 -0
  79. package/admin/assets/kn-BZp_PBdl.js +1 -0
  80. package/admin/assets/ko-RirpyUl_.js +1 -0
  81. package/admin/assets/ku-Dz8ACD5w.js +1 -0
  82. package/admin/assets/ky-BN3ylOhj.js +1 -0
  83. package/admin/assets/lb-D7h_YoEn.js +1 -0
  84. package/admin/assets/lo-BrlbTUPD.js +1 -0
  85. package/admin/assets/lt-cXuHFdTa.js +1 -0
  86. package/admin/assets/lv-CjIpv13Q.js +1 -0
  87. package/admin/assets/me-B-jTh39Z.js +1 -0
  88. package/admin/assets/mi-D06xdVmt.js +1 -0
  89. package/admin/assets/mk-iLbuxyOf.js +1 -0
  90. package/admin/assets/ml-2W0y6Zb2.js +1 -0
  91. package/admin/assets/mn-6zbvKjeb.js +1 -0
  92. package/admin/assets/mr-7-jrgLyw.js +1 -0
  93. package/admin/assets/ms-CRH6rEXt.js +1 -0
  94. package/admin/assets/ms-my-BIJmu2S-.js +1 -0
  95. package/admin/assets/mt-CbXmxK-D.js +1 -0
  96. package/admin/assets/my-BvMUsxU8.js +1 -0
  97. package/admin/assets/nb-DvKHgF7L.js +1 -0
  98. package/admin/assets/ne-DiZZ3Lm6.js +1 -0
  99. package/admin/assets/nl-be-Dy7PYRbC.js +1 -0
  100. package/admin/assets/nl-t_2A_VAT.js +1 -0
  101. package/admin/assets/nn-Cw7EwosO.js +1 -0
  102. package/admin/assets/oc-lnc-CuFfB75K.js +1 -0
  103. package/admin/assets/pa-in-DOFyZ-Ft.js +1 -0
  104. package/admin/assets/pl-BD7FyCJj.js +1 -0
  105. package/admin/assets/pt-DSKLLE_u.js +1 -0
  106. package/admin/assets/pt-br-BhL4gb5Z.js +1 -0
  107. package/admin/assets/rn-DoHoZZPd.js +1 -0
  108. package/admin/assets/ro-B0v-_lH0.js +1 -0
  109. package/admin/assets/ru-BMVOk5eA.js +1 -0
  110. package/admin/assets/rw-CWF0w6eL.js +1 -0
  111. package/admin/assets/sd-DO2rrjch.js +1 -0
  112. package/admin/assets/se-CAolO9WQ.js +1 -0
  113. package/admin/assets/{sha512-D936QW8l.js → sha512-ZlUYj4Hr.js} +1 -1
  114. package/admin/assets/si-D03dHfb5.js +1 -0
  115. package/admin/assets/sk-_GcZGaN3.js +1 -0
  116. package/admin/assets/sl-Cb1lUGab.js +1 -0
  117. package/admin/assets/sq-Czzt23Tr.js +1 -0
  118. package/admin/assets/sr-D76dVqKJ.js +1 -0
  119. package/admin/assets/sr-cyrl-CuoFbJjW.js +1 -0
  120. package/admin/assets/ss-g-fGaM29.js +1 -0
  121. package/admin/assets/sv-CZxc8I45.js +1 -0
  122. package/admin/assets/sv-fi-D8REJeLz.js +1 -0
  123. package/admin/assets/sw-B1n3PjWG.js +1 -0
  124. package/admin/assets/ta-K5mexJNT.js +1 -0
  125. package/admin/assets/te-DT6dj5B6.js +1 -0
  126. package/admin/assets/tet-DXwYNm_H.js +1 -0
  127. package/admin/assets/tg-BCcZKcE2.js +1 -0
  128. package/admin/assets/th-CeeeseFX.js +1 -0
  129. package/admin/assets/tk-CJ6KW44d.js +1 -0
  130. package/admin/assets/tl-ph-DzE8lDmm.js +1 -0
  131. package/admin/assets/tlh-ER6KiMxG.js +1 -0
  132. package/admin/assets/tr-D48NGNpr.js +1 -0
  133. package/admin/assets/tzl-5AsmDTYM.js +1 -0
  134. package/admin/assets/tzm-2AzZ1YPf.js +1 -0
  135. package/admin/assets/tzm-latn-Cd5hPQuT.js +1 -0
  136. package/admin/assets/ug-cn-DU-MZ9Vx.js +1 -0
  137. package/admin/assets/uk-hn6vcOkn.js +1 -0
  138. package/admin/assets/ur-jOObtqB6.js +1 -0
  139. package/admin/assets/uz-BlftYfHF.js +1 -0
  140. package/admin/assets/uz-latn-BpuI0ccM.js +1 -0
  141. package/admin/assets/vi-DVo3LusT.js +1 -0
  142. package/admin/assets/x-pseudo-BuVzNhqi.js +1 -0
  143. package/admin/assets/yo-BvWGwb4m.js +1 -0
  144. package/admin/assets/zh-D9-tfba1.js +1 -0
  145. package/admin/assets/zh-cn-CFL5sbIW.js +1 -0
  146. package/admin/assets/zh-hk-DRP8u65r.js +1 -0
  147. package/admin/assets/zh-tw-CtVs1ihI.js +1 -0
  148. package/admin/index.html +2 -2
  149. package/frontend/assets/index-legacy-D3BTBYs5.js +9 -0
  150. package/frontend/assets/{index-legacy-vmpqwZZf.js → index-legacy-DcrWtKxQ.js} +1 -1
  151. package/frontend/assets/{sha512-legacy-wI89-UHR.js → sha512-legacy-DJvEwScE.js} +1 -1
  152. package/frontend/index.html +1 -1
  153. package/npm-shrinkwrap.json +144 -71
  154. package/package.json +9 -9
  155. package/plugins/antibrute/plugin.js +150 -19
  156. package/plugins/list-uploader/public/main.js +1 -1
  157. package/src/acme.js +11 -7
  158. package/src/api.accounts.js +3 -3
  159. package/src/api.auth.js +3 -1
  160. package/src/api.get_file_list.js +17 -13
  161. package/src/api.monitor.js +47 -43
  162. package/src/api.net.js +4 -3
  163. package/src/api.vfs.js +1 -1
  164. package/src/basicWeb.js +1 -1
  165. package/src/commands.js +54 -1
  166. package/src/comments.js +7 -4
  167. package/src/config.js +9 -5
  168. package/src/consoleLog.js +39 -1
  169. package/src/const.js +4 -0
  170. package/src/cross.js +33 -6
  171. package/src/errorPages.js +20 -10
  172. package/src/events.js +3 -3
  173. package/src/expiringCache.js +8 -7
  174. package/src/fileAttr.js +73 -15
  175. package/src/frontEndApis.js +20 -15
  176. package/src/index.js +2 -1
  177. package/src/langs/hfs-lang-ar.json +2 -1
  178. package/src/langs/hfs-lang-bg.json +2 -1
  179. package/src/langs/hfs-lang-de.json +2 -1
  180. package/src/langs/hfs-lang-el.json +2 -1
  181. package/src/langs/hfs-lang-es.json +2 -1
  182. package/src/langs/hfs-lang-fi.json +2 -1
  183. package/src/langs/hfs-lang-fr.json +2 -1
  184. package/src/langs/hfs-lang-hu.json +2 -1
  185. package/src/langs/hfs-lang-it.json +2 -1
  186. package/src/langs/hfs-lang-ja.json +2 -1
  187. package/src/langs/hfs-lang-ko.json +2 -1
  188. package/src/langs/hfs-lang-lt.json +2 -1
  189. package/src/langs/hfs-lang-ms.json +2 -0
  190. package/src/langs/hfs-lang-nl.json +2 -1
  191. package/src/langs/hfs-lang-pt-br.json +2 -1
  192. package/src/langs/hfs-lang-ro.json +2 -1
  193. package/src/langs/hfs-lang-ru.json +2 -1
  194. package/src/langs/hfs-lang-sr-latn.json +2 -1
  195. package/src/langs/hfs-lang-sr.json +2 -1
  196. package/src/langs/hfs-lang-th.json +2 -1
  197. package/src/langs/hfs-lang-tr.json +2 -1
  198. package/src/langs/hfs-lang-uk.json +2 -1
  199. package/src/langs/hfs-lang-vi.json +2 -1
  200. package/src/langs/hfs-lang-zh-tw.json +2 -1
  201. package/src/langs/hfs-lang-zh.json +2 -1
  202. package/src/listen.js +2 -1
  203. package/src/log.js +27 -2
  204. package/src/middlewares.js +8 -2
  205. package/src/misc.js +14 -0
  206. package/src/nat.js +61 -21
  207. package/src/outboundProxy.js +1 -1
  208. package/src/perm.js +5 -1
  209. package/src/plugins.js +34 -5
  210. package/src/roots.js +1 -1
  211. package/src/selfCheck.js +2 -1
  212. package/src/serveGuiAndSharedFiles.js +18 -7
  213. package/src/serveGuiFiles.js +2 -1
  214. package/src/update.js +10 -18
  215. package/src/urlList.js +32 -0
  216. package/src/util-files.js +24 -9
  217. package/src/util-http.js +4 -0
  218. package/src/vfs.js +21 -9
  219. package/src/walkDir.js +7 -1
  220. package/src/webdav.js +4 -1
  221. package/src/zip.js +3 -2
  222. package/admin/assets/index-B66w-a0v.css +0 -1
  223. package/admin/assets/index-Df6vYR7s.js +0 -822
  224. package/frontend/assets/index-legacy-emBsvICj.js +0 -9
@@ -57,7 +57,8 @@
57
57
  "Create folder": "สร้างโฟลเดอร์",
58
58
  "Pick files": "เลือกไฟล์",
59
59
  "Pick folder": "เลือกโฟลเดอร์",
60
- "send_files": "Send {n,plural,one{# file} other{# files}}, {size}",
60
+ "ready_to_upload": "{n,plural,one{# ไฟล์} other{# ไฟล์}}, {size}, พร้อมอัปโหลด",
61
+ "Send": "ส่ง",
61
62
  "Clear": "ล้าง",
62
63
  "failed_upload": "ไม่สามารถอัปโหลด {name}",
63
64
  "confirm_resume": "ดำเนินการอัปโหลดต่อหรือไม่?",
@@ -57,7 +57,8 @@
57
57
  "Create folder": "Klasör Oluştur",
58
58
  "Pick files": "Dosya(lar)ı Seç",
59
59
  "Pick folder": "Klasör Seç",
60
- "send_files": "{n,plural,one{# file} other{# files}} dosyayı gönder (UPLOAD), {size}",
60
+ "ready_to_upload": "{n,plural,one{# dosya} other{# dosya}}, {size}, yüklemeye hazır",
61
+ "Send": "Gönder",
61
62
  "Clear": "Temizle",
62
63
  "failed_upload": "{name} UPLOAD edilemedi (veri aktarım sorunu)",
63
64
  "confirm_resume": "UPLOAD kaldığı yerden devam etsin mi?",
@@ -57,7 +57,8 @@
57
57
  "Create folder": "Створити теку",
58
58
  "Pick files": "Вибрати файли",
59
59
  "Pick folder": "Вибрати теку",
60
- "send_files": "Завантажити {n,plural, one{# файл} few{# файли} many{# файлів}}, {size}",
60
+ "ready_to_upload": "{n,plural, one{# файл} few{# файли} many{# файлів}}, {size}, готово до завантаження",
61
+ "Send": "Завантажити",
61
62
  "Clear": "Очистити",
62
63
  "failed_upload": "Не вдалося завантажити {name}",
63
64
  "confirm_resume": "Відновити завантаження?",
@@ -58,7 +58,8 @@
58
58
  "Create folder": "Tạo folder...",
59
59
  "Pick files": "Chọn file...",
60
60
  "Pick folder": "Chọn folder...",
61
- "send_files": "Gửi {n,plural,one{# file} other{# file}}, {size}",
61
+ "ready_to_upload": "{n,plural,one{# file} other{# file}}, {size}, sẵn sàng tải lên",
62
+ "Send": "Gửi",
62
63
  "Clear": "Dọn.",
63
64
  "failed_upload": "Không thể upload {name}!",
64
65
  "confirm_resume": "Tiếp tục upload?",
@@ -57,7 +57,8 @@
57
57
  "Create folder": "建立資料夾",
58
58
  "Pick files": "選擇檔案",
59
59
  "Pick folder": "選擇資料夾",
60
- "send_files": "開始上傳 {n,plural,one{# 個檔案} other{# 個檔案}}, {size}",
60
+ "ready_to_upload": "{n,plural,one{# 個檔案} other{# 個檔案}}, {size},準備上傳",
61
+ "Send": "傳送",
61
62
  "Clear": "清空",
62
63
  "failed_upload": "無法上傳 {name}",
63
64
  "confirm_resume": "繼續上傳?",
@@ -57,7 +57,8 @@
57
57
  "Create folder": "创建文件夹",
58
58
  "Pick files": "选择文件",
59
59
  "Pick folder": "选择文件夹",
60
- "send_files": "开始上传 {n,plural,other{# 个文件}},{size}",
60
+ "ready_to_upload": "{n,plural,other{# 个文件}},{size},准备上传",
61
+ "Send": "发送",
61
62
  "Clear": "清除",
62
63
  "failed_upload": "无法上传 {name}",
63
64
  "confirm_resume": "是否重命名?",
package/src/listen.js CHANGED
@@ -68,6 +68,7 @@ const persistence_1 = require("./persistence");
68
68
  const argv_1 = require("./argv");
69
69
  const consoleLog_1 = require("./consoleLog");
70
70
  const first_1 = require("./first");
71
+ const fileAttr_1 = require("./fileAttr");
71
72
  let httpSrv;
72
73
  let httpsSrv;
73
74
  // the update relaunch can keep a bridge process alive, so we proactively close listeners here to release ports before the next binary binds; do it before (5) the storage file is closed, because sockets write there
@@ -86,7 +87,7 @@ const commonServerOptions = {
86
87
  };
87
88
  // these are properties that can be assigned to the server object
88
89
  const commonServerAssign = { headersTimeout: 30_000, timeout: misc_1.MINUTE }; // 'headersTimeout' is not recognized by type lib, and 'timeout' is not effective when passed in parameters
89
- const readyToListen = Promise.all([persistence_1.storedMap.isOpening(), events_1.default.once('app')]);
90
+ const readyToListen = Promise.all([persistence_1.storedMap.isOpening(), fileAttr_1.fileAttrDb.isOpening(), events_1.default.once('app')]);
90
91
  const considerHttp = (0, misc_1.debounceAsync)(async () => {
91
92
  await readyToListen;
92
93
  void stopServer(httpSrv);
package/src/log.js CHANGED
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.logMw = exports.loggers = void 0;
41
+ exports.zipLogFile = zipLogFile;
41
42
  exports.getRotatedFiles = getRotatedFiles;
42
43
  const events_1 = require("events");
43
44
  const config_1 = require("./config");
@@ -48,6 +49,9 @@ const util_files_1 = require("./util-files");
48
49
  const auth_1 = require("./auth");
49
50
  const misc_1 = require("./misc");
50
51
  const path_1 = require("path");
52
+ const yazl_1 = __importDefault(require("yazl"));
53
+ const promises_1 = require("stream/promises");
54
+ const promises_2 = require("fs/promises");
51
55
  const events_2 = __importDefault(require("./events"));
52
56
  const connections_1 = require("./connections");
53
57
  const index_1 = require("./index");
@@ -143,6 +147,7 @@ const logMw = async (ctx, next) => {
143
147
  const newPath = (0, misc_1.strinsert)(path, path.length - (0, path_1.extname)(path).length, suffix);
144
148
  try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
145
149
  (0, fs_1.renameSync)(path, newPath);
150
+ void zipLogFile(newPath, last).catch(console.error);
146
151
  }
147
152
  catch (e) { // ok, rename failed, but this doesn't mean we ain't gonna log
148
153
  console.error(e.message || String(e));
@@ -177,7 +182,7 @@ const logMw = async (ctx, next) => {
177
182
  debounce(() => // once in a while we check if the file is still good (not deleted, etc), or we'll reopen it
178
183
  (0, util_files_1.statWithTimeout)(logger.path).catch(() => logger.reopen())); // async = smoother but we may lose some entries
179
184
  stream.write(util.format(format, ctx.ip, user || '-', date, ctx.method, uri, ctx.req.httpVersion, ctx.status, length?.toString() ?? '-', lodash_1.default.isEmpty(extra) ? '' : JSON.stringify(JSON.stringify(extra))));
180
- });
185
+ }).catch(e => console.error('log completion:', e.message || String(e)));
181
186
  };
182
187
  exports.logMw = logMw;
183
188
  events_2.default.once('app', () => {
@@ -185,13 +190,33 @@ events_2.default.once('app', () => {
185
190
  lodash_1.default.merge(this.state, { logExtra: { ...anything, params } }); // params will be considered as parameters of the API
186
191
  };
187
192
  });
193
+ async function zipLogFile(path, touch) {
194
+ const zipPath = path + '.zip';
195
+ try {
196
+ const zip = new yazl_1.default.ZipFile();
197
+ const output = (0, fs_1.createWriteStream)(zipPath);
198
+ zip.addFile(path, (0, path_1.basename)(path));
199
+ zip.end();
200
+ await (0, promises_1.pipeline)(zip.outputStream, output);
201
+ }
202
+ catch (e) {
203
+ await (0, promises_2.rm)(zipPath, { force: true }).catch(() => { });
204
+ throw e;
205
+ }
206
+ await (0, promises_2.utimes)(zipPath, touch, touch).catch(console.error);
207
+ await events_2.default.emitAsync('logRotated', { path, zipPath });
208
+ await (0, promises_2.unlink)(path).catch(console.error);
209
+ }
188
210
  function doubleDigit(n) {
189
211
  return n > 9 ? n : '0' + n;
190
212
  }
191
213
  async function getRotatedFiles() {
192
214
  return Object.fromEntries(await Promise.all(exports.loggers.map(async (x) => {
193
215
  const mask = (0, misc_1.strinsert)(x.path, x.path.length - (0, path_1.extname)(x.path).length, '-2*'); // including 2, initial digit of the year, will only take rotated files and not "-error"
194
- return [x.name, (await (0, fast_glob_1.default)(mask, { stats: true })).map(x => ({ path: x.path, size: x.stats?.size }))];
216
+ const list = await Promise.all(['', '.zip'].map(async (postfix) => // before 3.2 rotated logs were not zipped, and even today there's a very small (negligible) chance that the zipping fails
217
+ (await (0, fast_glob_1.default)(mask + postfix, { stats: true }))
218
+ .map(x => ({ path: x.path, size: x.stats?.size }))));
219
+ return [x.name, list.flat()];
195
220
  })));
196
221
  }
197
222
  // dump console.error to file
@@ -52,7 +52,6 @@ const headRequests = async (ctx, next) => {
52
52
  exports.headRequests = headRequests;
53
53
  let proxyDetected;
54
54
  const someSecurity = (ctx, next) => {
55
- ctx.request.ip = (0, connections_1.normalizeIp)(ctx.ip);
56
55
  const ss = ctx.session;
57
56
  if (ss?.username && !ss?.[misc_1.ALLOW_SESSION_IP_CHANGE])
58
57
  if (!ss.ip)
@@ -91,6 +90,8 @@ function getProxyDetected() {
91
90
  return proxyDetected && { from: proxyDetected.socket.remoteAddress, for: proxyDetected.get('X-Forwarded-For') };
92
91
  }
93
92
  const prepareState = async (ctx, next) => {
93
+ // normalize once so auth, filters and logging agree on the same client address
94
+ ctx.request.ip = (0, connections_1.normalizeIp)(ctx.ip);
94
95
  const s = ctx.session;
95
96
  if (s?.username) {
96
97
  if (s.ts < auth_1.invalidateSessionBefore.get(s?.username))
@@ -99,7 +100,8 @@ const prepareState = async (ctx, next) => {
99
100
  }
100
101
  // calculate these once and for all
101
102
  ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
102
- let a = await urlLogin() || await getHttpAccount();
103
+ // explicit credentials and existing sessions must take precedence, so a matching IP cannot override a chosen account
104
+ let a = await urlLogin() || await getHttpAccount() || !s?.username && autoLogin();
103
105
  const loggedInNotBySession = a;
104
106
  ctx.state.account = a ||= (0, perm_1.getAccount)(s?.username, false); // with least precedence, we consider session
105
107
  if (a)
@@ -133,6 +135,10 @@ const prepareState = async (ctx, next) => {
133
135
  }
134
136
  catch { }
135
137
  }
138
+ function autoLogin() {
139
+ // keep the mask direct so group inheritance cannot make identity depend on account order
140
+ return Object.values(perm_1.accounts.get()).find(a => (0, perm_1.accountCanLogin)(a) && a.auto_login_net && (0, misc_1.netMatches)(ctx.ip, a.auto_login_net, true));
141
+ }
136
142
  };
137
143
  exports.prepareState = prepareState;
138
144
  function failAllowNet(ctx, a) {
package/src/misc.js CHANGED
@@ -26,6 +26,7 @@ exports.same = same;
26
26
  exports.asyncGeneratorToReadable = asyncGeneratorToReadable;
27
27
  exports.apiAssertTypes = apiAssertTypes;
28
28
  exports.createStreamLimiter = createStreamLimiter;
29
+ exports.retrySync = retrySync;
29
30
  const path_1 = require("path");
30
31
  __exportStar(require("./util-http"), exports);
31
32
  __exportStar(require("./util-files"), exports);
@@ -155,3 +156,16 @@ function createStreamLimiter(limit) {
155
156
  }
156
157
  });
157
158
  }
159
+ function retrySync(cb, attempts = 20, sleep = 500) {
160
+ const sleepSyncBuffer = new Int32Array(new SharedArrayBuffer(4));
161
+ for (let retry = 0;; retry++) {
162
+ try {
163
+ return cb();
164
+ }
165
+ catch (e) {
166
+ if (e?.code !== 'EBUSY' || retry >= attempts)
167
+ throw e;
168
+ Atomics.wait(sleepSyncBuffer, 0, 0, sleep);
169
+ }
170
+ }
171
+ }
package/src/nat.js CHANGED
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getNatInfo = exports.getPublicIps = exports.upnpClient = exports.defaultBaseUrl = void 0;
6
+ exports.getNatInfo = exports.getPublicIps = exports.upnpEnabled = exports.mappedPort = exports.defaultBaseUrl = void 0;
7
+ exports.upnpMappingParam = upnpMappingParam;
8
+ exports.getUpnpClient = getUpnpClient;
7
9
  const valtio_1 = require("valtio");
8
10
  const nat_upnp_1 = require("@rejetto/nat-upnp");
9
11
  const debounceAsync_1 = require("./debounceAsync");
@@ -16,6 +18,7 @@ const net_1 = require("net");
16
18
  const listen_1 = require("./listen");
17
19
  const child_process_1 = require("child_process");
18
20
  const const_1 = require("./const");
21
+ const config_1 = require("./config");
19
22
  exports.defaultBaseUrl = (0, valtio_1.proxy)({
20
23
  proto: 'http',
21
24
  publicIps: [],
@@ -30,20 +33,23 @@ exports.defaultBaseUrl = (0, valtio_1.proxy)({
30
33
  return `${this.proto}://${(0, cross_1.ipForUrl)(ip || 'localhost')}${!port || port === defPort ? '' : ':' + port}`;
31
34
  }
32
35
  });
33
- exports.upnpClient = new nat_upnp_1.Client({ timeout: 4_000 });
34
- const originalMethod = exports.upnpClient.getGateway;
35
- // other client methods call getGateway too, so this will ensure they reuse this same result
36
- exports.upnpClient.getGateway = (0, debounceAsync_1.debounceAsync)(() => originalMethod.apply(exports.upnpClient), { retain: cross_1.HOUR, retainFailure: 30_000 });
37
- exports.upnpClient.getGateway().then(res => {
38
- console.log("UPnP found", res.gateway.description);
39
- }, e => console.debug('UPnP failed:', e.message || String(e)));
40
- // poll external ip asking the modem is cheap, so it can be done often
41
- (0, cross_1.repeat)(cross_1.MINUTE, () => exports.upnpClient.getPublicIp().then(v => {
42
- if (v === exports.defaultBaseUrl.externalIp)
36
+ exports.mappedPort = (0, config_1.defineConfig)('mapped_port', 0);
37
+ exports.upnpEnabled = (0, config_1.defineConfig)(cross_1.CFG.upnp_enabled, true);
38
+ let upnpClient;
39
+ // poll external ip when UPnP is enabled, asking the modem is inexpensive, so it can be done often
40
+ (0, cross_1.repeat)(cross_1.MINUTE, async () => {
41
+ if (!await exports.upnpEnabled.getWhenReady())
42
+ return;
43
+ const v = await getUpnpClient().getPublicIp().catch(() => '');
44
+ if (!v || v === exports.defaultBaseUrl.externalIp)
43
45
  return;
44
46
  exports.getPublicIps.clearRetain();
45
- return exports.defaultBaseUrl.externalIp = v;
46
- }, () => { }));
47
+ exports.defaultBaseUrl.externalIp = v;
48
+ });
49
+ function upnpMappingParam(privatePort, publicPort, description = 'hfs', ttl = 0) {
50
+ // nat-upnp-rejetto requires the object form of `public` to preserve the host field correctly
51
+ return { private: privatePort, public: { host: '', port: publicPort }, description, ttl };
52
+ }
47
53
  exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
48
54
  const res = await (0, github_1.getProjectInfo)();
49
55
  const groupedByVersion = Object.values(lodash_1.default.groupBy(res.publicIpServices, x => x.v ?? 4));
@@ -70,25 +76,36 @@ exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
70
76
  throw "no good";
71
77
  return validIps;
72
78
  }))));
73
- 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;
74
83
  }, { retain: 10 * cross_1.MINUTE });
75
84
  exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
85
+ const upnp = await exports.upnpEnabled.getWhenReady() ? getUpnpClient() : null;
76
86
  const gatewayIpPromise = findGateway().catch(() => undefined);
77
- const res = await (0, cross_1.haveTimeout)(10_000, exports.upnpClient.getGateway()).catch(() => null);
87
+ const gw = upnp && await (0, cross_1.haveTimeout)(10_000, upnp.getGateway()).catch(() => null);
78
88
  const status = await (0, listen_1.getServerStatus)();
79
- const mappings = res && await (0, cross_1.haveTimeout)(5_000, exports.upnpClient.getMappings()).catch(() => null);
80
- console.debug("Mappings found:", mappings?.map(x => x.description).join(', ') || "none");
89
+ let mappings = gw && await (0, cross_1.haveTimeout)(5_000, upnp.getMappings())?.catch(() => null);
90
+ console.debug(gw ? "Mappings found:" : "Mappings not queried:", mappings?.map(x => x.description).join(', ') || (gw ? "none" : upnp ? "gateway not found" : "UPnP disabled"));
81
91
  const localIps = await (0, listen_1.getIps)(false);
82
92
  const gatewayIp = await gatewayIpPromise;
83
- const localIp = res?.address || (gatewayIp ? lodash_1.default.maxBy(localIps, x => (0, cross_1.inCommon)(x, gatewayIp)) : localIps[0]);
93
+ const localIp = gw?.address || (gatewayIp ? lodash_1.default.maxBy(localIps, x => (0, cross_1.inCommon)(x, gatewayIp)) : localIps[0]);
84
94
  const internalPort = status?.https?.listening && status.https.port || status?.http?.listening && status.http.port || undefined;
85
- const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
95
+ let mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
96
+ if (upnp && mappings && localIp && internalPort && !mapped && exports.mappedPort.get())
97
+ // restore the HFS-created mapping after routers that forget UPnP state across reboots
98
+ await (0, cross_1.haveTimeout)(5_000, upnp.createMapping(upnpMappingParam(internalPort, exports.mappedPort.get()))).then(async () => {
99
+ // confirm router state after restore instead of trusting the AddPortMapping result
100
+ mappings = await (0, cross_1.haveTimeout)(5_000, upnp.getMappings());
101
+ mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
102
+ }).catch(e => console.warn('UPnP mapping restore failed:', e?.message || String(e)));
86
103
  const externalPort = mapped?.public.port;
87
104
  if (localIp)
88
105
  exports.defaultBaseUrl.localIp = localIp;
89
106
  exports.defaultBaseUrl.port = externalPort || internalPort || 0;
90
107
  return {
91
- upnp: Boolean(res),
108
+ upnp: Boolean(gw),
92
109
  localIp,
93
110
  gatewayIp,
94
111
  externalIp: exports.defaultBaseUrl.externalIp,
@@ -99,7 +116,30 @@ exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
99
116
  proto: status?.https?.listening ? 'https' : status?.http?.listening ? 'http' : '',
100
117
  };
101
118
  }, { reuseRunning: true });
102
- (0, exports.getNatInfo)();
119
+ exports.upnpEnabled.sub(v => {
120
+ exports.getNatInfo.clearRetain();
121
+ if (v) {
122
+ getUpnpClient().getGateway().then(res => console.log("UPnP found", res.gateway.description), e => console.debug('UPnP failed:', e.message || String(e)));
123
+ return;
124
+ }
125
+ // closing the client guarantees the disabled setting also stops any existing SSDP socket
126
+ upnpClient?.close();
127
+ upnpClient = undefined;
128
+ exports.defaultBaseUrl.externalIp = '';
129
+ exports.getPublicIps.clearRetain();
130
+ });
131
+ config_1.configReady.then(exports.getNatInfo).catch(() => { });
132
+ function getUpnpClient() {
133
+ if (!exports.upnpEnabled.get()) // keep this guard upstream so disabled UPnP cannot leak SSDP traffic through callers
134
+ throw Error("UPnP disabled");
135
+ if (!upnpClient) {
136
+ upnpClient = new nat_upnp_1.Client({ timeout: 4_000 });
137
+ const originalMethod = upnpClient.getGateway;
138
+ // other client methods call getGateway too, so this will ensure they reuse this same result
139
+ upnpClient.getGateway = (0, debounceAsync_1.debounceAsync)(() => originalMethod.apply(upnpClient), { retain: cross_1.HOUR, retainFailure: 30_000 });
140
+ }
141
+ return upnpClient;
142
+ }
103
143
  function findGateway() {
104
144
  return new Promise((resolve, reject) => (0, child_process_1.exec)(const_1.IS_WINDOWS || const_1.IS_MAC ? 'netstat -rn' : 'route -n', (err, out) => {
105
145
  if (err)
@@ -22,7 +22,7 @@ const outboundProxy = (0, config_1.defineConfig)(cross_1.CFG.outbound_proxy, '',
22
22
  return '';
23
23
  }
24
24
  });
25
- config_1.configReady.then(async ([startedWithoutConfig]) => {
25
+ config_1.configReady.then(async (startedWithoutConfig) => {
26
26
  if (!const_1.IS_WINDOWS || !startedWithoutConfig)
27
27
  return;
28
28
  // try to read Windows system setting for proxy
package/src/perm.js CHANGED
@@ -18,6 +18,7 @@ exports.addAccount = addAccount;
18
18
  exports.delAccount = delAccount;
19
19
  exports.getFromAccount = getFromAccount;
20
20
  exports.accountHasPassword = accountHasPassword;
21
+ exports.accountHasLoginMethod = accountHasLoginMethod;
21
22
  exports.accountCanLogin = accountCanLogin;
22
23
  exports.accountIsDisabled = accountIsDisabled;
23
24
  exports.accountCanLoginAdmin = accountCanLoginAdmin;
@@ -208,8 +209,11 @@ function getFromAccount(account, getter) {
208
209
  function accountHasPassword(account) {
209
210
  return Boolean(account.password || account.srp);
210
211
  }
212
+ function accountHasLoginMethod(account) {
213
+ return Boolean(accountHasPassword(account) || account.plugin?.auth || account.auto_login_net);
214
+ }
211
215
  function accountCanLogin(account) {
212
- return (accountHasPassword(account) || account.plugin?.auth) && !accountIsDisabled(account);
216
+ return accountHasLoginMethod(account) && !accountIsDisabled(account);
213
217
  }
214
218
  function accountIsDisabled(account) {
215
219
  return Boolean(account.disabled
package/src/plugins.js CHANGED
@@ -157,9 +157,30 @@ async function initPlugin(pl, morePassedToInit) {
157
157
  const timeouts = [];
158
158
  const controlledEvents = Object.create(events_1.default, (0, misc_1.objFromKeys)(['on', 'once', 'multi'], k => ({
159
159
  value() {
160
+ if (k === 'multi')
161
+ arguments[0] = (0, misc_1.objSameKeys)(arguments[0], trap);
162
+ else
163
+ arguments[1] = trap(arguments[1]);
160
164
  const ret = events_1.default[k](...arguments);
161
165
  undoEvents.push(ret);
162
166
  return ret;
167
+ function trap(cb) {
168
+ return (...args) => {
169
+ try {
170
+ if (!lodash_1.default.isFunction(cb))
171
+ return;
172
+ const ret = cb(...args);
173
+ return ret instanceof Promise ? ret.catch(printError) : ret;
174
+ }
175
+ catch (e) {
176
+ printError(e);
177
+ }
178
+ function printError(e) {
179
+ const { event } = args.at(-1);
180
+ console.error(`plugin ${morePassedToInit?.id || '?'} on event ${event}:`, e);
181
+ }
182
+ };
183
+ }
163
184
  }
164
185
  })));
165
186
  const res = await pl.init?.({
@@ -174,8 +195,16 @@ async function initPlugin(pl, morePassedToInit) {
174
195
  timeouts.push(ret); // intervals can be canceled by clearTimeout (source: MDN)
175
196
  return ret;
176
197
  },
177
- setTimeout() {
178
- const ret = setTimeout(...arguments);
198
+ setTimeout(cb, delay = 0, ...args) {
199
+ let ret;
200
+ if (lodash_1.default.isFunction(cb)) {
201
+ const original = cb;
202
+ cb = function () {
203
+ lodash_1.default.pull(timeouts, ret);
204
+ return original.apply(this, args);
205
+ };
206
+ }
207
+ ret = setTimeout(cb, delay, ...args); // 'any' to allow build of frontend and admin
179
208
  timeouts.push(ret);
180
209
  return ret;
181
210
  },
@@ -426,17 +455,17 @@ async function rescan() {
426
455
  const patterns = [exports.PATH + '/*'];
427
456
  if (const_1.APP_PATH !== process.cwd())
428
457
  patterns.unshift((0, misc_1.escapeGlobPath)(const_1.APP_PATH) + '/' + patterns[0]); // first search bundled plugins, because otherwise they won't be loaded because of the folders with same name in .hfs/plugins (used for storage)
429
- const existing = [];
458
+ const existing = new Set();
430
459
  for (const { path, dirent } of await (0, fast_glob_1.default)(patterns, { onlyFiles: false, suppressErrors: true, objectMode: true })) {
431
460
  if (!dirent.isDirectory() || path.endsWith(exports.DISABLING_SUFFIX))
432
461
  continue;
433
462
  const id = path.split('/').slice(-1)[0];
434
- existing.push(id);
463
+ existing.add(id);
435
464
  if (!pluginWatchers.has(id))
436
465
  pluginWatchers.set(id, watchPlugin(id, (0, path_1.join)(path, exports.PLUGIN_MAIN_FILE)));
437
466
  }
438
467
  for (const [id, cancelWatcher] of pluginWatchers.entries())
439
- if (!existing.includes(id)) {
468
+ if (!existing.has(id)) {
440
469
  enablePlugin(id, false);
441
470
  cancelWatcher();
442
471
  pluginWatchers.delete(id);
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
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)(8_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)
@@ -28,6 +28,7 @@ const comments_1 = require("./comments");
28
28
  const basicWeb_1 = require("./basicWeb");
29
29
  const icons_1 = require("./icons");
30
30
  const plugins_1 = require("./plugins");
31
+ const roots_1 = require("./roots");
31
32
  const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, cross_const_1.FRONTEND_URI);
32
33
  const serveFrontendPrefixed = (0, koa_mount_1.default)(cross_const_1.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
33
34
  const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, cross_const_1.ADMIN_URI);
@@ -110,6 +111,7 @@ const serveSharedFiles = async (ctx, next) => {
110
111
  if ((await events_1.default.emitAsync('deleting', { node, ctx }))?.isDefaultPrevented())
111
112
  return ctx.status = cross_const_1.HTTP_FAILED_DEPENDENCY;
112
113
  await (0, promises_1.rm)(source, { recursive: true });
114
+ await (0, misc_1.deleteStoredFileAttrs)(source);
113
115
  void (0, comments_1.setCommentFor)(source, ''); // necessary only to clean a possible descript.ion or kvstorage
114
116
  return ctx.status = cross_const_1.HTTP_OK;
115
117
  }
@@ -118,11 +120,10 @@ const serveSharedFiles = async (ctx, next) => {
118
120
  return ctx.status = cross_const_1.HTTP_SERVER_ERROR;
119
121
  }
120
122
  }
121
- if (node.default && path.endsWith('/') && !get) { // final/ needed on browser to make resource urls correctly with html pages
122
- const found = await (0, vfs_1.urlToNode)(node.default, ctx, node);
123
- 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)))
124
126
  ctx.state.considerAsGui = true;
125
- node = found ?? node;
126
127
  }
127
128
  if (get === 'icon')
128
129
  return (0, serveFile_1.serveFile)(ctx, node.icon || '|'); // pipe to cause not-found
@@ -159,9 +160,19 @@ async function sendFolderList(node, ctx) {
159
160
  ctx.type = 'text';
160
161
  if (prepend === undefined || prepend === '*') { // * = force auto-detection even if we have baseUrl set
161
162
  const { URL } = ctx;
162
- const base = prepend === undefined && listen_1.baseUrl.get()
163
- || URL.protocol + '//' + URL.host + ctx.state.revProxyPath;
164
- 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);
165
176
  }
166
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
167
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)}`);
@@ -170,6 +170,7 @@ function serveProxied(port, uri) {
170
170
  let proxy;
171
171
  import('koa-better-http-proxy').then(lib => // dynamic import to avoid having this in final distribution
172
172
  proxy = lib.default('127.0.0.1:' + port, {
173
+ parseReqBody: false, // the dev GUI proxy serves app/assets, so avoid koa-better-http-proxy trying to reread ctx.req
173
174
  proxyReqPathResolver: (ctx) => shouldServeApp(ctx) ? '/' : ctx.path,
174
175
  userResDecorator(res, data, ctx) {
175
176
  return shouldServeApp(ctx) ? treatIndex(ctx, uri, String(data))
package/src/update.js CHANGED
@@ -27,6 +27,7 @@ const first_1 = require("./first");
27
27
  const persistence_1 = require("./persistence");
28
28
  const lodash_1 = __importDefault(require("lodash"));
29
29
  const argv_1 = require("./argv");
30
+ const promises_2 = require("stream/promises");
30
31
  const updateToBeta = (0, config_1.defineConfig)('update_to_beta', false);
31
32
  const autoCheckUpdate = (0, config_1.defineConfig)('auto_check_update', true);
32
33
  const lastCheckUpdate = persistence_1.storedMap.singleSync('lastCheckUpdate', 0);
@@ -127,9 +128,7 @@ async function update(tagOrUrl = '') {
127
128
  throw "No update has been found";
128
129
  const plat = '-' + (0, misc_1.xlate)(process.platform, { win32: 'windows', darwin: 'mac' });
129
130
  const assetSearch = `${plat}-${process.arch}`;
130
- const legacyAssetSearch = `${plat}${(0, misc_1.prefix)('-', (0, misc_1.xlate)(process.arch, { x64: '', arm64: 'arm' }))}.zip`; // legacy pre-0.53.0-rc16
131
- const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'))
132
- || update.assets.find((x) => x.name.endsWith(legacyAssetSearch));
131
+ const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'));
133
132
  if (!asset)
134
133
  throw `Asset not found: ${assetSearch}`;
135
134
  url = asset.browser_download_url;
@@ -139,7 +138,13 @@ async function update(tagOrUrl = '') {
139
138
  const temp = LOCAL_UPDATE + '-temp';
140
139
  await (0, promises_1.rm)(temp, { force: true });
141
140
  try {
142
- await (0, promises_1.writeFile)(temp, await (0, misc_1.httpStream)(url));
141
+ const stream = await (0, misc_1.httpStream)(url);
142
+ const total = Number(stream.headers['content-length']) || 0;
143
+ let downloadedSize = 0;
144
+ const progress = total && setInterval(() => console.log("Download progress", (0, misc_1.formatPerc)(downloadedSize / total)), 5_000);
145
+ stream.on('data', chunk => downloadedSize += chunk.length);
146
+ await (0, promises_2.pipeline)(stream, (0, fs_1.createWriteStream)(temp))
147
+ .finally(() => clearInterval(progress));
143
148
  }
144
149
  catch (e) {
145
150
  await (0, promises_1.rm)(temp).catch(() => { }); // no leftovers
@@ -182,7 +187,7 @@ async function update(tagOrUrl = '') {
182
187
  (0, fs_1.renameSync)(bin, oldBin);
183
188
  if (!preserveTerminal) {
184
189
  try {
185
- renameSyncWithBusyRetry(newBin, (0, path_1.join)(binPath, binFile));
190
+ (0, misc_1.retrySync)(() => (0, fs_1.renameSync)(newBin, (0, path_1.join)(binPath, binFile)));
186
191
  }
187
192
  catch (e) {
188
193
  try {
@@ -209,19 +214,6 @@ async function update(tagOrUrl = '') {
209
214
  throw e?.message || String(e);
210
215
  }
211
216
  }
212
- function renameSyncWithBusyRetry(src, dest) {
213
- const sleepSyncBuffer = new Int32Array(new SharedArrayBuffer(4));
214
- for (let retry = 0;; retry++) {
215
- try {
216
- return (0, fs_1.renameSync)(src, dest);
217
- }
218
- catch (e) {
219
- if (e?.code !== 'EBUSY' || retry >= 20)
220
- throw e;
221
- Atomics.wait(sleepSyncBuffer, 0, 0, 500);
222
- }
223
- }
224
- }
225
217
  if (argv_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
226
218
  const bin = process.execPath;
227
219
  const dest = (0, path_1.join)((0, path_1.dirname)(bin), argv_1.argv.updating);