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 +29 -0
- package/package.json +1 -1
- package/server/db.js +1 -0
- package/server/server.js +55 -2
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
package/server/db.js
CHANGED
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
|
-
|
|
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
|
|