// js/webtorrent.js import WebTorrent from "./webtorrent.min.js"; export class TorrentClient { constructor() { this.client = new WebTorrent(); this.currentTorrent = null; this.TIMEOUT_DURATION = 60000; // 60 seconds } log(msg) { console.log(msg); } async isBrave() { return ( (navigator.brave?.isBrave && (await navigator.brave.isBrave())) || false ); } isFirefox() { return /firefox/i.test(window.navigator.userAgent); } async waitForServiceWorkerActivation(registration) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Service worker activation timeout")); }, this.TIMEOUT_DURATION); this.log("Waiting for service worker activation..."); const checkActivation = () => { if (registration.active) { clearTimeout(timeout); this.log("Service worker is active"); resolve(registration); return true; } return false; }; // Check if already active if (checkActivation()) return; registration.addEventListener("activate", () => { checkActivation(); }); if (registration.waiting) { this.log("Service worker is waiting, sending skip waiting message"); registration.waiting.postMessage({ type: "SKIP_WAITING" }); } registration.addEventListener("statechange", () => { checkActivation(); }); }); } // -------------------------- // UPDATED: setupServiceWorker // -------------------------- async setupServiceWorker() { try { const isBraveBrowser = await this.isBrave(); if (!window.isSecureContext) { throw new Error("HTTPS or localhost required"); } if (!("serviceWorker" in navigator) || !navigator.serviceWorker) { throw new Error("Service Worker not supported or disabled"); } // Optional Brave config if (isBraveBrowser) { this.log("Checking Brave configuration..."); if (!navigator.serviceWorker) { throw new Error( "Please enable Service Workers in Brave Shield settings" ); } if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { throw new Error("Please enable WebRTC in Brave Shield settings"); } // Unregister any existing SW const registrations = await navigator.serviceWorker.getRegistrations(); for (const registration of registrations) { await registration.unregister(); } await new Promise((resolve) => setTimeout(resolve, 1000)); } // Directly register sw.min.js at /src/sw.min.js // with a scope that covers /src/ (which includes /src/webtorrent). this.log("Registering service worker at /src/sw.min.js..."); const registration = await navigator.serviceWorker.register( "/src/sw.min.js", { scope: "/src/", updateViaCache: "none", } ); this.log("Service worker registered"); if (registration.installing) { this.log("Waiting for installation..."); await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error("Installation timeout")); }, this.TIMEOUT_DURATION); registration.installing.addEventListener("statechange", (e) => { this.log("Service worker state:", e.target.state); if ( e.target.state === "activated" || e.target.state === "redundant" ) { clearTimeout(timeout); resolve(); } }); }); } await this.waitForServiceWorkerActivation(registration); this.log("Service worker activated"); // Make sure the SW is fully ready const readyRegistration = await Promise.race([ navigator.serviceWorker.ready, new Promise((_, reject) => setTimeout( () => reject(new Error("Service worker ready timeout")), this.TIMEOUT_DURATION ) ), ]); if (!readyRegistration.active) { throw new Error("Service worker not active after ready state"); } this.log("Service worker ready"); return registration; } catch (error) { this.log("Service worker setup error:", error); throw error; } } formatBytes(bytes) { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`; } /** * Streams the magnet to the