mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 17:32:26 +00:00
Refactor WebTorrent handlers to reduce duplication
- Merged `handleChromeTorrent` and `handleFirefoxTorrent` into `handleTorrentStream` with a `context` parameter. - Preserved browser-specific logic (CORS workaround for Chrome, highWaterMark for Firefox). - Added `tests/webtorrent-handlers.test.mjs` to cover the refactored logic. Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
566c2dbf89
commit
b2eedb46df
2 files changed
+176
-75
No files matched your search
+48
-75
@@ -706,34 +706,44 @@ export class TorrentClient {
|
||||
});
|
||||
}
|
||||
|
||||
// Handle Chrome-based browsers
|
||||
handleChromeTorrent(torrent, videoElement, resolve, reject) {
|
||||
// Prune demo web seeds/trackers that chronically trip Chromium CORS and
|
||||
// deliberately mutate `torrent._opts` as a sanctioned WebTorrent workaround.
|
||||
torrent.on("warning", (err) => {
|
||||
if (err && typeof err.message === "string") {
|
||||
if (
|
||||
err.message.includes("CORS") ||
|
||||
err.message.includes("Access-Control-Allow-Origin")
|
||||
) {
|
||||
userLogger.warn(
|
||||
"CORS warning detected. Attempting to remove the failing webseed/tracker."
|
||||
);
|
||||
if (torrent._opts?.urlList?.length) {
|
||||
torrent._opts.urlList = torrent._opts.urlList.filter((url) => {
|
||||
return !url.includes("distribution.bbb3d.renderfarming.net");
|
||||
});
|
||||
userLogger.warn("Cleaned up webseeds =>", torrent._opts.urlList);
|
||||
}
|
||||
if (torrent._opts?.announce?.length) {
|
||||
torrent._opts.announce = torrent._opts.announce.filter((url) => {
|
||||
return !url.includes("fastcast.nz");
|
||||
});
|
||||
userLogger.warn("Cleaned up trackers =>", torrent._opts.announce);
|
||||
handleTorrentStream(
|
||||
torrent,
|
||||
videoElement,
|
||||
resolve,
|
||||
reject,
|
||||
context = "chrome"
|
||||
) {
|
||||
const isChrome = context === "chrome";
|
||||
const isFirefox = context === "firefox";
|
||||
|
||||
// Chrome-specific: Prune demo web seeds/trackers that chronically trip Chromium CORS
|
||||
// and deliberately mutate `torrent._opts` as a sanctioned WebTorrent workaround.
|
||||
if (isChrome) {
|
||||
torrent.on("warning", (err) => {
|
||||
if (err && typeof err.message === "string") {
|
||||
if (
|
||||
err.message.includes("CORS") ||
|
||||
err.message.includes("Access-Control-Allow-Origin")
|
||||
) {
|
||||
userLogger.warn(
|
||||
"CORS warning detected. Attempting to remove the failing webseed/tracker."
|
||||
);
|
||||
if (torrent._opts?.urlList?.length) {
|
||||
torrent._opts.urlList = torrent._opts.urlList.filter((url) => {
|
||||
return !url.includes("distribution.bbb3d.renderfarming.net");
|
||||
});
|
||||
userLogger.warn("Cleaned up webseeds =>", torrent._opts.urlList);
|
||||
}
|
||||
if (torrent._opts?.announce?.length) {
|
||||
torrent._opts.announce = torrent._opts.announce.filter((url) => {
|
||||
return !url.includes("fastcast.nz");
|
||||
});
|
||||
userLogger.warn("Cleaned up trackers =>", torrent._opts.announce);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const file = torrent.files.find((f) => /\.(mp4|webm|mkv)$/i.test(f.name));
|
||||
if (!file) {
|
||||
@@ -744,18 +754,23 @@ export class TorrentClient {
|
||||
videoElement.crossOrigin = "anonymous";
|
||||
|
||||
videoElement.addEventListener("error", (e) => {
|
||||
this.log("Video error:", e.target.error);
|
||||
this.log(`Video error (${context} path):`, e.target.error);
|
||||
});
|
||||
|
||||
const tryStart = () => {
|
||||
this.attemptAutoplay(videoElement, "chrome");
|
||||
this.attemptAutoplay(videoElement, context);
|
||||
};
|
||||
|
||||
videoElement.addEventListener("canplay", tryStart, { once: true });
|
||||
videoElement.addEventListener("loadeddata", tryStart, { once: true });
|
||||
|
||||
try {
|
||||
file.streamTo(videoElement);
|
||||
const streamOptions = {};
|
||||
if (isFirefox) {
|
||||
streamOptions.highWaterMark = 256 * 1024;
|
||||
}
|
||||
file.streamTo(videoElement, streamOptions);
|
||||
|
||||
// If the video is already ready (e.g. from cache or fast load), try starting immediately
|
||||
if (videoElement.readyState >= 3) {
|
||||
tryStart();
|
||||
@@ -763,54 +778,12 @@ export class TorrentClient {
|
||||
this.currentTorrent = torrent;
|
||||
resolve(torrent);
|
||||
} catch (err) {
|
||||
this.log("Streaming error (Chrome path):", err);
|
||||
this.log(`Streaming error (${context} path):`, err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
torrent.on("error", (err) => {
|
||||
this.log("Torrent error (Chrome path):", err);
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle Firefox-based browsers
|
||||
handleFirefoxTorrent(torrent, videoElement, resolve, reject) {
|
||||
const file = torrent.files.find((f) =>
|
||||
/\.(mp4|webm|mkv)$/.test(f.name.toLowerCase())
|
||||
);
|
||||
if (!file) {
|
||||
return reject(new Error("No compatible video file found in torrent"));
|
||||
}
|
||||
|
||||
// Satisfy autoplay requirements and keep cross-origin chunks usable (e.g., for snapshots).
|
||||
videoElement.crossOrigin = "anonymous";
|
||||
|
||||
videoElement.addEventListener("error", (e) => {
|
||||
this.log("Video error (Firefox path):", e.target.error);
|
||||
});
|
||||
|
||||
const tryStart = () => {
|
||||
this.attemptAutoplay(videoElement, "firefox");
|
||||
};
|
||||
|
||||
videoElement.addEventListener("canplay", tryStart, { once: true });
|
||||
videoElement.addEventListener("loadeddata", tryStart, { once: true });
|
||||
|
||||
try {
|
||||
file.streamTo(videoElement, { highWaterMark: 256 * 1024 });
|
||||
// If the video is already ready (e.g. from cache or fast load), try starting immediately
|
||||
if (videoElement.readyState >= 3) {
|
||||
tryStart();
|
||||
}
|
||||
this.currentTorrent = torrent;
|
||||
resolve(torrent);
|
||||
} catch (err) {
|
||||
this.log("Streaming error (Firefox path):", err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
torrent.on("error", (err) => {
|
||||
this.log("Torrent error (Firefox path):", err);
|
||||
this.log(`Torrent error (${context} path):`, err);
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
@@ -932,14 +905,14 @@ export class TorrentClient {
|
||||
{ ...chromeOptions, maxWebConns: 4 },
|
||||
(torrent) => {
|
||||
this.log("Torrent added (Firefox path):", torrent.name);
|
||||
this.handleFirefoxTorrent(torrent, videoElement, resolve, reject);
|
||||
this.handleTorrentStream(torrent, videoElement, resolve, reject, "firefox");
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.log("Starting torrent download (Chrome path)");
|
||||
this.client.add(magnetURI, chromeOptions, (torrent) => {
|
||||
this.log("Torrent added (Chrome path):", torrent.name);
|
||||
this.handleChromeTorrent(torrent, videoElement, resolve, reject);
|
||||
this.handleTorrentStream(torrent, videoElement, resolve, reject, "chrome");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import { test, describe, it, before, after } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import EventEmitter from "events";
|
||||
import { TorrentClient } from "../js/webtorrent.js";
|
||||
|
||||
// Mock JSDOM-like environment if needed
|
||||
if (!global.window) {
|
||||
global.window = new EventEmitter();
|
||||
global.window.navigator = { userAgent: "Node" };
|
||||
}
|
||||
|
||||
class MockFile {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
streamTo(elem, opts) {
|
||||
elem.opts = opts; // Capture opts for verification
|
||||
elem.src = "blob:mock-stream";
|
||||
// Simulate ready state progression
|
||||
setTimeout(() => {
|
||||
elem.readyState = 3;
|
||||
elem.emit('canplay');
|
||||
elem.emit('loadeddata');
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
class MockTorrent extends EventEmitter {
|
||||
constructor(magnet, opts) {
|
||||
super();
|
||||
this.magnet = magnet;
|
||||
this.files = [new MockFile("video.mp4")];
|
||||
this._opts = opts || {};
|
||||
}
|
||||
destroy() {}
|
||||
}
|
||||
|
||||
class MockVideoElement extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.playCalled = false;
|
||||
this.crossOrigin = null;
|
||||
this.readyState = 0;
|
||||
this.muted = false;
|
||||
this.error = null;
|
||||
}
|
||||
play() {
|
||||
this.playCalled = true;
|
||||
return Promise.resolve();
|
||||
}
|
||||
addEventListener(event, handler, opts) {
|
||||
this.on(event, handler);
|
||||
}
|
||||
removeEventListener(event, handler) {
|
||||
this.off(event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
describe("TorrentClient Handlers", () => {
|
||||
let client;
|
||||
let videoElement;
|
||||
let torrent;
|
||||
|
||||
before(() => {
|
||||
client = new TorrentClient();
|
||||
// Silence logs for tests
|
||||
client.log = () => {};
|
||||
});
|
||||
|
||||
it("handleTorrentStream (chrome) should set up video correctly", async () => {
|
||||
videoElement = new MockVideoElement();
|
||||
torrent = new MockTorrent("magnet:?xt=urn:btih:test");
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
client.handleTorrentStream(torrent, videoElement, resolve, reject, "chrome");
|
||||
});
|
||||
|
||||
// Wait for events to fire
|
||||
await new Promise(r => setTimeout(r, 20));
|
||||
|
||||
assert.strictEqual(videoElement.crossOrigin, "anonymous", "crossOrigin should be anonymous");
|
||||
assert.strictEqual(videoElement.playCalled, true, "play() should be called");
|
||||
assert.ok(videoElement.src, "src should be set");
|
||||
assert.strictEqual(client.currentTorrent, torrent, "currentTorrent should be set");
|
||||
});
|
||||
|
||||
it("handleTorrentStream (firefox) should set up video correctly with highWaterMark", async () => {
|
||||
videoElement = new MockVideoElement();
|
||||
torrent = new MockTorrent("magnet:?xt=urn:btih:test");
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
client.handleTorrentStream(torrent, videoElement, resolve, reject, "firefox");
|
||||
});
|
||||
|
||||
// Wait for events to fire
|
||||
await new Promise(r => setTimeout(r, 20));
|
||||
|
||||
assert.strictEqual(videoElement.crossOrigin, "anonymous", "crossOrigin should be anonymous");
|
||||
assert.strictEqual(videoElement.playCalled, true, "play() should be called");
|
||||
assert.ok(videoElement.src, "src should be set");
|
||||
// Check options directly on videoElement which our mock sets
|
||||
assert.ok(videoElement.opts, "opts should be set");
|
||||
assert.strictEqual(videoElement.opts.highWaterMark, 256 * 1024, "highWaterMark should be set for Firefox");
|
||||
assert.strictEqual(client.currentTorrent, torrent, "currentTorrent should be set");
|
||||
});
|
||||
|
||||
it("handleTorrentStream (chrome) should handle CORS warning logic", () => {
|
||||
videoElement = new MockVideoElement();
|
||||
torrent = new MockTorrent("magnet:?xt=urn:btih:test", {
|
||||
urlList: ["http://distribution.bbb3d.renderfarming.net/video.mp4", "http://good.com/video.mp4"],
|
||||
announce: ["ws://fastcast.nz", "ws://good.com"]
|
||||
});
|
||||
|
||||
// We don't await because we are testing the event listener setup which happens synchronously
|
||||
// But we need a dummy resolve/reject
|
||||
client.handleTorrentStream(torrent, videoElement, () => {}, () => {}, "chrome");
|
||||
|
||||
// Emit warning
|
||||
torrent.emit('warning', { message: 'CORS policy' });
|
||||
|
||||
assert.strictEqual(torrent._opts.urlList.length, 1, "Should filter urlList");
|
||||
assert.strictEqual(torrent._opts.urlList[0], "http://good.com/video.mp4", "Should keep good url");
|
||||
assert.strictEqual(torrent._opts.announce.length, 1, "Should filter announce");
|
||||
assert.strictEqual(torrent._opts.announce[0], "ws://good.com", "Should keep good tracker");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user