instbyte 1.9.0 → 1.9.1

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.
package/client/js/app.js CHANGED
@@ -1250,13 +1250,32 @@ async function uploadFiles(files) {
1250
1250
  const xhr = new XMLHttpRequest();
1251
1251
  xhr.open("POST", "/upload", true);
1252
1252
 
1253
+ // Stall detection — if no progress for 30s, the connection is dead.
1254
+ // We don't use xhr.timeout because that would kill legitimate large
1255
+ // file uploads on slow LANs.
1256
+ // Instead we track the last progress event and abort if nothing moves for 30 seconds.
1257
+ const STALL_MS = 30_000;
1258
+ let stallTimer = null;
1259
+
1260
+ function resetStallTimer() {
1261
+ clearTimeout(stallTimer);
1262
+ stallTimer = setTimeout(() => {
1263
+ xhr.abort();
1264
+ text.innerText = `⚠ ${file.name} stalled — skipped`;
1265
+ bar.style.width = "0%";
1266
+ setTimeout(resolve, 1200);
1267
+ }, STALL_MS);
1268
+ }
1269
+
1253
1270
  xhr.upload.onprogress = e => {
1254
1271
  if (e.lengthComputable) {
1255
1272
  bar.style.width = Math.round((e.loaded / e.total) * 100) + "%";
1256
1273
  }
1274
+ resetStallTimer(); // data is moving, push the timer out
1257
1275
  };
1258
1276
 
1259
1277
  xhr.onload = () => {
1278
+ clearTimeout(stallTimer);
1260
1279
  if (xhr.status === 413) {
1261
1280
  text.innerText = `⚠ ${file.name} is too large — skipped`;
1262
1281
  bar.style.width = "0%";
@@ -1267,11 +1286,21 @@ async function uploadFiles(files) {
1267
1286
  };
1268
1287
 
1269
1288
  xhr.onerror = () => {
1289
+ clearTimeout(stallTimer);
1270
1290
  text.innerText = `⚠ ${file.name} failed — skipped`;
1271
1291
  bar.style.width = "0%";
1272
1292
  setTimeout(resolve, 1200);
1273
1293
  };
1274
1294
 
1295
+ xhr.onabort = () => {
1296
+ clearTimeout(stallTimer);
1297
+ // ontimeout/stall already set the message — only handle
1298
+ // explicit user-triggered aborts here if we add a cancel
1299
+ // button in future. For now just resolve to unblock queue.
1300
+ resolve();
1301
+ };
1302
+
1303
+ resetStallTimer(); // start timer — covers stall before first progress
1275
1304
  xhr.send(form);
1276
1305
  });
1277
1306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instbyte",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "A self-hosted LAN sharing utility for fast, frictionless file, link, and snippet exchange across devices — no cloud required.",
5
5
  "main": "server/server.js",
6
6
  "bin": {
package/server/db.js CHANGED
@@ -6,6 +6,7 @@ const dbPath = process.env.INSTBYTE_DATA
6
6
  : path.join(__dirname, "../db.sqlite");
7
7
 
8
8
  const db = new sqlite3.Database(dbPath);
9
+ db.configure("busyTimeout", 5000);
9
10
 
10
11
  db.serialize(() => {
11
12
 
package/server/server.js CHANGED
@@ -21,6 +21,29 @@ const config = require("./config");
21
21
  const UPLOADS_DIR = process.env.INSTBYTE_UPLOADS
22
22
  || path.join(__dirname, "../uploads");
23
23
 
24
+ /* STARTUP ORPHAN SCAN
25
+ Deletes any files in uploads dir that have no matching DB record.
26
+ Catches ghost files left by aborted uploads before fix in v1.9.1 */
27
+ function scanOrphans() {
28
+ fs.readdir(UPLOADS_DIR, (err, files) => {
29
+ if (err || !files || !files.length) return;
30
+
31
+ db.all("SELECT filename FROM items WHERE filename IS NOT NULL", (err, rows) => {
32
+ if (err) return;
33
+
34
+ const known = new Set(rows.map(r => r.filename));
35
+ files.forEach(file => {
36
+ if (!known.has(file)) {
37
+ const orphan = path.join(UPLOADS_DIR, file);
38
+ fs.unlink(orphan, err => {
39
+ if (!err) console.log("Orphan removed:", file);
40
+ });
41
+ }
42
+ });
43
+ });
44
+ });
45
+ }
46
+
24
47
  const CLIENT_DIR = path.join(__dirname, "../client");
25
48
 
26
49
  const app = express();
@@ -276,12 +299,35 @@ app.post("/logout", (req, res) => {
276
299
  app.post("/upload", (req, res) => {
277
300
  upload.single("file")(req, res, (err) => {
278
301
  if (err && err.code === "LIMIT_FILE_SIZE") {
279
- return res.status(413).json({ error: "File exceeds 2GB limit" });
302
+ // Clean up in case Multer may have written a partial file before hitting limit
303
+ if (req.file) {
304
+ const partial = path.join(UPLOADS_DIR, req.file.filename);
305
+ if (fs.existsSync(partial)) fs.unlinkSync(partial);
306
+ }
307
+ return res.status(413).json({ error: "File exceeds limit" });
280
308
  }
281
309
  if (err) {
310
+ if (req.file) {
311
+ const partial = path.join(UPLOADS_DIR, req.file.filename);
312
+ if (fs.existsSync(partial)) fs.unlinkSync(partial);
313
+ }
282
314
  return res.status(500).json({ error: "Upload failed" });
283
315
  }
284
316
 
317
+ // req.file missing means the request was aborted before Multer
318
+ // finished. No register or clean up required
319
+ if (!req.file) {
320
+ return res.status(400).json({ error: "No file received" });
321
+ }
322
+
323
+ // Detect client disconnect that happened after Multer finished writing
324
+ // but before we could respond. Clean up the orphaned file.
325
+ if (req.destroyed || res.destroyed) {
326
+ const partial = path.join(UPLOADS_DIR, req.file.filename);
327
+ if (fs.existsSync(partial)) fs.unlinkSync(partial);
328
+ return;
329
+ }
330
+
285
331
  const { channel, uploader } = req.body;
286
332
 
287
333
  const item = {
@@ -297,7 +343,13 @@ app.post("/upload", (req, res) => {
297
343
  `INSERT INTO items (type, filename, size, channel, uploader, created_at)
298
344
  VALUES (?, ?, ?, ?, ?, ?)`,
299
345
  ["file", item.filename, item.size, channel, uploader, item.created_at],
300
- function () {
346
+ function (dbErr) {
347
+ if (dbErr) {
348
+ // DB insert failed — don't leave the file on disk orphaned
349
+ const orphan = path.join(UPLOADS_DIR, item.filename);
350
+ if (fs.existsSync(orphan)) fs.unlinkSync(orphan);
351
+ return res.status(500).json({ error: "Failed to save item" });
352
+ }
301
353
  item.id = this.lastID;
302
354
  io.emit("new-item", item);
303
355
  res.json(item);
@@ -779,6 +831,7 @@ findFreePort(PREFERRED).then(p => {
779
831
  console.log(`(port ${PREFERRED} was busy, switched to ${PORT})`);
780
832
  }
781
833
  console.log("");
834
+ scanOrphans(); // clean up any pre-v1.9.1 ghost files
782
835
  });
783
836
  });
784
837