hfs 0.55.0 → 0.55.2-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-BoMy1vEB.js"],(function(h,t){"use strict";var i,s;return{setters:[function(h){i=h.g,s=h.c}],execute:function(){function t(h,t){for(var i=function(){var i=t[s];if("string"!=typeof i&&!Array.isArray(i)){var r=function(t){if("default"!==t&&!(t in h)){var s=Object.getOwnPropertyDescriptor(i,t);s&&Object.defineProperty(h,t,s.get?s:{enumerable:!0,get:function(){return i[t]}})}};for(var e in i)r(e)}},s=0;s<t.length;s++)i();return Object.freeze(Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}))}var r={exports:{}};
1
+ System.register(["./index-legacy-BFUS5Ol_.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
  *
@@ -22,6 +22,6 @@
22
22
  document.getElementById('root').innerHTML = 'Loading...'
23
23
  </script>
24
24
  <script crossorigin id="vite-legacy-polyfill" src="/assets/polyfills-legacy-DMrMt_pQ.js"></script>
25
- <script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-BoMy1vEB.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
25
+ <script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-BFUS5Ol_.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
26
26
  </body>
27
27
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.55.0",
3
+ "version": "0.55.2-beta3",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": ["file server", "http server"],
6
6
  "homepage": "https://rejetto.com/hfs",
@@ -79,6 +79,7 @@
79
79
  "fs-x-attributes": "^1.0.2",
80
80
  "fswin": "^3.24.829",
81
81
  "iconv-lite": "^0.6.3",
82
+ "immer": "^9.0.21",
82
83
  "ip2location-nodejs": "^9.6.0",
83
84
  "koa": "^2.15.3",
84
85
  "koa-compress": "^5.1.0",
package/src/config.js CHANGED
@@ -196,10 +196,6 @@ const saveDebounced = (0, misc_1.debounceAsync)(async () => {
196
196
  const aWeekAgo = Date.now() - misc_1.DAY * 7;
197
197
  if (await (0, promises_1.stat)(bak).then(x => aWeekAgo > Number(x.mtime || x.ctime), () => true))
198
198
  await (0, promises_1.copyFile)(filePath, bak).catch(() => { }); // ignore errors
199
- if (lodash_1.default.isEmpty(state)) {
200
- console.error('saving empty config');
201
- debugger;
202
- }
203
199
  await exports.configFile.save(stringify({ ...state, version: const_1.VERSION }))
204
200
  .catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
205
201
  });
package/src/const.js CHANGED
@@ -54,7 +54,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
54
54
  exports.ORIGINAL_CWD = process.cwd();
55
55
  exports.HFS_STARTED = new Date();
56
56
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
57
- exports.BUILD_TIMESTAMP = "2024-12-30T14:34:39.587Z";
57
+ exports.BUILD_TIMESTAMP = "2025-01-03T13:39:42.709Z";
58
58
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
59
59
  exports.VERSION = pkg.version;
60
60
  exports.RUNNING_BETA = exports.VERSION.includes('-');
@@ -80,7 +80,7 @@ console.debug('arguments', exports.argv);
80
80
  const dir = exports.argv.cwd || useHomeDir() && (0, path_1.join)((0, os_1.homedir)(), '.hfs');
81
81
  if (dir) {
82
82
  try {
83
- fs.mkdirSync(dir);
83
+ fs.mkdirSync(dir, { recursive: true });
84
84
  }
85
85
  catch (e) {
86
86
  if (e.code !== 'EEXIST')
@@ -88,6 +88,8 @@ if (dir) {
88
88
  }
89
89
  process.chdir(dir);
90
90
  }
91
+ else if (process.cwd().startsWith(process.env.windir + '\\')) // this happens if you run hfs from task scheduler
92
+ process.chdir(exports.APP_PATH);
91
93
  console.log('working directory (cwd)', process.cwd());
92
94
  if (exports.APP_PATH !== process.cwd())
93
95
  console.log('app', exports.APP_PATH);
@@ -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.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;
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.UPLOAD_STATUS = exports.UPLOAD_RESUMABLE = 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/';
@@ -10,6 +10,8 @@ exports.PORT_DISABLED = -1;
10
10
  exports.NBSP = '\xA0';
11
11
  exports.PLUGIN_CUSTOM_REST_PREFIX = '_';
12
12
  exports.HFS_REPO = 'rejetto/hfs';
13
+ exports.UPLOAD_RESUMABLE = 'upload.resumable';
14
+ exports.UPLOAD_STATUS = 'upload.status';
13
15
  exports.HTTP_OK = 200;
14
16
  exports.HTTP_NO_CONTENT = 204;
15
17
  exports.HTTP_PARTIAL_CONTENT = 206;
package/src/cross.js CHANGED
@@ -534,22 +534,3 @@ const BROWSERS = {
534
534
  Xbox: /xbox/i,
535
535
  UC: /UCBrowser/i,
536
536
  };
537
- Object.defineProperties(Object.prototype, {
538
- _L: {
539
- enumerable: false,
540
- writable: true,
541
- value(msg = '') {
542
- const val = this instanceof Number || this instanceof String || this instanceof Boolean ? this.valueOf() : this;
543
- console.log('**', msg, val);
544
- return val;
545
- }
546
- },
547
- _D: {
548
- enumerable: false,
549
- writable: true,
550
- value(msg = '') {
551
- debugger;
552
- return this._L(msg);
553
- }
554
- }
555
- });
package/src/i18n.js CHANGED
@@ -40,6 +40,8 @@ function i18nFromTranslations(translations, embedded = 'en') {
40
40
  for (const lang of searchLangs)
41
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
42
  break;
43
+ if (found)
44
+ break;
43
45
  if (!warns.has(key) && langs.length && langs[0] !== embedded) {
44
46
  warns.add(key);
45
47
  console.debug("miss i18n:", key);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "thenoppy12 (Nguyễn Văn Ngu)",
3
- "version": 2.5,
4
- "hfs_version": "0.54.0",
3
+ "version": 2.6,
4
+ "hfs_version": "0.55.0",
5
5
  "translate": {
6
6
  "Select": "Chọn",
7
7
  "n_files": "{n,plural,one{# tập tin} other{# tập tin}}",
@@ -143,7 +143,7 @@
143
143
  "Close": "Đóng",
144
144
  "Folder": "Thư mục",
145
145
  "Web page": "Trang web",
146
- "Link": "Link",
146
+ "Link": "Đường dẫn",
147
147
  "Auto-play": "Tự động phát",
148
148
  "autoplay_seconds": "Số giây chờ ảnh",
149
149
  "Select all": "Chọn hết",
@@ -166,6 +166,8 @@
166
166
  "Logged in": "Đã đăng nhập!",
167
167
  "Logged out": "Đã đăng xuất!",
168
168
  "Cancel": "Huỷ bỏ",
169
- "allow_session_ip_change": "Cho phép đổi IP trong phiên này"
169
+ "allow_session_ip_change": "Cho phép đổi IP trong phiên này",
170
+ "focus_hint": "Bằng cách nhập nhanh lên bàn phím, có thể tìm và xem các phần tử trong danh sách",
171
+ "copy_links": "Copy đường dẫn"
170
172
  }
171
173
  }
@@ -54,6 +54,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
54
54
  ctx.status = cross_const_1.HTTP_SERVER_ERROR;
55
55
  ctx.body = err.message || String(err);
56
56
  });
57
+ ctx.req.on('close', () => dest.end());
57
58
  const uri = await dest.lockMiddleware; // we need to wait more than just the stream
58
59
  ctx.body = { uri };
59
60
  }
package/src/upload.js CHANGED
@@ -37,7 +37,7 @@ function setUploadMeta(path, ctx) {
37
37
  }
38
38
  // stay sync because we use this function with formidable()
39
39
  const diskSpaceCache = (0, expiringCache_1.expiringCache)(3000); // invalidate shortly
40
- const openFiles = new Set();
40
+ const uploadingFiles = new Set();
41
41
  function uploadWriter(base, baseUri, path, ctx) {
42
42
  let fullPath = '';
43
43
  if ((0, misc_1.dirTraversal)(path))
@@ -78,12 +78,12 @@ function uploadWriter(base, baseUri, path, ctx) {
78
78
  catch (e) { // warn, but let it through
79
79
  console.warn("can't check disk size:", e.message || String(e));
80
80
  }
81
- if (openFiles.has(fullPath))
82
- return fail(const_1.HTTP_CONFLICT, 'uploading');
83
81
  // optionally 'skip'
84
82
  if (ctx.query.existing === 'skip' && fs_1.default.existsSync(fullPath))
85
83
  return fail(const_1.HTTP_CONFLICT, 'exists');
86
- openFiles.add(fullPath);
84
+ if (uploadingFiles.has(fullPath))
85
+ return fail(const_1.HTTP_CONFLICT, 'uploading');
86
+ uploadingFiles.add(fullPath);
87
87
  let overwriteRequestedButForbidden = false;
88
88
  try {
89
89
  // if upload creates a folder, then add meta to it too
@@ -91,38 +91,42 @@ function uploadWriter(base, baseUri, path, ctx) {
91
91
  setUploadMeta(dir, ctx);
92
92
  // use temporary name while uploading
93
93
  const keepName = (0, path_1.basename)(fullPath).slice(-200);
94
- let tempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
95
- const resumable = fs_1.default.existsSync(tempName) && !openFiles.has(tempName) && tempName; // resumable is temp-file-1
96
- if (resumable)
97
- tempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
94
+ const firstTempName = (0, path_1.join)(dir, 'hfs$upload-' + keepName);
95
+ const altTempName = (0, path_1.join)(dir, 'hfs$upload2-' + keepName);
96
+ const splitAndPreserving = ctx.query.preserveTempFile; // frontend knows about existing temp that can be resumed, but it is not resuming that, but instead it is continuing split-uploading on alternative temp file
97
+ let tempName = splitAndPreserving ? altTempName : firstTempName;
98
+ const stats = (0, misc_1.try_)(() => fs_1.default.statSync(tempName));
99
+ const resumableSize = (stats === null || stats === void 0 ? void 0 : stats.size) || 0; // we use size to even when user has not required resume, yet, to notify frontend of the possibility
100
+ const resumableTempName = resumableSize > 0 && tempName;
101
+ if (resumableTempName)
102
+ tempName = altTempName;
98
103
  // checks for resume feature
99
104
  let resume = Number(ctx.query.resume);
100
- const size = resumable && (0, misc_1.try_)(() => fs_1.default.statSync(resumable).size);
101
- if (size === undefined) // stat failed
102
- return fail(const_1.HTTP_SERVER_ERROR);
103
- if (lodash_1.default.isNumber(size) && resume > size)
105
+ if (resume > resumableSize)
104
106
  return fail(const_1.HTTP_RANGE_NOT_SATISFIABLE);
105
107
  // warn frontend about resume possibility
106
108
  let resumableLost = false;
107
- if (!resume && resumable) {
109
+ if (!resume && !resumableTempName)
110
+ (0, frontEndApis_1.notifyClient)(ctx, const_1.UPLOAD_RESUMABLE, { [path]: 0 });
111
+ if (!resume && resumableTempName) {
108
112
  const timeout = 30;
109
- (0, frontEndApis_1.notifyClient)(ctx, 'upload.resumable', { [path]: size, expires: Date.now() + timeout * 1000 });
110
- delayedDelete(resumable, timeout, () => // if user resumes, this upload is interrupted, and next upload will cancel this delayedDelete
111
- fs_1.default.rename(tempName, resumable, err => {
113
+ (0, frontEndApis_1.notifyClient)(ctx, const_1.UPLOAD_RESUMABLE, { [path]: resumableSize, expires: Date.now() + timeout * 1000 });
114
+ delayedDelete(resumableTempName, timeout, () => // if user resumes, this upload is interrupted, and next upload will cancel this delayedDelete
115
+ fs_1.default.rename(tempName, resumableTempName, err => {
112
116
  if (err)
113
117
  return;
114
- tempName = resumable;
118
+ tempName = resumableTempName;
115
119
  resumableLost = true;
116
120
  }));
117
121
  }
118
122
  // append if resuming
119
- const resuming = resume && resumable;
123
+ const resuming = resume && resumableTempName;
120
124
  if (!resuming)
121
125
  resume = 0;
122
126
  const writeStream = (0, misc_1.createStreamLimiter)(reqSize !== null && reqSize !== void 0 ? reqSize : Infinity);
123
- if (resuming) {
127
+ if (resuming && !splitAndPreserving) {
124
128
  fs_1.default.rm(tempName, () => { });
125
- tempName = resumable;
129
+ tempName = resumableTempName;
126
130
  }
127
131
  cancelDeletion(tempName);
128
132
  ctx.state.uploadDestinationPath = tempName;
@@ -131,7 +135,7 @@ function uploadWriter(base, baseUri, path, ctx) {
131
135
  const resEvent = events_1.default.emit('uploadStart', obj);
132
136
  if (resEvent === null || resEvent === void 0 ? void 0 : resEvent.isDefaultPrevented())
133
137
  return;
134
- const fileStream = resuming ? fs_1.default.createWriteStream(resumable, { flags: 'r+', start: resume })
138
+ const fileStream = resuming ? fs_1.default.createWriteStream(resumableTempName, { flags: 'r+', start: resume })
135
139
  : fs_1.default.createWriteStream(tempName);
136
140
  writeStream.on('error', e => {
137
141
  releaseFile();
@@ -145,7 +149,7 @@ function uploadWriter(base, baseUri, path, ctx) {
145
149
  try {
146
150
  await new Promise(res => fileStream.close(res)); // this only seem to be necessary on Windows
147
151
  if (ctx.req.aborted) {
148
- if (resumable && !resumableLost && !resuming) // we don't want to be left with 2 temp files
152
+ if (resumableTempName && !resumableLost && !resuming) // we don't want to be left with 2 temp files
149
153
  return (0, promises_1.rm)(tempName);
150
154
  const sec = exports.deleteUnfinishedUploadsAfter.get();
151
155
  return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec);
@@ -168,12 +172,14 @@ function uploadWriter(base, baseUri, path, ctx) {
168
172
  try {
169
173
  await (0, promises_1.rename)(tempName, dest);
170
174
  cancelDeletion(tempName); // not necessary, as deletion's failure is silent, but still
175
+ if (splitAndPreserving) // we've been using altTempName, but now we're done, so we can delete firstTempName
176
+ delayedDelete(firstTempName, 0);
171
177
  ctx.state.uploadDestinationPath = dest;
172
178
  setUploadMeta(dest, ctx);
173
179
  if (ctx.query.comment)
174
180
  void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
175
- if (resumable && !resuming) // this happens if user decided to not resume and the new upload finished before delayedDelete
176
- (0, promises_1.rm)(resumable).catch(console.warn);
181
+ if (resumableTempName && !resuming) // this happens if user decided to not resume and the new upload finished before delayedDelete
182
+ (0, promises_1.rm)(resumableTempName).catch(console.warn);
177
183
  obj.uri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(dest));
178
184
  events_1.default.emit('uploadFinished', obj);
179
185
  if (resEvent)
@@ -236,7 +242,7 @@ function uploadWriter(base, baseUri, path, ctx) {
236
242
  delete waitingToBeDeleted[path];
237
243
  }
238
244
  function releaseFile() {
239
- openFiles.delete(fullPath);
245
+ uploadingFiles.delete(fullPath);
240
246
  }
241
247
  function fail(status, msg) {
242
248
  console.debug('upload failed', status, msg);
@@ -245,7 +251,7 @@ function uploadWriter(base, baseUri, path, ctx) {
245
251
  ctx.status = status;
246
252
  if (msg)
247
253
  ctx.body = msg;
248
- (0, frontEndApis_1.notifyClient)(ctx, 'upload.status', { [path]: ctx.status }); // allow browsers to detect failure while still sending body
254
+ (0, frontEndApis_1.notifyClient)(ctx, const_1.UPLOAD_STATUS, { [path]: ctx.status }); // allow browsers to detect failure while still sending body
249
255
  }
250
256
  }
251
257
  exports.uploadWriter = uploadWriter;