From ca56f1e3d3133af476c20e5bbdc685e8ab971d79 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Tue, 28 Jul 2026 18:17:09 +0200 Subject: [PATCH] =?UTF-8?q?fix(torrent):=20engine.get()=20sync=20via=20cli?= =?UTF-8?q?ent.torrents=20(webtorrent=202.x=20client.get=20is=20async)=20?= =?UTF-8?q?=E2=80=94=20fixes=20remove/files/stream=20500=20(ref=20#917)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WebTorrent 2.x made client.get() async (returns a Promise). engine.get() used it as if synchronous, so every caller operated on a Promise instead of the Torrent: remove → promise.destroy() (500), /files → promise.files (500), stats → promise.progress (garbage), and /stream never resolved its file. Match on the synchronous client.torrents array instead. FakeWebTorrent.get() was sync and masked this — made it async to model reality, added regression tests asserting engine.get() returns a Torrent (not a Promise) and remove() drops it cleanly. Verified on gk2: add/files/remove all 200 (files+remove were 500), service restarts:0. Co-Authored-By: Gerald KERMA --- packages/secubox-torrent/debian/changelog | 14 +++++++++++++ packages/secubox-torrent/lxc/app/engine.js | 6 +++++- .../secubox-torrent/lxc/app/engine.test.js | 20 +++++++++++++++++++ packages/secubox-torrent/lxc/app/fakes.js | 4 +++- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/secubox-torrent/debian/changelog b/packages/secubox-torrent/debian/changelog index 4b04eb71..5363a45c 100644 --- a/packages/secubox-torrent/debian/changelog +++ b/packages/secubox-torrent/debian/changelog @@ -1,3 +1,17 @@ +secubox-torrent (2.1.2-1~bookworm1) bookworm; urgency=high + + * Fix remove/files/stream 500 + broken stats: WebTorrent 2.x's client.get() + is async (returns a Promise), but engine.get() used it as if sync — every + caller (remove → t.destroy, /files → t.files, /stream, stats → t.progress) + then operated on a Promise instead of the Torrent, giving a 500 (remove, + files) or garbage (stats), and streaming never resolved its file. Match on + the synchronous client.torrents array instead. The FakeWebTorrent test + double had a sync get() that masked this; it is now async to model reality, + with regression tests asserting engine.get() is sync and remove() works. + (ref #917) + + -- Gerald KERMA Tue, 28 Jul 2026 18:45:00 +0200 + secubox-torrent (2.1.1-1~bookworm1) bookworm; urgency=high * Fix add-torrent hang / 502: pin uint8-util to 2.2.6 via npm overrides. diff --git a/packages/secubox-torrent/lxc/app/engine.js b/packages/secubox-torrent/lxc/app/engine.js index 56ee43ab..2e48d48b 100644 --- a/packages/secubox-torrent/lxc/app/engine.js +++ b/packages/secubox-torrent/lxc/app/engine.js @@ -62,7 +62,11 @@ export class Engine { if (t.infoHash || (t.files && t.files.length)) done(); }); } - get(infohash) { return this.client.get(infohash); } + // WebTorrent 2.x's client.get() is ASYNC (returns a Promise) — using it as if + // it were sync makes every caller (stats/remove/files/stream) operate on a + // Promise instead of a Torrent (t.destroy/t.files/t.progress → 500 or garbage). + // client.torrents is a plain array, so match on it synchronously instead. + get(infohash) { return this.client.torrents.find(t => t.infoHash === infohash) || null; } stats(infohash) { const t = this.get(infohash); if (!t) return null; return { progress: t.progress, downloadSpeed: t.downloadSpeed, uploadSpeed: t.uploadSpeed, diff --git a/packages/secubox-torrent/lxc/app/engine.test.js b/packages/secubox-torrent/lxc/app/engine.test.js index c9f362b7..9c5de976 100644 --- a/packages/secubox-torrent/lxc/app/engine.test.js +++ b/packages/secubox-torrent/lxc/app/engine.test.js @@ -43,6 +43,26 @@ test('remove frees capacity for new add', async () => { assert.equal(r2.infohash, 'b'.repeat(40)); }); +test('get returns a torrent synchronously (not a Promise) so callers work', async () => { + const eng = new Engine({ WebTorrentCtor: FakeWebTorrent, downloadDir: '/tmp/x', maxActive: 5, webrtc: true }); + const r = await eng.add('magnet:?xt=urn:btih:' + 'a'.repeat(40)); + const t = eng.get(r.infohash); + // Regression: real webtorrent client.get() is async — engine must NOT return a + // Promise here or stats/remove/files/stream all operate on the wrong object. + assert.equal(typeof t.then, 'undefined'); + assert.equal(t.infoHash, 'a'.repeat(40)); + assert.ok(Array.isArray(t.files)); +}); + +test('remove drops the torrent from the client without throwing', async () => { + const eng = new Engine({ WebTorrentCtor: FakeWebTorrent, downloadDir: '/tmp/x', maxActive: 5, webrtc: true }); + const r = await eng.add('magnet:?xt=urn:btih:' + 'a'.repeat(40)); + assert.equal(eng.client.torrents.length, 1); + eng.remove(r.infohash, { deleteData: true }); // must not throw (t.destroy on a Torrent, not a Promise) + assert.equal(eng.client.torrents.length, 0); + assert.equal(eng.get(r.infohash), null); +}); + test('add rejects (never hangs/crashes) when the torrent emits error', async () => { const client = new FakeWebTorrent(); const eng = new Engine({ WebTorrentCtor: function () { return client; }, diff --git a/packages/secubox-torrent/lxc/app/fakes.js b/packages/secubox-torrent/lxc/app/fakes.js index 744344d7..257e4106 100644 --- a/packages/secubox-torrent/lxc/app/fakes.js +++ b/packages/secubox-torrent/lxc/app/fakes.js @@ -38,7 +38,9 @@ export class FakeWebTorrent { const t = this._next || new FakeTorrent('a'.repeat(40), 'Fake', [{ name: 'movie.mp4', length: 100 }]); t._client = this; this.torrents.push(t); if (cb) queueMicrotask(() => cb(t)); return t; } - get(infohash) { return this.torrents.find(t => t.infoHash === infohash) || null; } + // Real WebTorrent 2.x client.get() is async (Promise) — model that so no code + // may treat it as sync again. Engine matches on the sync `torrents` array. + async get(infohash) { return this.torrents.find(t => t.infoHash === infohash) || null; } on() {} // real WebTorrent client is an EventEmitter; engine attaches 'error' destroy(cb) { if (cb) cb(); } }