diff --git a/tests/torrent/beacon-watchdog.test.mjs b/tests/torrent/beacon-watchdog.test.mjs new file mode 100644 index 00000000..998b73c6 --- /dev/null +++ b/tests/torrent/beacon-watchdog.test.mjs @@ -0,0 +1,123 @@ +import { strict as assert } from "node:assert"; +import { afterEach, beforeEach, describe, it } from "node:test"; +import { JSDOM } from "jsdom"; + +import { createBeaconApp } from "../../torrent/app.js"; + +// The processing overlay is the only element these scenarios assert on; every +// other element createBeaconApp queries is optional and guarded. +const BODY = ''; +const MAGNET = `magnet:?xt=urn:btih:${"a".repeat(40)}`; + +function makeFakeTorrent() { + return { infoHash: "a".repeat(40), name: "fake", progress: 0, files: [], once() {} }; +} + +// A WebTorrent stub whose add() never invokes the ready callback — exactly what +// a magnet with no live seeders does, which used to spin the overlay forever. +class StuckWebTorrent { + constructor() { + this.torrents = []; + this.downloadSpeed = 0; + this.uploadSpeed = 0; + this.ratio = 0; + } + on() {} + removeListener() {} + add() { + const torrent = makeFakeTorrent(); + this.torrents.push(torrent); + return torrent; // intentionally never calls the ready callback + } + seed() { + const torrent = makeFakeTorrent(); + this.torrents.push(torrent); + return torrent; + } + destroy() {} +} +StuckWebTorrent.WEBRTC_SUPPORT = true; + +// A WebTorrent stub whose add() resolves immediately (metadata arrives). +class ReadyWebTorrent extends StuckWebTorrent { + add(magnet, options, cb) { + const torrent = makeFakeTorrent(); + this.torrents.push(torrent); + if (typeof cb === "function") cb(torrent); + return torrent; + } +} +ReadyWebTorrent.WEBRTC_SUPPORT = true; + +let dom; +let firedTimeouts; + +function setupDom() { + dom = new JSDOM(`${BODY}`, { + url: `https://beacon.test/#${MAGNET}`, + pretendToBeVisual: true, + }); + const view = dom.window; + view.requestAnimationFrame = (cb) => cb(); + // Capture the watchdog timeout so the scenario controls when it "fires", + // and neutralise the 1s render interval so no real timers run. + firedTimeouts = []; + view.setTimeout = (fn) => { + firedTimeouts.push(fn); + return firedTimeouts.length; + }; + view.clearTimeout = () => {}; + view.setInterval = () => 0; + view.clearInterval = () => {}; + return view.document; +} + +function overlay(documentRef) { + return documentRef.querySelector('[data-beacon="processing-overlay"]'); +} + +describe("torrent/app beacon processing watchdog", () => { + beforeEach(() => {}); + + afterEach(() => { + if (dom) { + dom.window.close(); + dom = null; + } + }); + + it("drops the spinner (and warns) when a magnet never finds peers", () => { + const documentRef = setupDom(); + const app = createBeaconApp({ documentRef, WebTorrentCtor: StuckWebTorrent }); + + app.mount(); // reads location.hash → addMagnet(MAGNET) → spinner on + + assert.equal(overlay(documentRef).hidden, false, "spinner shows while connecting"); + assert.equal(firedTimeouts.length, 1, "a watchdog timeout was armed"); + + // Simulate the timeout elapsing with the torrent still not ready. + firedTimeouts[0](); + + assert.equal( + overlay(documentRef).hidden, + true, + "spinner is dismissed instead of hanging forever", + ); + const toast = documentRef.querySelector(".torrent-toast--warn"); + assert.ok(toast, "a warning toast explains it's still trying in the background"); + }); + + it("clears the spinner and the watchdog as soon as metadata arrives", () => { + const documentRef = setupDom(); + const app = createBeaconApp({ documentRef, WebTorrentCtor: ReadyWebTorrent }); + + app.mount(); // ready callback fires synchronously → handleTorrentReady + + assert.equal(overlay(documentRef).hidden, true, "spinner resolves on success"); + // Firing a stale watchdog after success must not re-hide / re-toast. + const before = documentRef.querySelectorAll(".torrent-toast--warn").length; + firedTimeouts.forEach((fn) => fn()); + const after = documentRef.querySelectorAll(".torrent-toast--warn").length; + assert.equal(after, before, "a resolved op does not fire a stale watchdog warning"); + }); +}); diff --git a/todo/TODO_2026-06-20_pre-launch.md b/todo/TODO_2026-06-20_pre-launch.md index 266833b8..59ff1b02 100644 --- a/todo/TODO_2026-06-20_pre-launch.md +++ b/todo/TODO_2026-06-20_pre-launch.md @@ -516,12 +516,26 @@ SILENT one: `handleEventDetailsAction` only opened the modal `if (… && payload - [ ] **VERIFY live**: click ⋯ → Event Details AND ⋯ → Popularity in the video modal AND on a grid/channel card; both should open the correct video. -### 28. Beacon torrent app stuck — spinner never resolves (BUG) -- [ ] The torrent beacon app (`scripts/build:beacon` / `torrent/` integration) shows - a spinner that never goes away. Trace the beacon's init/connect path: what the - spinner is waiting on (tracker/handshake/WebTorrent ready), whether it errors - silently, and whether it's related to the vendored `webtorrent.min.js` - `null.fill` crash (#10). Make it either resolve or fail visibly. +### 28. Beacon torrent app stuck — spinner never resolves (BUG) — FIXED 2026-06-24 +- [x] **Root cause:** the processing overlay (`torrent/app.js`) was gated on + WebTorrent's metadata-ready callback (`client.add(magnet, opts, cb)` / + `client.seed`). That callback only fires once metadata arrives from peers, so + a magnet with **no live seeders** never resolved it and the spinner span + forever — and a torrent that emits its own `'error'` (bad/duplicate magnet) + didn't always reach the client-level handler. (NOT the `null.fill` crash + (#10); the client constructs fine — this was a missing timeout/failure path.) +- [x] **Fix:** added a watchdog around the overlay. `beginProcessing()` arms a + 30s `PROCESSING_TIMEOUT_MS` timer; if the op hasn't completed it drops the + overlay and warns ("still searching for peers — it'll keep trying in the + background") while the torrent stays in the table and keeps announcing. + `handleTorrentReady`, the client error handler, and `destroy()` all clear the + watchdog. `addMagnet`/`seedFiles` now wrap `add`/`seed` in try/catch and + attach a per-torrent `'error'` listener so bad magnets fail visibly. +- [x] **Tests:** `tests/torrent/beacon-watchdog.test.mjs` (JSDOM) — a no-peers + magnet drops the spinner + warns; a ready torrent resolves it and a stale + watchdog can't re-fire. +- [ ] **VERIFY live**: open the beacon, paste a dead magnet → spinner should clear + with a warning after ~30s instead of hanging; paste a live magnet → resolves. ### 29. Admin-whitelisted users bypass the Web-of-Trust (anti-abuse) - [ ] When an admin **whitelists** a user, that user's content should **bypass the diff --git a/torrent/app.js b/torrent/app.js index 447c1519..76e052da 100644 --- a/torrent/app.js +++ b/torrent/app.js @@ -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; diff --git a/torrent/dist/beacon.js b/torrent/dist/beacon.js index 7d9f6e97..906e29f2 100644 --- a/torrent/dist/beacon.js +++ b/torrent/dist/beacon.js @@ -1 +1 @@ -import{IS_DEV_MODE as ve,IS_VERBOSE_DEV_MODE as Ie,IS_LOCKDOWN_MODE as Oe,LOCKDOWN_TITLE as _t,LOCKDOWN_MESSAGE as St,ADMIN_SUPER_NPUB as wt,ADMIN_DM_IMAGE_URL as Lt,BITVID_WEBSITE_URL as At,BLOG_URL as Ct,COMMUNITY_URL as xt,NOSTR_URL as vt,GITHUB_URL as It,BETA_URL as Ot,DNS_URL as Bt,TIP_JAR_URL as Nt,MAX_WALLET_DEFAULT_ZAP as Mt,PLATFORM_FEE_PERCENT as Dt,PLATFORM_LUD16_OVERRIDE as Rt,DEFAULT_RELAY_URLS_OVERRIDE as Ut,ADMIN_WHITELIST_MODE_STORAGE_KEY as Ht,DEFAULT_WHITELIST_MODE_ENABLED as kt,ALLOW_NSFW_CONTENT as Ft,DEFAULT_TRUST_SEED_NPUBS as Vt,DEFAULT_BLUR_THRESHOLD as Pt,DEFAULT_AUTOPLAY_BLOCK_THRESHOLD as Wt,DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD as qt,DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD as jt,WATCH_HISTORY_KIND as $t,WATCH_HISTORY_LIST_IDENTIFIER as Yt,WATCH_HISTORY_MAX_ITEMS as Kt,WATCH_HISTORY_BATCH_RESOLVE as zt,WATCH_HISTORY_BATCH_PAGE_SIZE as Gt,WATCH_HISTORY_PAYLOAD_MAX_BYTES as Xt,WATCH_HISTORY_FETCH_EVENT_LIMIT as Zt,WATCH_HISTORY_CACHE_TTL_MS as Jt,VIEW_COUNT_DEDUPE_WINDOW_SECONDS as Qt,VIEW_COUNT_BACKFILL_MAX_DAYS as er,VIEW_COUNT_CACHE_TTL_MS as tr,ENSURE_PRESENCE_REBROADCAST_COOLDOWN_SECONDS as rr,DEFAULT_PLAYBACK_SOURCE as nr,DEFAULT_PLAYBACK_START_TIMEOUT as or}from"../config/instance-config.js";var re=typeof globalThis>"u"?null:globalThis,ne=e=>{if(typeof e=="boolean")return e;if(typeof e=="string"){let t=e.trim().toLowerCase();if(t==="true")return!0;if(t==="false")return!1}return null},ee=ne(re?.__BITVID_DEV_MODE_OVERRIDE__),te=ne(re?.__BITVID_VERBOSE_DEV_MODE_OVERRIDE__),U=ee===null?!!ve:ee;var Be=te===null?!!Ie:te;var Ne=!!Oe;typeof window<"u"&&(window.__BITVID_DEV_MODE__=U,window.__BITVID_VERBOSE_DEV_MODE__=Be,window.__BITVID_LOCKDOWN_MODE__=Ne);var k=()=>{};function Me(){return typeof console>"u"||console===null?null:console}function De(){let e=Me();if(!e)return{log:k,info:k,debug:k,warn:k,error:k};let t=typeof e.log=="function"?e.log.bind(e):k;return{log:typeof e.log=="function"?e.log.bind(e):t,info:typeof e.info=="function"?e.info.bind(e):t,debug:typeof e.debug=="function"?e.debug.bind(e):t,warn:typeof e.warn=="function"?e.warn.bind(e):t,error:typeof e.error=="function"?e.error.bind(e):t}}var _=De(),M="[bitvid]";function oe(e){if(e.length===0)return{args:e,force:!1};let t=e[e.length-1];if(t&&typeof t=="object"&&!Array.isArray(t)&&Object.prototype.hasOwnProperty.call(t,"force")){let r=[...e],o=r.pop();return{args:r,force:!!o.force}}return{args:e,force:!1}}var S=Object.freeze({log:(...e)=>{U&&_.log(...e)},info:(...e)=>{U&&_.info(...e)},debug:(...e)=>{U&&_.debug(...e)},warn:(...e)=>{U&&_.warn(...e)},error:(...e)=>{U&&_.error(...e)}}),b=Object.freeze({log:(...e)=>{let{args:t,force:r}=oe(e);if(!r&&t.length===0)return;if(r){_.log(M,...t);return}let[o,...n]=t;if(typeof o=="string"&&n.length===0){_.log(`${M} ${o}`);return}o!==void 0&&_.log(M,o)},info:(...e)=>{let{args:t,force:r}=oe(e);if(!r&&t.length===0)return;if(r){_.info(M,...t);return}let[o,...n]=t;if(typeof o=="string"&&n.length===0){_.info(`${M} ${o}`);return}o!==void 0&&_.info(M,o)},warn:(...e)=>{_.warn(M,...e)},error:(...e)=>{_.error(M,...e)}}),ae=Object.freeze({dev:S,user:b});function Re(e){return!e||typeof e!="object"?!1:Object.entries({dev:["log","info","debug","warn","error"],user:["log","info","warn","error"]}).every(([r,o])=>{let n=e[r];return!n||typeof n!="object"?!1:o.every(c=>typeof n[c]=="function")})}typeof window<"u"&&(Re(window.bitvidLogger)||(window.bitvidLogger=ae));Object.freeze(ae);import{THEME_ACCENT_OVERRIDES as se}from"../config/instance-config.js";var G="bitvid:theme",v="dark",Ue=new Set(["light","dark"]),ie={dark:"rgb(15, 23, 42)",light:"rgb(248, 250, 252)"},ce=["[data-theme-toggle]",'[data-action="toggle-theme"]',".js-theme-toggle","#themeToggle"],ue=Object.freeze({accent:"--color-accent",accentStrong:"--color-accent-strong",accentPressed:"--color-accent-pressed"}),le=new WeakSet,W=new Set,D=null,de=!1,F=null,I=()=>typeof window<"u"&&typeof document<"u",q=e=>{if(typeof e!="string")return null;let t=e.trim().toLowerCase();return t==="default"||t===""?v:Ue.has(t)?t:null},He=()=>{if(!I())return null;try{let e=window.localStorage.getItem(G);return q(e)}catch(e){return b.warn("Unable to read stored theme preference:",e),null}},ke=e=>{if(I())try{window.localStorage.setItem(G,e)}catch(t){b.warn("Unable to persist theme preference:",t)}},K=()=>{if(!I())return null;let{documentElement:e}=document;return e instanceof HTMLElement?e:null},Fe=e=>typeof e=="string"?e.trim():"",Ve=e=>{if(!e||typeof e!="object")return null;let t=Object.entries(e).reduce((r,[o,n])=>{if(!n||typeof n!="object")return r;let c=Object.entries(n).reduce((a,[u,d])=>(Object.prototype.hasOwnProperty.call(ue,u)&&(a[u]=d),a),{});return Object.keys(c).length>0&&(r[o]=c),r},{});return Object.keys(t).length>0?t:null},fe=e=>{F=Ve(e)};fe(typeof se<"u"?se:null);var pe=(e,t)=>{if(!e)return;let r=F&&typeof F=="object"&&F[t]&&typeof F[t]=="object"?F[t]:null,o=!1;Object.entries(ue).forEach(([n,c])=>{let a=r?r[n]:null,u=Fe(a);u?(e.style.setProperty(c,u),o=!0):e.style.removeProperty(c)}),o?e.dataset.themeAccent="override":e.removeAttribute("data-theme-accent")},Pe=e=>{if(!I())return;let t=document.querySelector('meta[name="theme-color"]');if(!t)return;let r=K();if(!r)return;let o="";try{typeof window.getComputedStyle=="function"&&(o=getComputedStyle(r).getPropertyValue("--color-page").trim())}catch(a){b.warn("Unable to compute theme color token:",a)}let n=ie[e]||ie[v],c=o||n;c&&t.setAttribute("content",c)},me=(e,t)=>{if(!(e instanceof HTMLElement))return;e.dataset.themeState=t,e.setAttribute("aria-pressed",t==="dark"?"true":"false");let r=t==="dark"?"Switch to light theme":"Switch to dark theme";e.setAttribute("aria-label",r),e.setAttribute("title",r);let o=e.querySelector("[data-theme-toggle-label]")||e.querySelector(".sr-only");o&&(o.textContent=r);let n=e.querySelector("[data-theme-toggle-icon]")||e.querySelector('[aria-hidden="true"]');if(n){let c=typeof SVGElement<"u"?SVGElement:null;if(c&&n instanceof c||n instanceof Element&&!!n.querySelector("svg"))n instanceof Element&&n.setAttribute("data-theme-icon-state",t);else{let u=e.dataset.iconLight||"\u2600\uFE0F",d=e.dataset.iconDark||"\u{1F319}";n.textContent=t==="dark"?u:d}}},We=()=>{if(!I()){W.clear();return}W.forEach(e=>{(!(e instanceof HTMLElement)||!document.contains(e))&&W.delete(e)})},qe=e=>{We(),W.forEach(t=>{me(t,e)})},je=e=>{!(e instanceof HTMLElement)||le.has(e)||(e.addEventListener("click",t=>{t.preventDefault(),Ye(D==="dark"?"light":"dark")}),e.dataset.themeToggleBound="true",le.add(e))},$e=(e=document)=>{if(!I())return[];let t=new Set,r=e&&typeof e.querySelectorAll=="function"?e:document;return ce.forEach(o=>{try{r.querySelectorAll(o).forEach(n=>{n instanceof HTMLElement&&t.add(n)})}catch(n){b.warn(`Invalid selector for theme toggle: ${o}`,n)}}),e instanceof HTMLElement&&ce.forEach(o=>{e.matches(o)&&t.add(e)}),Array.from(t)},X=(e,{persist:t=!0}={})=>{let r=q(e)||v;D=r;let o=K();return o&&(o.dataset.theme=r,pe(o,r)),t&&ke(r),Pe(r),qe(r),r},he=e=>{fe(e);let t=K();if(!t)return;pe(t,D||v)},Ye=e=>I()?X(e,{persist:!0}):(D=q(e)||v,D),Ke=(e=document)=>{if(!I())return;$e(e).forEach(r=>{W.add(r),je(r),me(r,D||v)})},ze=()=>{if(!I())return v;let e=K();return(e?q(e.dataset.theme):null)||He()||v},Ge=e=>{if(!e||e.key!==G)return;let t=q(e.newValue)||v;t!==D&&X(t,{persist:!1})},be=()=>{if(!I()){D=v;return}de||(window.addEventListener("storage",Ge),de=!0);let e=ze();X(e,{persist:!0}),Ke(document)};var ye={info:"torrent-toast torrent-toast--info",success:"torrent-toast torrent-toast--success",error:"torrent-toast torrent-toast--error",warn:"torrent-toast torrent-toast--warn"},Ee="transition duration-200 ease-out",Xe="beacon-toast-motion",Z="data-beacon-motion";function Ze(e,t){let r=e?.defaultView;if(r&&typeof r.requestAnimationFrame=="function"){r.requestAnimationFrame(t);return}setTimeout(t,16)}function Je(e){let t=e||(typeof document<"u"?document:null);if(!t)return null;let r=t.getElementById("beacon-toast-container");if(!r){r=t.createElement("div"),r.id="beacon-toast-container",r.setAttribute("role","region"),r.setAttribute("aria-live","polite"),r.className=["pointer-events-none","fixed","inset-x-0","top-0","z-overlay-toast","flex","w-full","flex-col","items-center","gap-3","px-4","pt-6","sm:items-end","sm:px-6","sm:pt-8"].join(" ");let o=t.body||t.getElementsByTagName?.("body")?.[0];o&&o.appendChild(r)}return r}function Qe(e,t,r){let o=e.createElement("div");o.className=[Ee,r].join(" "),o.setAttribute("role","status"),o.setAttribute("tabindex","0"),o.textContent=t;let n=e.createElement("button");n.type="button",n.className=["notification-dismiss","focus-ring","absolute","right-3","top-3"].join(" "),n.setAttribute("aria-label","Dismiss notification"),n.innerHTML='';let c=e.createElement("div");return c.className=["relative","w-full",Ee,Xe].join(" "),c.setAttribute(Z,"exit"),c.appendChild(o),o.appendChild(n),{wrapper:c,toast:o,closeButton:n}}function et(e,t=setTimeout,r=clearTimeout){return{show(o,n={}){if(!o)return;let c=n.type&&ye[n.type]?n.type:"info",a=Je(e);if(!a)return;let{wrapper:u,toast:d,closeButton:L}=Qe(e,o,ye[c]);a.appendChild(u),Ze(e,()=>{u.setAttribute(Z,"enter")});let w=typeof n.duration=="number"?n.duration:3500,m=()=>{u.setAttribute(Z,"exit");let N=t(()=>{u.parentNode&&u.parentNode.removeChild(u)},200);return L.removeEventListener("click",A),d.removeEventListener("keydown",y),L.removeEventListener("keydown",y),N},A=()=>m(),y=N=>{N.key==="Escape"&&(N.stopPropagation(),m())};L.addEventListener("click",A),d.addEventListener("keydown",y),L.addEventListener("keydown",y);let B=null;return n.sticky||(B=t(()=>{m()},w)),{dismiss(){B&&r(B),m()}}},set(o,n){return typeof n=="string"?this.show(o,{type:n}):this.show(o,n)},info(o,n={}){return this.show(o,{...n,type:"info"})},success(o,n={}){return this.show(o,{...n,type:"success"})},error(o,n={}){return this.show(o,{...n,type:"error"})},warn(o,n={}){return this.show(o,{...n,type:"warn"})}}}function Te(e,t=setTimeout,r=clearTimeout){return et(e,t,r)}var J=[{key:"name",label:"Name",headerClass:"w-full sm:w-1/3 text-left",cellClass:"font-medium text-text truncate"},{key:"progress",label:"Progress",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"progress"},{key:"downloadSpeed",label:"\u2193 Speed",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"bytesPerSecond"},{key:"uploadSpeed",label:"\u2191 Speed",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"bytesPerSecond"},{key:"numPeers",label:"Peers",headerClass:"w-20 text-right",cellClass:"tabular-nums text-right",formatter:"integer"},{key:"ratio",label:"Ratio",headerClass:"w-20 text-right",cellClass:"tabular-nums text-right",formatter:"ratio"}];function tt(e){return e?e.infoHash||e.magnetURI||e.name||String(Math.random()):""}function rt(e,t){return!e||!t?!1:e===t||e.magnetURI&&e.magnetURI===t.magnetURI||e.infoHash&&e.infoHash===t.infoHash}function nt(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function ot(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function at(e){return typeof e!="number"||!Number.isFinite(e)?"0":Math.max(0,Math.floor(e)).toString()}function st(e,t){if(typeof t=="function")return t(e,!0)||"";if(typeof e!="number"||Number.isNaN(e)||e<1)return"";let r=["B","kB","MB","GB","TB"],o=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),r.length-1);return`${(e/Math.pow(1e3,o)).toFixed(1)} ${r[o]}/s`}function it(e,t){let r=e.formatter;if(typeof r=="function")return r;let o=t?.[r];if(typeof o=="function")return o;switch(r){case"progress":return nt;case"ratio":return ot;case"integer":return at;case"bytesPerSecond":return n=>st(n,t?.bytes);default:return n=>n}}function ge({documentRef:e=typeof document<"u"?document:null,root:t,onSelect:r,formatters:o={}}={}){if(!e)throw new Error("createTorrentTable requires a document reference");if(!(t instanceof e.defaultView.HTMLElement))throw new Error("createTorrentTable requires a valid root element");let n=e;t.innerHTML="";let c=n.createElement("div");c.className="overflow-hidden rounded-xl border border-border-translucent bg-surface shadow-sm";let a=n.createElement("div");a.className="overflow-x-auto",c.appendChild(a);let u=n.createElement("table");u.className="min-w-full divide-y divide-border-translucent text-sm",a.appendChild(u);let d=n.createElement("thead");d.className="bg-surface-alt text-muted-strong";let L=n.createElement("tr");J.forEach(E=>{let x=n.createElement("th");x.setAttribute("scope","col"),x.className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide",E.headerClass&&(x.className+=` ${E.headerClass}`),x.textContent=E.label,L.appendChild(x)}),d.appendChild(L),u.appendChild(d);let w=n.createElement("tbody");w.className="divide-y divide-border-translucent",u.appendChild(w);let m=n.createElement("tbody"),A=n.createElement("tr"),y=n.createElement("td");y.className="px-4 py-8 text-center text-sm text-muted",y.colSpan=J.length,y.textContent="No active torrents yet. Add a magnet link to get started.",A.appendChild(y),m.appendChild(A),u.appendChild(m),t.appendChild(c);function B(E=[],x=null){let V=Array.isArray(E)?E:[];if(w.innerHTML="",!V.length){w.hidden=!0,m.hidden=!1;return}w.hidden=!1,m.hidden=!0;for(let O of V){let g=n.createElement("tr");g.className=["group","align-middle","cursor-pointer","transition","hover:bg-panel-hover","focus-visible:outline","focus-visible:outline-2","focus-visible:outline-offset-2","focus-visible:outline-info"].join(" "),g.setAttribute("role","button"),g.setAttribute("tabindex","0"),g.dataset.torrentId=tt(O),rt(x,O)?(g.classList.add("bg-info/10","text-text"),g.setAttribute("aria-pressed","true")):g.setAttribute("aria-pressed","false"),g.addEventListener("click",()=>{typeof r=="function"&&r(O)}),g.addEventListener("keydown",C=>{(C.key==="Enter"||C.key===" ")&&(C.preventDefault(),typeof r=="function"&&r(O))}),J.forEach(C=>{let H=n.createElement("td");H.className="px-4 py-3",C.cellClass&&(H.className+=` ${C.cellClass}`);let z=O?O[C.key]:void 0,s=it(C,o)(z,O,C),i=n.createElement("span");i.textContent=s==null?"":String(s),H.appendChild(i),g.appendChild(H)}),w.appendChild(g)}}function N(){t.innerHTML=""}return{render:B,destroy:N}}var ct="2.0",Le=["wss://tracker.btorrent.xyz","wss://tracker.openwebtorrent.com"],_e={announce:Le},lt={announce:Le};function j(e,t=!1){if(typeof e!="number"||Number.isNaN(e))return"";if(e<1)return t?"":"0 B";let r=["B","kB","MB","GB","TB"],o=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),r.length-1);return`${(e/Math.pow(1e3,o)).toFixed(1)} ${r[o]}${t?"/s":""}`}function dt(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function Se(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function ut(e,t){return!e||!t?!1:!!(e===t||e.infoHash&&t.infoHash&&e.infoHash===t.infoHash||e.magnetURI&&t.magnetURI&&e.magnetURI===t.magnetURI)}function ft(e){if(!(!e||!Array.isArray(e.files))&&(e.files.forEach(t=>{typeof t.priority!="string"&&(t.priority="0"),typeof t.getBlobURL=="function"&&t.getBlobURL((r,o)=>{r||!o||(t.url=o)})}),e.torrentFile&&typeof Blob=="function"&&typeof URL<"u"))try{let t=new Blob([e.torrentFile],{type:"application/x-bittorrent"});if(e.torrentFileBlobURL=URL.createObjectURL(t),!e.fileName){let r=e.name||"download";e.fileName=`${r}.torrent`}}catch(t){S.warn("[beacon] Failed to create torrent blob URL",t)}}function pt(e,t){if(!e)return;let r=typeof t=="string"?t:String(t??"0");if(e.priority=r,r==="-1"){typeof e.deselect=="function"&&e.deselect();return}let o=Number.parseInt(r,10);Number.isFinite(o)&&typeof e.select=="function"&&e.select(o)}function we(e,t){if(!Array.isArray(t)||t.length===0)return null;if(!e)return t[0];let r=t.find(o=>ut(o,e));return r||t[0]}function Ae({documentRef:e=typeof document<"u"?document:null,WebTorrentCtor:t}={}){if(!e)throw new Error("createBeaconApp requires a document reference");let r=e.defaultView||globalThis,o=typeof t=="function"?t:typeof r?.WebTorrent=="function"?r.WebTorrent:null;if(!o)throw new Error("createBeaconApp requires a WebTorrent constructor");let n=Te(e),c=new o({tracker:lt}),a={form:e.querySelector('[data-beacon="magnet-form"]'),magnetInput:e.querySelector('[data-beacon="magnet-input"]'),seedButton:e.querySelector('[data-beacon="seed-button"]'),seedInput:e.querySelector('[data-beacon="seed-input"]'),tableRoot:e.querySelector('[data-beacon="torrent-table"]'),selectedSection:e.querySelector('[data-beacon="selected"]'),selectedName:e.querySelector('[data-beacon="selected-name"]'),pauseButton:e.querySelector('[data-beacon-action="pause"]'),resumeButton:e.querySelector('[data-beacon-action="resume"]'),downloadAllButton:e.querySelector('[data-beacon-action="download-all"]'),removeButton:e.querySelector('[data-beacon-action="remove"]'),copyMagnetButton:e.querySelector('[data-beacon-action="copy-magnet"]'),infoHashValue:e.querySelector('[data-beacon="info-hash"]'),fileList:e.querySelector('[data-beacon="file-list"]'),stats:e.querySelector('[data-beacon="client-stats"]'),overlay:e.querySelector('[data-beacon="processing-overlay"]')},u=a.tableRoot?ge({documentRef:e,root:a.tableRoot,formatters:{bytes:(s,i=!1)=>j(s,i),progress:s=>dt(s),ratio:s=>Se(s),integer:s=>typeof s=="number"&&Number.isFinite(s)?Math.floor(s).toString():"0",bytesPerSecond:s=>j(s,!0)},onSelect(s){d.selectedTorrent=s,E()}}):null,d={selectedTorrent:null,processing:!1},L=[],w=[];function m(s){L.push(s)}function A(s){if(d.processing=!!s,!a.overlay)return;let i=d.processing;a.overlay.hidden=!i,a.overlay.setAttribute("aria-hidden",i?"false":"true"),i?a.overlay.style.removeProperty("display"):a.overlay.style.display="none"}function y(){return Array.isArray(c.torrents)?[...c.torrents]:[]}function B(){if(!a.stats)return;let s=j(c.downloadSpeed,!0)||"0 B/s",i=j(c.uploadSpeed,!0)||"0 B/s",p=Se(c.ratio);a.stats.textContent=`Client stats: \u2193 ${s} \xB7 \u2191 ${i} \xB7 Ratio: ${p}`}function N(s){a.fileList&&(a.fileList.innerHTML="",!(!s||!Array.isArray(s.files)||!s.files.length)&&s.files.forEach((i,p)=>{let f=e.createElement("tr");f.className="align-top";let l=e.createElement("td");if(l.className="px-3 py-2",i.done&&i.url){let T=e.createElement("a");T.className="text-info hover:text-info-strong",T.href=i.url,T.download=i.name||`file-${p+1}`,T.textContent=i.name||`File ${p+1}`,l.appendChild(T)}else{let T=e.createElement("span");T.textContent=i.name||`File ${p+1}`,l.appendChild(T)}f.appendChild(l);let h=e.createElement("td");h.className="px-3 py-2 text-muted-strong",h.textContent=j(i.length)||"0 B",f.appendChild(h);let P=e.createElement("td");P.className="px-3 py-2";let R=e.createElement("select");R.className="select",R.name=`${i.name||`file-${p+1}`}-priority`,R.setAttribute("aria-label",`Set priority for ${i.name||`file ${p+1}`}`),[{value:"1",label:"High priority"},{value:"0",label:"Low priority"},{value:"-1",label:"Don't download"}].forEach(T=>{let Y=e.createElement("option");Y.value=T.value,Y.textContent=T.label,T.value===i.priority&&(Y.selected=!0),R.appendChild(Y)}),R.value=i.priority??"0",R.addEventListener("change",T=>{pt(i,T.target.value)}),P.appendChild(R),f.appendChild(P),a.fileList.appendChild(f)}))}function E(){if(!a.selectedSection)return;let s=y();d.selectedTorrent=we(d.selectedTorrent,s);let i=d.selectedTorrent;a.selectedSection.hidden=!i,i&&(a.selectedName&&(a.selectedName.textContent=i.name||"Untitled torrent"),a.pauseButton&&(a.pauseButton.hidden=i.paused===!0,a.pauseButton.disabled=i.paused===!0),a.resumeButton&&(a.resumeButton.hidden=i.paused!==!0,a.resumeButton.disabled=i.paused!==!0),a.downloadAllButton&&(a.downloadAllButton.disabled=!(i.progress>=1)),a.infoHashValue&&(a.infoHashValue.textContent=i.infoHash||""),N(i))}function x(s,{isSeed:i=!1}={}){A(!1),ft(s),i||n.info(`Received ${s.name||"torrent"} metadata`),d.selectedTorrent||(d.selectedTorrent=s),E(),u?.render(y(),d.selectedTorrent)}function V(s){let i=typeof s=="string"?s.trim():"";i&&(A(!0),c.add(i,_e,p=>{x(p,{isSeed:!1})}))}function O(s){!Array.isArray(s)||s.length===0||(A(!0),c.seed(s,_e,i=>{x(i,{isSeed:!0}),n.success(`Seeding ${i.files.length} file(s)`)}))}function g(s){if(s){if(s.progress<1){n.warn("Torrent is not finished downloading yet.");return}s.files.forEach(i=>{typeof i.getBlob=="function"&&i.getBlob((p,f)=>{if(p||!f){b.error("[beacon] Failed to get blob for file",i?.name,p),n.error("Failed to prepare file for download.");return}let l=URL.createObjectURL(f),h=e.createElement("a");h.className="beacon-hidden-download",h.setAttribute("data-beacon-hidden-download","true"),h.href=l,h.download=i.name||"download",e.body.appendChild(h),h.click(),e.body.removeChild(h),URL.revokeObjectURL(l)})})}}function C(s){if(!s?.magnetURI)return;let i=s.magnetURI,p=r?.navigator?.clipboard;if(p&&typeof p.writeText=="function"){p.writeText(i).then(()=>{n.success("Magnet URI copied to clipboard!")}).catch(f=>{b.error("[beacon] Clipboard error",f),n.error("Failed to copy magnet URI")});return}try{let f=e.createElement("textarea");f.value=i,f.className="beacon-clipboard-offscreen",f.setAttribute("data-beacon-clipboard-state","offscreen"),e.body.appendChild(f),f.select(),e.execCommand?.("copy"),e.body.removeChild(f),n.success("Magnet URI copied (fallback)!")}catch(f){b.error("[beacon] Clipboard fallback error",f),n.error("Failed to copy magnet URI")}}function H(s){s&&s.destroy(i=>{i&&(b.error("[beacon] Failed to destroy torrent",i),n.error("Failed to remove torrent"));let p=y();d.selectedTorrent=we(null,p),u?.render(p,d.selectedTorrent),E()})}function z(){if(S.info(`[beacon] Starting beacon runtime v${ct}`),n||S.warn("[beacon] Toast service unavailable"),o.WEBRTC_SUPPORT||n?.error("Please use a browser with WebRTC support.",{sticky:!0}),a.form){let l=h=>{h.preventDefault(),V(a.magnetInput?.value||""),a.magnetInput&&(a.magnetInput.value="")};a.form.addEventListener("submit",l),m(()=>a.form.removeEventListener("submit",l))}if(a.seedButton&&a.seedInput){let l=()=>{a.seedInput.click()},h=()=>{let P=Array.from(a.seedInput.files||[]);a.seedInput.value="",O(P)};a.seedButton.addEventListener("click",l),a.seedInput.addEventListener("change",h),m(()=>{a.seedButton.removeEventListener("click",l),a.seedInput.removeEventListener("change",h)})}if(a.pauseButton){let l=()=>{d.selectedTorrent&&typeof d.selectedTorrent.pause=="function"&&(d.selectedTorrent.pause(),E())};a.pauseButton.addEventListener("click",l),m(()=>a.pauseButton.removeEventListener("click",l))}if(a.resumeButton){let l=()=>{d.selectedTorrent&&typeof d.selectedTorrent.resume=="function"&&(d.selectedTorrent.resume(),E())};a.resumeButton.addEventListener("click",l),m(()=>a.resumeButton.removeEventListener("click",l))}if(a.downloadAllButton){let l=()=>g(d.selectedTorrent);a.downloadAllButton.addEventListener("click",l),m(()=>a.downloadAllButton.removeEventListener("click",l))}if(a.removeButton){let l=()=>H(d.selectedTorrent);a.removeButton.addEventListener("click",l),m(()=>a.removeButton.removeEventListener("click",l))}if(a.copyMagnetButton){let l=()=>C(d.selectedTorrent);a.copyMagnetButton.addEventListener("click",l),m(()=>a.copyMagnetButton.removeEventListener("click",l))}let s=l=>{b.error("[beacon] Torrent client error",l),n?.error(l?.message||String(l)),A(!1)};c.on("error",s),m(()=>c.removeListener("error",s));let i=l=>{if(c.torrents&&c.torrents.length>0)return l.preventDefault(),l.returnValue="Transfers are in progress. Are you sure you want to leave or refresh?",l.returnValue};r&&typeof r.addEventListener=="function"&&(r.addEventListener("beforeunload",i),m(()=>r.removeEventListener("beforeunload",i))),u?.render(y(),d.selectedTorrent),E(),B(),A(!1);let p=r.setInterval(()=>{u?.render(y(),d.selectedTorrent),E(),B()},1e3);w.push(p);let f=r?.location?.hash;typeof f=="string"&&f.length>1&&V(f.slice(1))}function Q(){w.forEach(s=>r.clearInterval(s)),w.length=0,L.forEach(s=>{try{s()}catch(i){S.warn("[beacon] Cleanup task failed",i)}}),L.length=0,u?.destroy();try{c.destroy()}catch(s){S.warn("[beacon] Failed to destroy client",s)}}return{mount:z,destroy:Q}}var Ce="../../config/instance-config.js";function mt(){return typeof window<"u"&&window||typeof globalThis<"u"&&globalThis||null}function ht(){let e=mt();if(e&&typeof e.WebTorrent=="function")return e.WebTorrent;throw new Error("WebTorrent runtime is not available on the global scope")}var $=null,bt=async()=>{try{let e=await import(Ce);if(e&&typeof e.THEME_ACCENT_OVERRIDES<"u")return e.THEME_ACCENT_OVERRIDES}catch(e){S.warn(`[beacon] Failed to load theme accent overrides from ${Ce}`,e)}return null},yt=async()=>{try{let e=await bt();e&&he(e)}catch(e){S.warn("[beacon] Unable to hydrate theme accent overrides",e)}try{be()}catch(e){S.warn("[beacon] Failed to initialize theme controller",e)}};yt();function xe(){if($)return S.info("[beacon] Reusing existing beacon app instance"),$;if(typeof document>"u")throw new Error("Beacon runtime requires a document environment");let e=ht();return S.info("[beacon] Creating beacon app instance"),$=Ae({documentRef:document,WebTorrentCtor:e}),$.mount(),$}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{try{xe()}catch(e){b.error("[beacon] Failed to mount app",e)}});else try{xe()}catch(e){b.error("[beacon] Failed to mount app",e)}export{Ae as createBeaconApp,xe as mountBeaconApp}; +import{IS_DEV_MODE as Be,IS_VERBOSE_DEV_MODE as Me,IS_LOCKDOWN_MODE as De,LOCKDOWN_TITLE as Ct,LOCKDOWN_MESSAGE as xt,ADMIN_SUPER_NPUB as It,ADMIN_DM_IMAGE_URL as vt,BITVID_WEBSITE_URL as Ot,BLOG_URL as Nt,COMMUNITY_URL as Bt,NOSTR_URL as Mt,GITHUB_URL as Dt,BETA_URL as Rt,DNS_URL as Ut,TIP_JAR_URL as Ht,MAX_WALLET_DEFAULT_ZAP as kt,PLATFORM_FEE_PERCENT as Ft,PLATFORM_LUD16_OVERRIDE as Pt,DEFAULT_RELAY_URLS_OVERRIDE as Vt,ADMIN_WHITELIST_MODE_STORAGE_KEY as Wt,DEFAULT_WHITELIST_MODE_ENABLED as qt,ALLOW_NSFW_CONTENT as jt,FEATURE_NIP71_INGEST as $t,DEFAULT_TRUST_SEED_NPUBS as Yt,DEFAULT_BLUR_THRESHOLD as Gt,DEFAULT_AUTOPLAY_BLOCK_THRESHOLD as Kt,DEFAULT_TRUSTED_MUTE_HIDE_THRESHOLD as zt,DEFAULT_TRUSTED_SPAM_HIDE_THRESHOLD as Xt,WATCH_HISTORY_KIND as Zt,WATCH_HISTORY_LIST_IDENTIFIER as Jt,WATCH_HISTORY_MAX_ITEMS as Qt,WATCH_HISTORY_BATCH_RESOLVE as er,WATCH_HISTORY_BATCH_PAGE_SIZE as tr,WATCH_HISTORY_PAYLOAD_MAX_BYTES as rr,WATCH_HISTORY_FETCH_EVENT_LIMIT as nr,WATCH_HISTORY_CACHE_TTL_MS as or,VIEW_COUNT_DEDUPE_WINDOW_SECONDS as ar,VIEW_COUNT_BACKFILL_MAX_DAYS as sr,VIEW_COUNT_CACHE_TTL_MS as ir,ENSURE_PRESENCE_REBROADCAST_COOLDOWN_SECONDS as cr,DEFAULT_PLAYBACK_SOURCE as lr,DEFAULT_PLAYBACK_START_TIMEOUT as dr,ENABLE_NIP17_RELAY_WARNING as ur}from"../config/instance-config.js";var oe=typeof globalThis>"u"?null:globalThis,ae=e=>{if(typeof e=="boolean")return e;if(typeof e=="string"){let t=e.trim().toLowerCase();if(t==="true")return!0;if(t==="false")return!1}return null},re=ae(oe?.__BITVID_DEV_MODE_OVERRIDE__),ne=ae(oe?.__BITVID_VERBOSE_DEV_MODE_OVERRIDE__),U=re===null?!!Be:re;var Re=ne===null?!!Me:ne;var Ue=!!De;typeof window<"u"&&(window.__BITVID_DEV_MODE__=U,window.__BITVID_VERBOSE_DEV_MODE__=Re,window.__BITVID_LOCKDOWN_MODE__=Ue);var k=()=>{};function He(){return typeof console>"u"||console===null?null:console}function ke(){let e=He();if(!e)return{log:k,info:k,debug:k,warn:k,error:k};let t=typeof e.log=="function"?e.log.bind(e):k;return{log:typeof e.log=="function"?e.log.bind(e):t,info:typeof e.info=="function"?e.info.bind(e):t,debug:typeof e.debug=="function"?e.debug.bind(e):t,warn:typeof e.warn=="function"?e.warn.bind(e):t,error:typeof e.error=="function"?e.error.bind(e):t}}var w=ke(),M="[bitvid]";function se(e){if(e.length===0)return{args:e,force:!1};let t=e[e.length-1];if(t&&typeof t=="object"&&!Array.isArray(t)&&Object.prototype.hasOwnProperty.call(t,"force")){let r=[...e],o=r.pop();return{args:r,force:!!o.force}}return{args:e,force:!1}}var A=Object.freeze({log:(...e)=>{U&&w.log(...e)},info:(...e)=>{U&&w.info(...e)},debug:(...e)=>{U&&w.debug(...e)},warn:(...e)=>{U&&w.warn(...e)},error:(...e)=>{U&&w.error(...e)}}),m=Object.freeze({log:(...e)=>{let{args:t,force:r}=se(e);if(!r&&t.length===0)return;if(r){w.log(M,...t);return}let[o,...n]=t;if(typeof o=="string"&&n.length===0){w.log(`${M} ${o}`);return}o!==void 0&&w.log(M,o)},info:(...e)=>{let{args:t,force:r}=se(e);if(!r&&t.length===0)return;if(r){w.info(M,...t);return}let[o,...n]=t;if(typeof o=="string"&&n.length===0){w.info(`${M} ${o}`);return}o!==void 0&&w.info(M,o)},warn:(...e)=>{w.warn(M,...e)},error:(...e)=>{w.error(M,...e)}}),ie=Object.freeze({dev:A,user:m});function Fe(e){return!e||typeof e!="object"?!1:Object.entries({dev:["log","info","debug","warn","error"],user:["log","info","warn","error"]}).every(([r,o])=>{let n=e[r];return!n||typeof n!="object"?!1:o.every(c=>typeof n[c]=="function")})}typeof window<"u"&&(Fe(window.bitvidLogger)||(window.bitvidLogger=ie));Object.freeze(ie);import{THEME_ACCENT_OVERRIDES as ce}from"../config/instance-config.js";var Z="bitvid:theme",O="dark",Pe=new Set(["light","dark"]),le={dark:"rgb(15, 23, 42)",light:"rgb(248, 250, 252)"},de=["[data-theme-toggle]",'[data-action="toggle-theme"]',".js-theme-toggle","#themeToggle"],pe=Object.freeze({accent:"--color-accent",accentStrong:"--color-accent-strong",accentPressed:"--color-accent-pressed"}),ue=new WeakSet,V=new Set,D=null,fe=!1,F=null,N=()=>typeof window<"u"&&typeof document<"u",W=e=>{if(typeof e!="string")return null;let t=e.trim().toLowerCase();return t==="default"||t===""?O:Pe.has(t)?t:null},Ve=()=>{if(!N())return null;try{let e=window.localStorage.getItem(Z);return W(e)}catch(e){return m.warn("Unable to read stored theme preference:",e),null}},We=e=>{if(N())try{window.localStorage.setItem(Z,e)}catch(t){m.warn("Unable to persist theme preference:",t)}},X=()=>{if(!N())return null;let{documentElement:e}=document;return e instanceof HTMLElement?e:null},qe=e=>typeof e=="string"?e.trim():"",je=e=>{if(!e||typeof e!="object")return null;let t=Object.entries(e).reduce((r,[o,n])=>{if(!n||typeof n!="object")return r;let c=Object.entries(n).reduce((a,[p,d])=>(Object.prototype.hasOwnProperty.call(pe,p)&&(a[p]=d),a),{});return Object.keys(c).length>0&&(r[o]=c),r},{});return Object.keys(t).length>0?t:null},me=e=>{F=je(e)};me(typeof ce<"u"?ce:null);var he=(e,t)=>{if(!e)return;let r=F&&typeof F=="object"&&F[t]&&typeof F[t]=="object"?F[t]:null,o=!1;Object.entries(pe).forEach(([n,c])=>{let a=r?r[n]:null,p=qe(a);p?(e.style.setProperty(c,p),o=!0):e.style.removeProperty(c)}),o?e.dataset.themeAccent="override":e.removeAttribute("data-theme-accent")},$e=e=>{if(!N())return;let t=document.querySelector('meta[name="theme-color"]');if(!t)return;let r=X();if(!r)return;let o="";try{typeof window.getComputedStyle=="function"&&(o=getComputedStyle(r).getPropertyValue("--color-page").trim())}catch(a){m.warn("Unable to compute theme color token:",a)}let n=le[e]||le[O],c=o||n;c&&t.setAttribute("content",c)},be=(e,t)=>{if(!(e instanceof HTMLElement))return;e.dataset.themeState=t,e.setAttribute("aria-pressed",t==="dark"?"true":"false");let r=t==="dark"?"Switch to light theme":"Switch to dark theme";e.setAttribute("aria-label",r),e.setAttribute("title",r);let o=e.querySelector("[data-theme-toggle-label]")||e.querySelector(".sr-only");o&&(o.textContent=r);let n=e.querySelector("[data-theme-toggle-icon]")||e.querySelector('[aria-hidden="true"]');if(n){let c=typeof SVGElement<"u"?SVGElement:null;if(c&&n instanceof c||n instanceof Element&&!!n.querySelector("svg"))n instanceof Element&&n.setAttribute("data-theme-icon-state",t);else{let p=e.dataset.iconLight||"\u2600\uFE0F",d=e.dataset.iconDark||"\u{1F319}";n.textContent=t==="dark"?p:d}}},Ye=()=>{if(!N()){V.clear();return}V.forEach(e=>{(!(e instanceof HTMLElement)||!document.contains(e))&&V.delete(e)})},Ge=e=>{Ye(),V.forEach(t=>{be(t,e)})},Ke=e=>{!(e instanceof HTMLElement)||ue.has(e)||(e.addEventListener("click",t=>{t.preventDefault(),Xe(D==="dark"?"light":"dark")}),e.dataset.themeToggleBound="true",ue.add(e))},ze=(e=document)=>{if(!N())return[];let t=new Set,r=e&&typeof e.querySelectorAll=="function"?e:document;return de.forEach(o=>{try{r.querySelectorAll(o).forEach(n=>{n instanceof HTMLElement&&t.add(n)})}catch(n){m.warn(`Invalid selector for theme toggle: ${o}`,n)}}),e instanceof HTMLElement&&de.forEach(o=>{e.matches(o)&&t.add(e)}),Array.from(t)},J=(e,{persist:t=!0}={})=>{let r=W(e)||O;D=r;let o=X();return o&&(o.dataset.theme=r,he(o,r)),t&&We(r),$e(r),Ge(r),r},Ee=e=>{me(e);let t=X();if(!t)return;he(t,D||O)},Xe=e=>N()?J(e,{persist:!0}):(D=W(e)||O,D),Ze=(e=document)=>{if(!N())return;ze(e).forEach(r=>{V.add(r),Ke(r),be(r,D||O)})},Je=()=>{if(!N())return O;let e=X();return(e?W(e.dataset.theme):null)||Ve()||O},Qe=e=>{if(!e||e.key!==Z)return;let t=W(e.newValue)||O;t!==D&&J(t,{persist:!1})},ye=()=>{if(!N()){D=O;return}fe||(window.addEventListener("storage",Qe),fe=!0);let e=Je();J(e,{persist:!0}),Ze(document)};var Te={info:"torrent-toast torrent-toast--info",success:"torrent-toast torrent-toast--success",error:"torrent-toast torrent-toast--error",warn:"torrent-toast torrent-toast--warn"},ge="transition duration-200 ease-out",et="beacon-toast-motion",Q="data-beacon-motion";function tt(e,t){let r=e?.defaultView;if(r&&typeof r.requestAnimationFrame=="function"){r.requestAnimationFrame(t);return}setTimeout(t,16)}function rt(e){let t=e||(typeof document<"u"?document:null);if(!t)return null;let r=t.getElementById("beacon-toast-container");if(!r){r=t.createElement("div"),r.id="beacon-toast-container",r.setAttribute("role","region"),r.setAttribute("aria-live","polite"),r.className=["pointer-events-none","fixed","inset-x-0","top-0","z-overlay-toast","flex","w-full","flex-col","items-center","gap-3","px-4","pt-6","sm:items-end","sm:px-6","sm:pt-8"].join(" ");let o=t.body||t.getElementsByTagName?.("body")?.[0];o&&o.appendChild(r)}return r}function nt(e,t,r){let o=e.createElement("div");o.className=[ge,r].join(" "),o.setAttribute("role","status"),o.setAttribute("tabindex","0"),o.textContent=t;let n=e.createElement("button");n.type="button",n.className=["notification-dismiss","focus-ring","absolute","right-3","top-3"].join(" "),n.setAttribute("aria-label","Dismiss notification"),n.innerHTML='';let c=e.createElement("div");return c.className=["relative","w-full",ge,et].join(" "),c.setAttribute(Q,"exit"),c.appendChild(o),o.appendChild(n),{wrapper:c,toast:o,closeButton:n}}function ot(e,t=setTimeout,r=clearTimeout){return{show(o,n={}){if(!o)return;let c=n.type&&Te[n.type]?n.type:"info",a=rt(e);if(!a)return;let{wrapper:p,toast:d,closeButton:I}=nt(e,o,Te[c]);a.appendChild(p),tt(e,()=>{p.setAttribute(Q,"enter")});let L=typeof n.duration=="number"?n.duration:3500,E=()=>{p.setAttribute(Q,"exit");let T=t(()=>{p.parentNode&&p.parentNode.removeChild(p)},200);return I.removeEventListener("click",y),d.removeEventListener("keydown",h),I.removeEventListener("keydown",h),T},y=()=>E(),h=T=>{T.key==="Escape"&&(T.stopPropagation(),E())};I.addEventListener("click",y),d.addEventListener("keydown",h),I.addEventListener("keydown",h);let B=null;return n.sticky||(B=t(()=>{E()},L)),{dismiss(){B&&r(B),E()}}},set(o,n){return typeof n=="string"?this.show(o,{type:n}):this.show(o,n)},info(o,n={}){return this.show(o,{...n,type:"info"})},success(o,n={}){return this.show(o,{...n,type:"success"})},error(o,n={}){return this.show(o,{...n,type:"error"})},warn(o,n={}){return this.show(o,{...n,type:"warn"})}}}function _e(e,t=setTimeout,r=clearTimeout){return ot(e,t,r)}var ee=[{key:"name",label:"Name",headerClass:"w-full sm:w-1/3 text-left",cellClass:"font-medium text-text truncate"},{key:"progress",label:"Progress",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"progress"},{key:"downloadSpeed",label:"\u2193 Speed",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"bytesPerSecond"},{key:"uploadSpeed",label:"\u2191 Speed",headerClass:"w-24 text-right",cellClass:"tabular-nums text-right",formatter:"bytesPerSecond"},{key:"numPeers",label:"Peers",headerClass:"w-20 text-right",cellClass:"tabular-nums text-right",formatter:"integer"},{key:"ratio",label:"Ratio",headerClass:"w-20 text-right",cellClass:"tabular-nums text-right",formatter:"ratio"}];function at(e){return e?e.infoHash||e.magnetURI||e.name||String(Math.random()):""}function st(e,t){return!e||!t?!1:e===t||e.magnetURI&&e.magnetURI===t.magnetURI||e.infoHash&&e.infoHash===t.infoHash}function it(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function ct(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function lt(e){return typeof e!="number"||!Number.isFinite(e)?"0":Math.max(0,Math.floor(e)).toString()}function dt(e,t){if(typeof t=="function")return t(e,!0)||"";if(typeof e!="number"||Number.isNaN(e)||e<1)return"";let r=["B","kB","MB","GB","TB"],o=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),r.length-1);return`${(e/Math.pow(1e3,o)).toFixed(1)} ${r[o]}/s`}function ut(e,t){let r=e.formatter;if(typeof r=="function")return r;let o=t?.[r];if(typeof o=="function")return o;switch(r){case"progress":return it;case"ratio":return ct;case"integer":return lt;case"bytesPerSecond":return n=>dt(n,t?.bytes);default:return n=>n}}function Se({documentRef:e=typeof document<"u"?document:null,root:t,onSelect:r,formatters:o={}}={}){if(!e)throw new Error("createTorrentTable requires a document reference");if(!(t instanceof e.defaultView.HTMLElement))throw new Error("createTorrentTable requires a valid root element");let n=e;t.innerHTML="";let c=n.createElement("div");c.className="overflow-hidden rounded-xl border border-border-translucent bg-surface shadow-sm";let a=n.createElement("div");a.className="overflow-x-auto",c.appendChild(a);let p=n.createElement("table");p.className="min-w-full divide-y divide-border-translucent text-sm",a.appendChild(p);let d=n.createElement("thead");d.className="bg-surface-alt text-muted-strong";let I=n.createElement("tr");ee.forEach(C=>{let v=n.createElement("th");v.setAttribute("scope","col"),v.className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide",C.headerClass&&(v.className+=` ${C.headerClass}`),v.textContent=C.label,I.appendChild(v)}),d.appendChild(I),p.appendChild(d);let L=n.createElement("tbody");L.className="divide-y divide-border-translucent",p.appendChild(L);let E=n.createElement("tbody"),y=n.createElement("tr"),h=n.createElement("td");h.className="px-4 py-8 text-center text-sm text-muted",h.colSpan=ee.length,h.textContent="No active torrents yet. Add a magnet link to get started.",y.appendChild(h),E.appendChild(y),p.appendChild(E),t.appendChild(c);function B(C=[],v=null){let $=Array.isArray(C)?C:[];if(L.innerHTML="",!$.length){L.hidden=!0,E.hidden=!1;return}L.hidden=!1,E.hidden=!0;for(let g of $){let _=n.createElement("tr");_.className=["group","align-middle","cursor-pointer","transition","hover:bg-panel-hover","focus-visible:outline","focus-visible:outline-2","focus-visible:outline-offset-2","focus-visible:outline-info"].join(" "),_.setAttribute("role","button"),_.setAttribute("tabindex","0"),_.dataset.torrentId=at(g),st(v,g)?(_.classList.add("bg-info/10","text-text"),_.setAttribute("aria-pressed","true")):_.setAttribute("aria-pressed","false"),_.addEventListener("click",()=>{typeof r=="function"&&r(g)}),_.addEventListener("keydown",x=>{(x.key==="Enter"||x.key===" ")&&(x.preventDefault(),typeof r=="function"&&r(g))}),ee.forEach(x=>{let H=n.createElement("td");H.className="px-4 py-3",x.cellClass&&(H.className+=` ${x.cellClass}`);let Y=g?g[x.key]:void 0,G=ut(x,o)(Y,g,x),K=n.createElement("span");K.textContent=G==null?"":String(G),H.appendChild(K),_.appendChild(H)}),L.appendChild(_)}}function T(){t.innerHTML=""}return{render:B,destroy:T}}var ft="2.0",Ce=["wss://tracker.btorrent.xyz","wss://tracker.openwebtorrent.com"],we={announce:Ce},pt={announce:Ce},mt=3e4;function q(e,t=!1){if(typeof e!="number"||Number.isNaN(e))return"";if(e<1)return t?"":"0 B";let r=["B","kB","MB","GB","TB"],o=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),r.length-1);return`${(e/Math.pow(1e3,o)).toFixed(1)} ${r[o]}${t?"/s":""}`}function ht(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function Ae(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function bt(e,t){return!e||!t?!1:!!(e===t||e.infoHash&&t.infoHash&&e.infoHash===t.infoHash||e.magnetURI&&t.magnetURI&&e.magnetURI===t.magnetURI)}function Et(e){if(!(!e||!Array.isArray(e.files))&&(e.files.forEach(t=>{typeof t.priority!="string"&&(t.priority="0"),typeof t.getBlobURL=="function"&&t.getBlobURL((r,o)=>{r||!o||(t.url=o)})}),e.torrentFile&&typeof Blob=="function"&&typeof URL<"u"))try{let t=new Blob([e.torrentFile],{type:"application/x-bittorrent"});if(e.torrentFileBlobURL=URL.createObjectURL(t),!e.fileName){let r=e.name||"download";e.fileName=`${r}.torrent`}}catch(t){A.warn("[beacon] Failed to create torrent blob URL",t)}}function yt(e,t){if(!e)return;let r=typeof t=="string"?t:String(t??"0");if(e.priority=r,r==="-1"){typeof e.deselect=="function"&&e.deselect();return}let o=Number.parseInt(r,10);Number.isFinite(o)&&typeof e.select=="function"&&e.select(o)}function Le(e,t){if(!Array.isArray(t)||t.length===0)return null;if(!e)return t[0];let r=t.find(o=>bt(o,e));return r||t[0]}function xe({documentRef:e=typeof document<"u"?document:null,WebTorrentCtor:t}={}){if(!e)throw new Error("createBeaconApp requires a document reference");let r=e.defaultView||globalThis,o=typeof t=="function"?t:typeof r?.WebTorrent=="function"?r.WebTorrent:null;if(!o)throw new Error("createBeaconApp requires a WebTorrent constructor");let n=_e(e),c=new o({tracker:pt}),a={form:e.querySelector('[data-beacon="magnet-form"]'),magnetInput:e.querySelector('[data-beacon="magnet-input"]'),seedButton:e.querySelector('[data-beacon="seed-button"]'),seedInput:e.querySelector('[data-beacon="seed-input"]'),tableRoot:e.querySelector('[data-beacon="torrent-table"]'),selectedSection:e.querySelector('[data-beacon="selected"]'),selectedName:e.querySelector('[data-beacon="selected-name"]'),pauseButton:e.querySelector('[data-beacon-action="pause"]'),resumeButton:e.querySelector('[data-beacon-action="resume"]'),downloadAllButton:e.querySelector('[data-beacon-action="download-all"]'),removeButton:e.querySelector('[data-beacon-action="remove"]'),copyMagnetButton:e.querySelector('[data-beacon-action="copy-magnet"]'),infoHashValue:e.querySelector('[data-beacon="info-hash"]'),fileList:e.querySelector('[data-beacon="file-list"]'),stats:e.querySelector('[data-beacon="client-stats"]'),overlay:e.querySelector('[data-beacon="processing-overlay"]')},p=a.tableRoot?Se({documentRef:e,root:a.tableRoot,formatters:{bytes:(s,i=!1)=>q(s,i),progress:s=>ht(s),ratio:s=>Ae(s),integer:s=>typeof s=="number"&&Number.isFinite(s)?Math.floor(s).toString():"0",bytesPerSecond:s=>q(s,!0)},onSelect(s){d.selectedTorrent=s,g()}}):null,d={selectedTorrent:null,processing:!1},I=[],L=[],E=null;function y(s){I.push(s)}function h(){E!==null&&(r.clearTimeout(E),E=null)}function B(){T(!0),h(),E=r.setTimeout(()=>{E=null,d.processing&&(T(!1),n?.warn("Still searching for peers \u2014 this is taking a while. It'll keep trying in the background."))},mt)}function T(s){if(d.processing=!!s,!a.overlay)return;let i=d.processing;a.overlay.hidden=!i,a.overlay.setAttribute("aria-hidden",i?"false":"true"),i?a.overlay.style.removeProperty("display"):a.overlay.style.display="none"}function C(){return Array.isArray(c.torrents)?[...c.torrents]:[]}function v(){if(!a.stats)return;let s=q(c.downloadSpeed,!0)||"0 B/s",i=q(c.uploadSpeed,!0)||"0 B/s",u=Ae(c.ratio);a.stats.textContent=`Client stats: \u2193 ${s} \xB7 \u2191 ${i} \xB7 Ratio: ${u}`}function $(s){a.fileList&&(a.fileList.innerHTML="",!(!s||!Array.isArray(s.files)||!s.files.length)&&s.files.forEach((i,u)=>{let f=e.createElement("tr");f.className="align-top";let l=e.createElement("td");if(l.className="px-3 py-2",i.done&&i.url){let S=e.createElement("a");S.className="text-info hover:text-info-strong",S.href=i.url,S.download=i.name||`file-${u+1}`,S.textContent=i.name||`File ${u+1}`,l.appendChild(S)}else{let S=e.createElement("span");S.textContent=i.name||`File ${u+1}`,l.appendChild(S)}f.appendChild(l);let b=e.createElement("td");b.className="px-3 py-2 text-muted-strong",b.textContent=q(i.length)||"0 B",f.appendChild(b);let P=e.createElement("td");P.className="px-3 py-2";let R=e.createElement("select");R.className="select",R.name=`${i.name||`file-${u+1}`}-priority`,R.setAttribute("aria-label",`Set priority for ${i.name||`file ${u+1}`}`),[{value:"1",label:"High priority"},{value:"0",label:"Low priority"},{value:"-1",label:"Don't download"}].forEach(S=>{let z=e.createElement("option");z.value=S.value,z.textContent=S.label,S.value===i.priority&&(z.selected=!0),R.appendChild(z)}),R.value=i.priority??"0",R.addEventListener("change",S=>{yt(i,S.target.value)}),P.appendChild(R),f.appendChild(P),a.fileList.appendChild(f)}))}function g(){if(!a.selectedSection)return;let s=C();d.selectedTorrent=Le(d.selectedTorrent,s);let i=d.selectedTorrent;a.selectedSection.hidden=!i,i&&(a.selectedName&&(a.selectedName.textContent=i.name||"Untitled torrent"),a.pauseButton&&(a.pauseButton.hidden=i.paused===!0,a.pauseButton.disabled=i.paused===!0),a.resumeButton&&(a.resumeButton.hidden=i.paused!==!0,a.resumeButton.disabled=i.paused!==!0),a.downloadAllButton&&(a.downloadAllButton.disabled=!(i.progress>=1)),a.infoHashValue&&(a.infoHashValue.textContent=i.infoHash||""),$(i))}function _(s,{isSeed:i=!1}={}){h(),T(!1),Et(s),i||n.info(`Received ${s.name||"torrent"} metadata`),d.selectedTorrent||(d.selectedTorrent=s),g(),p?.render(C(),d.selectedTorrent)}function x(s){let i=typeof s=="string"?s.trim():"";if(!i)return;B();let u;try{u=c.add(i,we,f=>{_(f,{isSeed:!1})})}catch(f){h(),T(!1),m.error("[beacon] Failed to add magnet",f),n?.error(f?.message||"Couldn't add that magnet.");return}Y(u)}function H(s){if(!Array.isArray(s)||s.length===0)return;B();let i;try{i=c.seed(s,we,u=>{_(u,{isSeed:!0}),n.success(`Seeding ${u.files.length} file(s)`)})}catch(u){h(),T(!1),m.error("[beacon] Failed to seed files",u),n?.error(u?.message||"Couldn't seed those files.");return}Y(i)}function Y(s){!s||typeof s.once!="function"||s.once("error",i=>{h(),T(!1),m.error("[beacon] Torrent error",i),n?.error(i?.message||"That torrent could not be loaded.")})}function te(s){if(s){if(s.progress<1){n.warn("Torrent is not finished downloading yet.");return}s.files.forEach(i=>{typeof i.getBlob=="function"&&i.getBlob((u,f)=>{if(u||!f){m.error("[beacon] Failed to get blob for file",i?.name,u),n.error("Failed to prepare file for download.");return}let l=URL.createObjectURL(f),b=e.createElement("a");b.className="beacon-hidden-download",b.setAttribute("data-beacon-hidden-download","true"),b.href=l,b.download=i.name||"download",e.body.appendChild(b),b.click(),e.body.removeChild(b),URL.revokeObjectURL(l)})})}}function G(s){if(!s?.magnetURI)return;let i=s.magnetURI,u=r?.navigator?.clipboard;if(u&&typeof u.writeText=="function"){u.writeText(i).then(()=>{n.success("Magnet URI copied to clipboard!")}).catch(f=>{m.error("[beacon] Clipboard error",f),n.error("Failed to copy magnet URI")});return}try{let f=e.createElement("textarea");f.value=i,f.className="beacon-clipboard-offscreen",f.setAttribute("data-beacon-clipboard-state","offscreen"),e.body.appendChild(f),f.select(),e.execCommand?.("copy"),e.body.removeChild(f),n.success("Magnet URI copied (fallback)!")}catch(f){m.error("[beacon] Clipboard fallback error",f),n.error("Failed to copy magnet URI")}}function K(s){s&&s.destroy(i=>{i&&(m.error("[beacon] Failed to destroy torrent",i),n.error("Failed to remove torrent"));let u=C();d.selectedTorrent=Le(null,u),p?.render(u,d.selectedTorrent),g()})}function Oe(){if(A.info(`[beacon] Starting beacon runtime v${ft}`),n||A.warn("[beacon] Toast service unavailable"),o.WEBRTC_SUPPORT||n?.error("Please use a browser with WebRTC support.",{sticky:!0}),a.form){let l=b=>{b.preventDefault(),x(a.magnetInput?.value||""),a.magnetInput&&(a.magnetInput.value="")};a.form.addEventListener("submit",l),y(()=>a.form.removeEventListener("submit",l))}if(a.seedButton&&a.seedInput){let l=()=>{a.seedInput.click()},b=()=>{let P=Array.from(a.seedInput.files||[]);a.seedInput.value="",H(P)};a.seedButton.addEventListener("click",l),a.seedInput.addEventListener("change",b),y(()=>{a.seedButton.removeEventListener("click",l),a.seedInput.removeEventListener("change",b)})}if(a.pauseButton){let l=()=>{d.selectedTorrent&&typeof d.selectedTorrent.pause=="function"&&(d.selectedTorrent.pause(),g())};a.pauseButton.addEventListener("click",l),y(()=>a.pauseButton.removeEventListener("click",l))}if(a.resumeButton){let l=()=>{d.selectedTorrent&&typeof d.selectedTorrent.resume=="function"&&(d.selectedTorrent.resume(),g())};a.resumeButton.addEventListener("click",l),y(()=>a.resumeButton.removeEventListener("click",l))}if(a.downloadAllButton){let l=()=>te(d.selectedTorrent);a.downloadAllButton.addEventListener("click",l),y(()=>a.downloadAllButton.removeEventListener("click",l))}if(a.removeButton){let l=()=>K(d.selectedTorrent);a.removeButton.addEventListener("click",l),y(()=>a.removeButton.removeEventListener("click",l))}if(a.copyMagnetButton){let l=()=>G(d.selectedTorrent);a.copyMagnetButton.addEventListener("click",l),y(()=>a.copyMagnetButton.removeEventListener("click",l))}let s=l=>{m.error("[beacon] Torrent client error",l),n?.error(l?.message||String(l)),h(),T(!1)};c.on("error",s),y(()=>c.removeListener("error",s));let i=l=>{if(c.torrents&&c.torrents.length>0)return l.preventDefault(),l.returnValue="Transfers are in progress. Are you sure you want to leave or refresh?",l.returnValue};r&&typeof r.addEventListener=="function"&&(r.addEventListener("beforeunload",i),y(()=>r.removeEventListener("beforeunload",i))),p?.render(C(),d.selectedTorrent),g(),v(),T(!1);let u=r.setInterval(()=>{p?.render(C(),d.selectedTorrent),g(),v()},1e3);L.push(u);let f=r?.location?.hash;typeof f=="string"&&f.length>1&&x(f.slice(1))}function Ne(){h(),L.forEach(s=>r.clearInterval(s)),L.length=0,I.forEach(s=>{try{s()}catch(i){A.warn("[beacon] Cleanup task failed",i)}}),I.length=0,p?.destroy();try{c.destroy()}catch(s){A.warn("[beacon] Failed to destroy client",s)}}return{mount:Oe,destroy:Ne}}var Ie="../../config/instance-config.js";function Tt(){return typeof window<"u"&&window||typeof globalThis<"u"&&globalThis||null}function gt(){let e=Tt();if(e&&typeof e.WebTorrent=="function")return e.WebTorrent;throw new Error("WebTorrent runtime is not available on the global scope")}var j=null,_t=async()=>{try{let e=await import(Ie);if(e&&typeof e.THEME_ACCENT_OVERRIDES<"u")return e.THEME_ACCENT_OVERRIDES}catch(e){A.warn(`[beacon] Failed to load theme accent overrides from ${Ie}`,e)}return null},St=async()=>{try{let e=await _t();e&&Ee(e)}catch(e){A.warn("[beacon] Unable to hydrate theme accent overrides",e)}try{ye()}catch(e){A.warn("[beacon] Failed to initialize theme controller",e)}};St();function ve(){if(j)return A.info("[beacon] Reusing existing beacon app instance"),j;if(typeof document>"u")throw new Error("Beacon runtime requires a document environment");let e=gt();return A.info("[beacon] Creating beacon app instance"),j=xe({documentRef:document,WebTorrentCtor:e}),j.mount(),j}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{try{ve()}catch(e){m.error("[beacon] Failed to mount app",e)}});else try{ve()}catch(e){m.error("[beacon] Failed to mount app",e)}export{xe as createBeaconApp,ve as mountBeaconApp};