docs: add detailed webtorrent architecture and regression prevention comments

- Create `docs/webtorrent-architecture.md` detailing the "Webseeds are Peers" strategy.
- Update `docs/video-availability-checks.md` with cross-reference.
- Add "CRITICAL" warning comments to `js/webtorrent.js` and `js/gridHealth.js` to prevent regressions.
- Enhance `tests/webtorrent-regression.test.mjs` to explicitly verify `urlList` passing in mocks.

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-01 19:35:02 +00:00
1 parent 7d749f9494
commit ee46b36a25
5 files changed
+88

No files matched your search

+2
View File
@@ -38,6 +38,8 @@ This document explains how bitvid probes URL and torrent availability, caches re
## Torrent (stream) health checks
For a detailed architectural overview of the WebTorrent implementation, including webseed handling and Service Worker integration, see [WebTorrent Architecture & Strategy](webtorrent-architecture.md).
### Card-level probes (`js/gridHealth.js`)
The grid health module manages per-card WebTorrent probes and badge updates:
+50
View File
@@ -0,0 +1,50 @@
# WebTorrent Architecture & Strategy
This document details the architecture of the WebTorrent implementation in bitvid, focusing on the client management, Service Worker integration, and the critical logic for handling webseeds.
## Core Principle: Webseeds Are Peers
**Crucial Logic (Regression Risk):**
A recurring regression in this codebase involves treating "Webseeds" (HTTP sources) as distinct from "Peers" (P2P sources) in a way that marks webseed-only videos as "unhealthy".
**The Correct Behavior:**
* We **must** pass webseeds (`urlList`) directly to the WebTorrent client via `client.add(url, { urlList: [...] })`.
* We **must** rely on the WebTorrent client to count that webseed as a connected peer.
* **Health = Peers > 0**. If a webseed connects, `numPeers` becomes 1. This is "Healthy".
**Anti-Pattern (Do Not Implement):**
* *Do not* attempt to manually check if `peers === 0 && hasWebseed` and label it as a special "healthy" state in the UI logic.
* *Do not* fail to pass `urlList` to `client.add`. If you don't pass it, WebTorrent won't see the peer, `numPeers` stays 0, and the video is marked unhealthy.
## Architecture Components
### 1. The Singleton Client (`TorrentClient`)
`js/webtorrent.js` exports a singleton `torrentClient`. We do not instantiate multiple clients.
* **Reason:** WebTorrent clients are expensive. They open many socket connections and maintain DHT state.
* **Reuse:** The same client instance is used for both "Probing" (checking health) and "Streaming" (playback).
### 2. The Service Worker (`sw.min.js`)
Browser-based WebTorrent cannot write directly to the filesystem or stream directly to a `<video>` tag's `src` attribute efficiently.
* **Solution:** We register a Service Worker that acts as a proxy.
* **Flow:** `<video src="/webtorrent/...">` -> Browser Network Request -> Service Worker Intercept -> Fetch bytes from `TorrentClient` -> Return stream.
### 3. The "Claim" Dance (Critical Startup Logic)
A major source of "grey screen" bugs (video loading forever) occurs when the Service Worker is installed but **not controlling the page**.
* **Scenario:** User loads page -> SW installs -> User plays video. If the SW hasn't "claimed" the client, the network request bypasses the SW and fails (404).
* **Fix:** `TorrentClient.init()` explicitly waits for `navigator.serviceWorker.controller` to exist. It calls `clients.claim()` in the SW and waits for the `controllerchange` event.
## Probing Strategy (`probePeers`)
We probe videos before playing them to determine if they are available.
1. **Input:** Magnet URI + optional Webseed URL.
2. **Action:** Call `client.add` with `urlList` set to the webseed.
3. **Wait:** Wait for `torrent.on('wire')` (connection established) or `peers > 0`.
4. **Result:**
* **Healthy:** `peers > 0`. (Includes webseeds!)
* **Unhealthy:** Timeout with `peers === 0`.
## Browser Specifics
* **Brave:** Requires aggressive Service Worker cleanup (`unregister` all) on startup due to lingering stale workers preventing new registrations.
* **Firefox:** Uses a specific `highWaterMark` tuning and separate handler (`handleFirefoxTorrent`) to manage memory buffering differences.
* **Chrome/Chromium:** Standard path (`handleChromeTorrent`), but includes logic to strip specific trackers/webseeds that trigger CORS warnings (e.g., specific demo trackers).
+10
View File
@@ -135,7 +135,17 @@ function normalizeResult(result) {
const peers = Number.isFinite(result.peers)
? Math.max(0, Number(result.peers))
: 0;
/**
* CRITICAL: Health determination relies SOLELY on `peers > 0`.
* Because we pass webseeds to the client, a connected webseed counts as a peer.
*
* Regression warning:
* Do not attempt to add `|| webseedOnly` here. If `peers` is 0, the webseed
* did not connect, and the stream is NOT healthy.
*/
const healthy = Boolean(result.healthy) && peers > 0;
const reason = typeof result.reason === "string" ? result.reason : "error";
const webseedOnly = Boolean(result.webseedOnly) && peers === 0;
return {
+18
View File
@@ -284,6 +284,19 @@ export class TorrentClient {
announce: trackers,
maxWebConns: safeMaxWebConns,
};
/**
* CRITICAL: We pass the webseed URL (if present) to the client via `urlList`.
*
* Regression warning:
* In the past, we tried to separate "webseed health" from "peer health".
* That was a mistake. WebTorrent counts a connected webseed as a peer.
*
* If we pass `urlList` here, the client connects to the webseed, and `numPeers`
* increments to at least 1. This correctly signals "Healthy" to our `gridHealth` logic.
*
* DO NOT remove this parameter or try to handle webseeds manually outside the client.
* Trust the client to count the webseed as a peer.
*/
if (hasExplicitWebSeed) {
addOptions.urlList = urlList;
}
@@ -895,6 +908,11 @@ export class TorrentClient {
: [];
const chromeOptions = { strategy: "sequential" };
/**
* CRITICAL: Passing `urlList` ensures the client can stream from the webseed.
* Without this, videos with 0 P2P peers will fail to play even if a valid
* webseed is available.
*/
if (candidateUrls.length) {
chromeOptions.urlList = candidateUrls;
}
+8
View File
@@ -92,6 +92,14 @@ describe("WebTorrent Regression Tests", () => {
assert.strictEqual(result.healthy, true, "Should be healthy due to webseed");
assert.strictEqual(result.reason, "peer", "Reason should be 'peer'");
assert.ok(result.peers >= 1, "Should report at least 1 peer");
// Verify that the client ACTUALLY received the webseed in the urlList
// This ensures we don't regress on passing the parameter
// Note: probePeers uses 'probeClient', not the main 'client'
const torrent = client.probeClient.torrents[0];
assert.ok(torrent, "Torrent should have been created");
assert.ok(Array.isArray(torrent.opts.urlList), "urlList should be an array");
assert.ok(torrent.opts.urlList.includes(webSeedUrl), "urlList should contain the webseed URL");
});
it("probePeers should report unhealthy if no webseed and 0 peers", async () => {