mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
fix(beacon): stop the processing spinner hanging forever (#28)
The torrent beacon's processing overlay was gated on WebTorrent's metadata-ready
callback (client.add/seed), which only fires once metadata arrives from peers. A
magnet with no live seeders never resolved it, so the spinner span forever, and a
torrent that emitted its own 'error' didn't always reach the client-level handler.
- beginProcessing() arms a 30s watchdog that drops the overlay and warns ("still
searching for peers — it'll keep trying in the background") instead of hanging;
the torrent stays in the table and keeps announcing.
- handleTorrentReady, the client error handler, and destroy() clear the watchdog.
- addMagnet/seedFiles wrap add/seed in try/catch and attach a per-torrent 'error'
listener so bad/duplicate magnets fail visibly.
- Tests: tests/torrent/beacon-watchdog.test.mjs (JSDOM).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
1c256808ff
commit
fdcf034464
4 files changed
+222
-15
No files matched your search
+78
-8
@@ -12,6 +12,12 @@ const TRACKERS = [
|
||||
const TORRENT_OPTIONS = { announce: TRACKERS };
|
||||
const TRACKER_OPTIONS = { announce: TRACKERS };
|
||||
|
||||
// A magnet with no live seeders never fires WebTorrent's metadata-ready
|
||||
// callback, so the processing overlay would otherwise spin forever. After this
|
||||
// long we drop the overlay and tell the user it's still trying in the
|
||||
// background (the torrent stays in the table and keeps announcing).
|
||||
const PROCESSING_TIMEOUT_MS = 30000;
|
||||
|
||||
function formatBytes(num, speed = false) {
|
||||
if (typeof num !== "number" || Number.isNaN(num)) {
|
||||
return "";
|
||||
@@ -208,11 +214,37 @@ export function createBeaconApp({
|
||||
|
||||
const cleanupTasks = [];
|
||||
const intervalHandles = [];
|
||||
let processingTimeoutId = null;
|
||||
|
||||
function addCleanup(task) {
|
||||
cleanupTasks.push(task);
|
||||
}
|
||||
|
||||
function clearProcessingWatchdog() {
|
||||
if (processingTimeoutId !== null) {
|
||||
view.clearTimeout(processingTimeoutId);
|
||||
processingTimeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Show the processing overlay and arm a watchdog so it can never hang
|
||||
// forever: if the operation hasn't completed in time, drop the overlay and
|
||||
// warn instead of leaving a dead spinner.
|
||||
function beginProcessing() {
|
||||
setProcessing(true);
|
||||
clearProcessingWatchdog();
|
||||
processingTimeoutId = view.setTimeout(() => {
|
||||
processingTimeoutId = null;
|
||||
if (!state.processing) {
|
||||
return;
|
||||
}
|
||||
setProcessing(false);
|
||||
toast?.warn(
|
||||
"Still searching for peers — this is taking a while. It'll keep trying in the background.",
|
||||
);
|
||||
}, PROCESSING_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function setProcessing(flag) {
|
||||
state.processing = Boolean(flag);
|
||||
|
||||
@@ -359,6 +391,7 @@ export function createBeaconApp({
|
||||
}
|
||||
|
||||
function handleTorrentReady(torrent, { isSeed = false } = {}) {
|
||||
clearProcessingWatchdog();
|
||||
setProcessing(false);
|
||||
prepareTorrentFiles(torrent);
|
||||
|
||||
@@ -380,10 +413,20 @@ export function createBeaconApp({
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
client.add(trimmed, TORRENT_OPTIONS, (torrent) => {
|
||||
handleTorrentReady(torrent, { isSeed: false });
|
||||
});
|
||||
beginProcessing();
|
||||
let torrent;
|
||||
try {
|
||||
torrent = client.add(trimmed, TORRENT_OPTIONS, (added) => {
|
||||
handleTorrentReady(added, { isSeed: false });
|
||||
});
|
||||
} catch (error) {
|
||||
clearProcessingWatchdog();
|
||||
setProcessing(false);
|
||||
userLogger.error("[beacon] Failed to add magnet", error);
|
||||
toast?.error(error?.message || "Couldn't add that magnet.");
|
||||
return;
|
||||
}
|
||||
watchTorrentForFailure(torrent);
|
||||
}
|
||||
|
||||
function seedFiles(files) {
|
||||
@@ -391,10 +434,35 @@ export function createBeaconApp({
|
||||
return;
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
client.seed(files, TORRENT_OPTIONS, (torrent) => {
|
||||
handleTorrentReady(torrent, { isSeed: true });
|
||||
toast.success(`Seeding ${torrent.files.length} file(s)`);
|
||||
beginProcessing();
|
||||
let torrent;
|
||||
try {
|
||||
torrent = client.seed(files, TORRENT_OPTIONS, (seeded) => {
|
||||
handleTorrentReady(seeded, { isSeed: true });
|
||||
toast.success(`Seeding ${seeded.files.length} file(s)`);
|
||||
});
|
||||
} catch (error) {
|
||||
clearProcessingWatchdog();
|
||||
setProcessing(false);
|
||||
userLogger.error("[beacon] Failed to seed files", error);
|
||||
toast?.error(error?.message || "Couldn't seed those files.");
|
||||
return;
|
||||
}
|
||||
watchTorrentForFailure(torrent);
|
||||
}
|
||||
|
||||
// A torrent can emit its own 'error' (bad/duplicate magnet) without the
|
||||
// client-level handler firing; surface it and drop the overlay so the add
|
||||
// fails visibly instead of spinning.
|
||||
function watchTorrentForFailure(torrent) {
|
||||
if (!torrent || typeof torrent.once !== "function") {
|
||||
return;
|
||||
}
|
||||
torrent.once("error", (error) => {
|
||||
clearProcessingWatchdog();
|
||||
setProcessing(false);
|
||||
userLogger.error("[beacon] Torrent error", error);
|
||||
toast?.error(error?.message || "That torrent could not be loaded.");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -572,6 +640,7 @@ export function createBeaconApp({
|
||||
const errorHandler = (error) => {
|
||||
userLogger.error("[beacon] Torrent client error", error);
|
||||
toast?.error(error?.message || String(error));
|
||||
clearProcessingWatchdog();
|
||||
setProcessing(false);
|
||||
};
|
||||
client.on("error", errorHandler);
|
||||
@@ -610,6 +679,7 @@ export function createBeaconApp({
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
clearProcessingWatchdog();
|
||||
intervalHandles.forEach((id) => view.clearInterval(id));
|
||||
intervalHandles.length = 0;
|
||||
|
||||
|
||||
Reference in new issue
Block a user