From cdafed2bc2d4f5dcb4b8c8dd0025b23dda5051de Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Wed, 15 Oct 2025 12:14:15 -0400
Subject: [PATCH 1/4] Refactor app logging to shared logger channels
---
js/app.js | 403 +++++++++++++++++++++++++-----------------------------
1 file changed, 185 insertions(+), 218 deletions(-)
diff --git a/js/app.js b/js/app.js
index 55e47001..090d06e7 100644
--- a/js/app.js
+++ b/js/app.js
@@ -45,6 +45,7 @@ import WatchHistoryTelemetry from "./services/watchHistoryTelemetry.js";
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
import { subscriptions } from "./subscriptions.js";
import { isWatchHistoryDebugEnabled } from "./watchHistoryDebug.js";
+import { devLogger, userLogger } from "./utils/logger.js";
import {
initViewCounter,
subscribeToVideoViewCount,
@@ -210,36 +211,15 @@ class Application {
this.nostrService = services.nostrService || nostrService;
this.r2Service = services.r2Service || r2Service;
- const feedEngineLogger = (...args) => {
- if (!isWatchHistoryDebugEnabled()) {
- return;
- }
- if (!Array.isArray(args) || args.length === 0) {
- return;
- }
- const [prefix, ...rest] = args;
- if (typeof prefix === "string" && !prefix.includes("watch-history")) {
- return;
- }
- try {
- if (rest.length) {
- console.info(prefix, ...rest);
- } else {
- console.info(prefix);
- }
- } catch (error) {
- if (rest.length) {
- console.log(prefix, ...rest);
- } else {
- console.log(prefix);
- }
- }
- };
-
this.feedEngine =
services.feedEngine ||
createFeedEngine({
- logger: feedEngineLogger,
+ logger: (...args) => {
+ if (!isWatchHistoryDebugEnabled()) {
+ return;
+ }
+ devLogger.info(...args);
+ },
});
this.payments = services.payments || null;
this.splitAndZap =
@@ -256,7 +236,7 @@ class Application {
getActivePubkey: () => this.pubkey,
payments: this.payments,
logger: {
- warn: (...args) => console.warn(...args),
+ warn: (...args) => devLogger.warn(...args),
},
notifyError: (message) => this.showError(message),
maxWalletDefaultZap: MAX_WALLET_DEFAULT_ZAP,
@@ -309,7 +289,7 @@ class Application {
this.playbackService =
services.playbackService ||
new PlaybackService({
- logger: (message, ...args) => this.log(message, ...args),
+ logger: devLogger,
torrentClient,
deriveTorrentPlaybackConfig,
isValidMagnetUri,
@@ -346,7 +326,7 @@ class Application {
nostrClient,
userBlocks,
relayManager,
- logger: (message, ...args) => this.log(message, ...args),
+ logger: devLogger,
});
this.authEventUnsubscribes.push(
this.authService.on("auth:login", (detail) => this.handleAuthLogin(detail))
@@ -559,12 +539,12 @@ class Application {
designSystem: this.designSystemContext,
});
} else {
- console.warn(
+ devLogger.warn(
"[Application] Profile modal controller disabled: modal container not found.",
);
}
} catch (error) {
- console.error("Failed to initialize profile modal controller:", error);
+ devLogger.error("Failed to initialize profile modal controller:", error);
}
@@ -712,7 +692,7 @@ class Application {
try {
this.profileController.populateBlockedList();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[profileModal] Failed to refresh blocked list after update:",
error,
);
@@ -884,7 +864,7 @@ class Application {
Promise.resolve(
this.playVideoByEventId(videoId, { url, magnet, trigger })
).catch((error) => {
- console.error("[VideoListView] Failed to play by event id:", error);
+ devLogger.error("[VideoListView] Failed to play by event id:", error);
});
return;
}
@@ -892,7 +872,7 @@ class Application {
this.playVideoWithFallback({ url, magnet, trigger })
).catch(
(error) => {
- console.error("[VideoListView] Failed to start playback:", error);
+ devLogger.error("[VideoListView] Failed to start playback:", error);
}
);
};
@@ -954,7 +934,7 @@ class Application {
this.blacklistedEventIds.add(decoded.data.id);
}
} catch (err) {
- console.error(
+ devLogger.error(
"[Application] Invalid nevent in blacklist:",
neventStr,
err
@@ -1063,7 +1043,7 @@ class Application {
return userBlocks.isBlocked(pubkey);
}
} catch (error) {
- console.warn("[Application] Failed to evaluate block status:", error);
+ devLogger.warn("[Application] Failed to evaluate block status:", error);
}
return false;
@@ -1096,7 +1076,7 @@ class Application {
const uploadModalPromise = this.uploadModal
.load()
.catch((error) => {
- console.error("initUploadModal failed:", error);
+ devLogger.error("initUploadModal failed:", error);
this.showError(`Failed to initialize upload modal: ${error.message}`);
})
.finally(() => {
@@ -1104,7 +1084,7 @@ class Application {
});
const editModalPromise = this.editModal.load().catch((error) => {
- console.error("Failed to load edit modal:", error);
+ devLogger.error("Failed to load edit modal:", error);
this.showError(`Failed to initialize edit modal: ${error.message}`);
});
@@ -1115,7 +1095,7 @@ class Application {
try {
this.renderSavedProfiles();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[profileModal] Failed to render saved profiles after load:",
error,
);
@@ -1124,7 +1104,7 @@ class Application {
try {
this.profileController.refreshWalletPaneState();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[profileModal] Failed to refresh wallet pane after load:",
error,
);
@@ -1132,7 +1112,7 @@ class Application {
return true;
})
.catch((error) => {
- console.error("Failed to load profile modal:", error);
+ devLogger.error("Failed to load profile modal:", error);
return false;
})
: Promise.resolve(false);
@@ -1151,21 +1131,24 @@ class Application {
try {
initViewCounter({ nostrClient });
} catch (error) {
- console.warn("Failed to initialize view counter:", error);
+ devLogger.warn("Failed to initialize view counter:", error);
}
const accessControlPromise = accessControl
.refresh()
.then(() => {
- console.assert(
- !accessControl.lastError ||
- accessControl.lastError?.code !== "nostr-unavailable",
- "[app.init()] Access control refresh should not run before nostrClient.init()",
- accessControl.lastError
- );
+ if (
+ accessControl.lastError &&
+ accessControl.lastError?.code === "nostr-unavailable"
+ ) {
+ devLogger.warn(
+ "[app.init()] Access control refresh should not run before nostrClient.init()",
+ accessControl.lastError
+ );
+ }
})
.catch((error) => {
- console.warn(
+ devLogger.warn(
"Failed to refresh admin lists after connecting to Nostr:",
error
);
@@ -1175,7 +1158,7 @@ class Application {
? Promise.resolve()
.then(() => this.profileController.refreshAdminPaneState())
.catch((error) => {
- console.warn(
+ devLogger.warn(
"Failed to update admin pane after connecting to Nostr:",
error,
);
@@ -1195,7 +1178,7 @@ class Application {
try {
await this.authService.login(savedPubKey, { persistActive: false });
} catch (error) {
- console.error("Auto-login failed:", error);
+ devLogger.error("Auto-login failed:", error);
}
}
@@ -1204,17 +1187,15 @@ class Application {
const watchHistoryInitPromise =
this.watchHistoryTelemetry?.initPreferenceSync?.().catch((error) => {
- if (isDevMode) {
- console.warn(
- "[app.init()] Failed to initialize watch history metadata sync:",
- error
- );
- }
+ devLogger.warn(
+ "[app.init()] Failed to initialize watch history metadata sync:",
+ error
+ );
}) || Promise.resolve();
// 6) Load the default view ONLY if there's no #view= already
if (!window.location.hash || !window.location.hash.startsWith("#view=")) {
- console.log(
+ devLogger.log(
"[app.init()] No #view= in the URL, loading default home view"
);
if (typeof this.loadView === "function") {
@@ -1226,7 +1207,7 @@ class Application {
await watchHistoryInitPromise;
}
} else {
- console.log(
+ devLogger.log(
"[app.init()] Found hash:",
window.location.hash,
"so skipping default load"
@@ -1244,7 +1225,7 @@ class Application {
this.checkUrlParams();
} catch (error) {
- console.error("Init failed:", error);
+ devLogger.error("Init failed:", error);
this.showError("Failed to connect to Nostr relay");
}
}
@@ -1259,7 +1240,7 @@ class Application {
// Switch to channel profile view
window.location.hash = `#view=channel-profile&npub=${npub}`;
} catch (err) {
- console.error("Failed to go to channel:", err);
+ devLogger.error("Failed to go to channel:", err);
this.showError("Could not open channel.");
}
}
@@ -1280,7 +1261,7 @@ class Application {
// Switch to channel profile view
window.location.hash = `#view=channel-profile&npub=${npub}`;
} catch (err) {
- console.error("Failed to open creator channel:", err);
+ devLogger.error("Failed to open creator channel:", err);
this.showError("Could not open channel.");
}
}
@@ -1361,7 +1342,7 @@ class Application {
: null)
);
} catch (error) {
- console.error(
+ devLogger.error(
"[Application] Failed to open the video modal before playback:",
error
);
@@ -1517,7 +1498,7 @@ class Application {
try {
this.modalViewCountUnsub();
} catch (error) {
- console.warn("[viewCount] Failed to tear down modal subscription:", error);
+ devLogger.warn("[viewCount] Failed to tear down modal subscription:", error);
}
}
this.modalViewCountUnsub = null;
@@ -1575,7 +1556,7 @@ class Application {
try {
unsubscribeFromVideoViewCount(pointer, token);
} catch (error) {
- console.warn(
+ devLogger.warn(
"[viewCount] Failed to unsubscribe modal view counter:",
error
);
@@ -1584,7 +1565,7 @@ class Application {
}
};
} catch (error) {
- console.warn("[viewCount] Failed to subscribe modal view counter:", error);
+ devLogger.warn("[viewCount] Failed to subscribe modal view counter:", error);
if (this.videoModal) {
this.videoModal.updateViewCountLabel("– views");
this.videoModal.setViewCountPointer(null);
@@ -1612,7 +1593,7 @@ class Application {
});
}
} catch (error) {
- console.warn(
+ devLogger.warn(
"[profileModal] Failed to synchronize saved profiles with controller:",
error,
);
@@ -1628,7 +1609,7 @@ class Application {
);
}
} catch (error) {
- console.warn(
+ devLogger.warn(
"[profileModal] Failed to synchronize active profile with controller:",
error,
);
@@ -1644,7 +1625,7 @@ class Application {
try {
this.profileController.renderSavedProfiles();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[profileModal] Failed to render saved profiles:",
error,
);
@@ -1783,7 +1764,7 @@ class Application {
this.showSuccess("Profile added. Select it when you're ready to switch.");
} catch (error) {
- console.error("Failed to add profile via NIP-07:", error);
+ devLogger.error("Failed to add profile via NIP-07:", error);
const message =
error && typeof error.message === "string" && error.message.trim()
? error.message.trim()
@@ -1802,7 +1783,7 @@ class Application {
try {
return accessControl.canEditAdminLists(actorNpub);
} catch (error) {
- console.warn("Unable to verify blacklist permissions:", error);
+ devLogger.warn("Unable to verify blacklist permissions:", error);
return false;
}
}
@@ -1862,12 +1843,12 @@ class Application {
try {
await this.profileController.refreshAdminPaneState();
} catch (error) {
- console.error("Failed to refresh admin pane after update:", error);
+ devLogger.error("Failed to refresh admin pane after update:", error);
}
}
this.loadVideos(true).catch((error) => {
- console.error("Failed to refresh videos after admin update:", error);
+ devLogger.error("Failed to refresh videos after admin update:", error);
});
window.dispatchEvent(new CustomEvent("bitvid:access-control-updated"));
}
@@ -1877,7 +1858,7 @@ class Application {
await this.loadVideos(true);
} catch (error) {
const context = reason ? ` after ${reason}` : "";
- console.error(`Failed to refresh videos${context}:`, error);
+ devLogger.error(`Failed to refresh videos${context}:`, error);
}
}
@@ -1891,7 +1872,7 @@ class Application {
try {
await this.authService.logout();
} catch (error) {
- console.error("Logout failed:", error);
+ devLogger.error("Logout failed:", error);
this.showError("Failed to logout. Please try again.");
}
});
@@ -1907,7 +1888,7 @@ class Application {
this.profileController
.show()
.catch((error) => {
- console.error("Failed to open profile modal:", error);
+ devLogger.error("Failed to open profile modal:", error);
});
});
}
@@ -1925,7 +1906,7 @@ class Application {
// 4) Login button => show the login modal
if (this.loginButton) {
this.loginButton.addEventListener("click", (event) => {
- console.log("Login button clicked!");
+ devLogger.log("Login button clicked!");
const loginModal =
prepareStaticModal({ id: "loginModal" }) || document.getElementById("loginModal");
const trigger = event?.currentTarget || event?.target || null;
@@ -1938,7 +1919,7 @@ class Application {
// 5) Close login modal button => hide modal
if (this.closeLoginModalBtn) {
this.closeLoginModalBtn.addEventListener("click", () => {
- console.log("[app.js] closeLoginModal button clicked!");
+ devLogger.log("[app.js] closeLoginModal button clicked!");
if (closeStaticModal("loginModal")) {
setGlobalModalState("login", false);
}
@@ -1987,12 +1968,12 @@ class Application {
}
setLoadingState(true);
- console.log(
+ devLogger.log(
"[app.js] loginNIP07 clicked! Attempting extension login..."
);
try {
const { pubkey, detail } = await this.authService.requestLogin();
- console.log("[NIP-07] login returned pubkey:", pubkey);
+ devLogger.log("[NIP-07] login returned pubkey:", pubkey);
if (pubkey) {
if (
@@ -2003,7 +1984,7 @@ class Application {
try {
await this.handleAuthLogin(detail);
} catch (error) {
- console.error(
+ devLogger.error(
"[NIP-07] handleAuthLogin fallback failed:",
error,
);
@@ -2015,7 +1996,7 @@ class Application {
}
}
} catch (err) {
- console.error("[NIP-07 login error]", err);
+ devLogger.error("[NIP-07 login error]", err);
const message =
err && typeof err.message === "string" && err.message.trim()
? err.message.trim()
@@ -2030,12 +2011,10 @@ class Application {
// 7) Cleanup on page unload
window.addEventListener("beforeunload", () => {
this.flushWatchHistory("session-end", "beforeunload").catch((error) => {
- if (isDevMode) {
- console.warn("[beforeunload] Watch history flush failed:", error);
- }
+ devLogger.warn("[beforeunload] Watch history flush failed:", error);
});
this.cleanup().catch((err) => {
- console.error("Cleanup before unload failed:", err);
+ devLogger.error("Cleanup before unload failed:", err);
});
});
@@ -2043,12 +2022,10 @@ class Application {
if (document.visibilityState === "hidden") {
this.flushWatchHistory("session-end", "visibilitychange").catch(
(error) => {
- if (isDevMode) {
- console.warn(
- "[visibilitychange] Watch history flush failed:",
- error
- );
- }
+ devLogger.warn(
+ "[visibilitychange] Watch history flush failed:",
+ error
+ );
}
);
}
@@ -2056,7 +2033,7 @@ class Application {
// 8) Handle back/forward navigation => hide video modal
window.addEventListener("popstate", async () => {
- console.log("[popstate] user navigated back/forward; cleaning modal...");
+ devLogger.log("[popstate] user navigated back/forward; cleaning modal...");
await this.hideModal();
});
@@ -2105,7 +2082,7 @@ class Application {
try {
this.videoListView.destroy();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[Application] Failed to destroy VideoListView during reinitialization:",
error,
);
@@ -2190,11 +2167,11 @@ class Application {
this.setProfileCacheEntry(pubkey, profile);
this.updateProfileInDOM(pubkey, profile);
} catch (err) {
- console.error("Profile parse error:", err);
+ devLogger.error("Profile parse error:", err);
}
}
} catch (err) {
- console.error("Batch profile fetch error:", err);
+ devLogger.error("Batch profile fetch error:", err);
}
}
@@ -2586,7 +2563,7 @@ class Application {
this.showSuccess("Video shared successfully!");
return true;
} catch (err) {
- console.error("Failed to publish video:", err);
+ devLogger.error("Failed to publish video:", err);
this.showError("Failed to share video. Please try again later.");
return false;
}
@@ -2629,7 +2606,7 @@ class Application {
try {
await this.loadVideos(true);
} catch (error) {
- console.error("Failed to refresh videos after switching profiles:", error);
+ devLogger.error("Failed to refresh videos after switching profiles:", error);
}
}
@@ -2710,12 +2687,10 @@ class Application {
try {
await this.onVideosShouldRefresh({ reason: refreshReason });
} catch (refreshError) {
- if (isDevMode) {
- console.warn(
- "[Profile] Failed to refresh videos after relay update:",
- refreshError,
- );
- }
+ devLogger.warn(
+ "[Profile] Failed to refresh videos after relay update:",
+ refreshError,
+ );
}
return context;
@@ -2727,12 +2702,10 @@ class Application {
relayManager.setEntries(previous, { allowEmpty: false });
}
} catch (restoreError) {
- if (isDevMode) {
- console.warn(
- "[Profile] Failed to restore relay preferences after publish error:",
- restoreError,
- );
- }
+ devLogger.warn(
+ "[Profile] Failed to restore relay preferences after publish error:",
+ restoreError,
+ );
}
return context;
}
@@ -2787,7 +2760,7 @@ class Application {
try {
await this.onVideosShouldRefresh({ reason: `blocklist-${action}` });
} catch (refreshError) {
- console.error(
+ devLogger.error(
"Failed to refresh videos after blocklist mutation:",
refreshError,
);
@@ -2884,7 +2857,7 @@ class Application {
if (!error) {
return;
}
- console.warn("[admin] Notification dispatch issue:", error);
+ devLogger.warn("[admin] Notification dispatch issue:", error);
}
handleProfileHistoryEvent() {
@@ -3054,7 +3027,7 @@ class Application {
const nwcPromise = Promise.resolve()
.then(() => this.nwcSettingsService.onLogin(loginContext))
.catch((error) => {
- console.error("Failed to process NWC settings during login:", error);
+ devLogger.error("Failed to process NWC settings during login:", error);
return null;
});
@@ -3063,14 +3036,14 @@ class Application {
const maybePromise = this.profileController.handleAuthLogin(detail);
if (maybePromise && typeof maybePromise.then === "function") {
maybePromise.catch((error) => {
- console.error(
+ devLogger.error(
"Failed to process login within the profile controller:",
error,
);
});
}
} catch (error) {
- console.error(
+ devLogger.error(
"Failed to process login within the profile controller:",
error,
);
@@ -3088,14 +3061,14 @@ class Application {
this.forceRefreshAllProfiles();
})
.catch((error) => {
- console.error("Post-login hydration failed:", error);
+ devLogger.error("Post-login hydration failed:", error);
});
let postLoginResult = null;
try {
postLoginResult = await postLoginPromise;
} catch (error) {
- console.error("Post-login processing failed:", error);
+ devLogger.error("Post-login processing failed:", error);
}
await nwcPromise;
@@ -3103,13 +3076,13 @@ class Application {
try {
this.reinitializeVideoListView({ reason: "login", postLoginResult });
} catch (error) {
- console.warn("Failed to reinitialize video list view after login:", error);
+ devLogger.warn("Failed to reinitialize video list view after login:", error);
}
try {
await this.loadVideos(true);
} catch (error) {
- console.error("Failed to refresh videos after login:", error);
+ devLogger.error("Failed to refresh videos after login:", error);
}
this.forceRefreshAllProfiles();
@@ -3131,7 +3104,7 @@ class Application {
try {
await this.profileController.handleAuthLogout(detail);
} catch (error) {
- console.error(
+ devLogger.error(
"Failed to process logout within the profile controller:",
error,
);
@@ -3145,7 +3118,7 @@ class Application {
try {
await this.loadVideos(true);
} catch (error) {
- console.error("Failed to refresh videos after logout:", error);
+ devLogger.error("Failed to refresh videos after logout:", error);
}
this.forceRefreshAllProfiles();
if (this.uploadModal?.refreshCloudflareBucketPreview) {
@@ -3190,7 +3163,7 @@ class Application {
try {
await this.cleanupPromise;
} catch (err) {
- console.warn("Previous cleanup rejected:", err);
+ devLogger.warn("Previous cleanup rejected:", err);
}
}
@@ -3241,7 +3214,7 @@ class Application {
try {
this.videoModal.clearPosterCleanup();
} catch (err) {
- console.warn("[cleanup] video modal poster cleanup threw:", err);
+ devLogger.warn("[cleanup] video modal poster cleanup threw:", err);
}
}
@@ -3259,7 +3232,7 @@ class Application {
try {
this.videoModal.setVideoElement(refreshedModal);
} catch (err) {
- console.warn(
+ devLogger.warn(
"[cleanup] Failed to sync video modal element after replacement:",
err
);
@@ -3271,7 +3244,7 @@ class Application {
await torrentClient.cleanup();
this.log("[cleanup] WebTorrent cleanup resolved.");
} catch (err) {
- console.error("Cleanup error:", err);
+ devLogger.error("Cleanup error:", err);
} finally {
this.log("[cleanup] Finished.");
}
@@ -3299,7 +3272,7 @@ class Application {
await this.cleanupPromise;
this.log("[waitForCleanup] Previous cleanup completed.");
} catch (err) {
- console.warn("waitForCleanup observed a rejected cleanup:", err);
+ devLogger.warn("waitForCleanup observed a rejected cleanup:", err);
}
}
@@ -3349,13 +3322,11 @@ class Application {
try {
watchHistoryService.setLocalMetadata(pointerInfo.key, metadata);
} catch (error) {
- if (isDevMode) {
- console.warn(
- "[watchHistory] Failed to persist local metadata for pointer:",
- pointerInfo.key,
- error,
- );
- }
+ devLogger.warn(
+ "[watchHistory] Failed to persist local metadata for pointer:",
+ pointerInfo.key,
+ error,
+ );
}
}
@@ -3374,13 +3345,11 @@ class Application {
try {
watchHistoryService.removeLocalMetadata(pointerKey);
} catch (error) {
- if (isDevMode) {
- console.warn(
- "[watchHistory] Failed to remove cached metadata for pointer:",
- pointerKey,
- error,
- );
- }
+ devLogger.warn(
+ "[watchHistory] Failed to remove cached metadata for pointer:",
+ pointerKey,
+ error,
+ );
}
}
@@ -3518,7 +3487,7 @@ class Application {
try {
fn();
} catch (err) {
- console.warn("[teardownVideoElement]", err);
+ devLogger.warn("[teardownVideoElement]", err);
}
};
@@ -3666,7 +3635,7 @@ class Application {
return base;
}
}
- console.warn("Unable to determine share URL base:", err);
+ devLogger.warn("Unable to determine share URL base:", err);
return "";
}
}
@@ -3691,7 +3660,7 @@ class Application {
const nevent = window.NostrTools.nip19.neventEncode({ id: eventId });
return this.buildShareUrlFromNevent(nevent);
} catch (err) {
- console.error("Error generating nevent for share URL:", err);
+ devLogger.error("Error generating nevent for share URL:", err);
return "";
}
}
@@ -3794,9 +3763,7 @@ class Application {
modalVideoElement.removeAttribute("src");
modalVideoElement.load();
} catch (error) {
- if (isDevMode) {
- console.warn("[hideModal] Failed to reset modal video element:", error);
- }
+ devLogger.warn("[hideModal] Failed to reset modal video element:", error);
}
}
@@ -3804,7 +3771,7 @@ class Application {
try {
this.videoModal.close();
} catch (error) {
- console.warn("[hideModal] Failed to close video modal immediately:", error);
+ devLogger.warn("[hideModal] Failed to close video modal immediately:", error);
}
}
@@ -3819,9 +3786,7 @@ class Application {
try {
await fetch("/webtorrent/cancel/", { mode: "no-cors" });
} catch (err) {
- if (isDevMode) {
- console.warn("[hideModal] webtorrent cancel fetch failed:", err);
- }
+ devLogger.warn("[hideModal] webtorrent cancel fetch failed:", err);
}
await this.cleanup({
@@ -3839,7 +3804,7 @@ class Application {
try {
await performCleanup();
} catch (error) {
- console.error("[hideModal] Cleanup failed:", error);
+ devLogger.error("[hideModal] Cleanup failed:", error);
}
}
@@ -3874,7 +3839,7 @@ class Application {
sorter: createChronologicalSorter(),
});
} catch (error) {
- console.warn("[Application] Failed to register recent feed:", error);
+ devLogger.warn("[Application] Failed to register recent feed:", error);
return null;
}
}
@@ -3912,7 +3877,7 @@ class Application {
},
});
} catch (error) {
- console.warn("[Application] Failed to register subscriptions feed:", error);
+ devLogger.warn("[Application] Failed to register subscriptions feed:", error);
return null;
}
}
@@ -3936,7 +3901,7 @@ class Application {
nostr: this.nostrService,
});
} catch (error) {
- console.warn("[Application] Failed to register watch history feed:", error);
+ devLogger.warn("[Application] Failed to register watch history feed:", error);
return null;
}
}
@@ -3994,7 +3959,7 @@ class Application {
return payload;
})
.catch((error) => {
- console.error("[Application] Failed to run recent feed:", error);
+ devLogger.error("[Application] Failed to run recent feed:", error);
const metadata = {
reason: normalizedReason || "error:recent-feed",
error: true,
@@ -4014,7 +3979,7 @@ class Application {
* Subscribe to videos (older + new) and render them as they come in.
*/
async loadVideos(forceFetch = false) {
- console.log("Starting loadVideos... (forceFetch =", forceFetch, ")");
+ devLogger.log("Starting loadVideos... (forceFetch =", forceFetch, ")");
const container = this.mountVideoListView();
if (this.videoListView && container) {
@@ -4252,7 +4217,7 @@ class Application {
}
})
.catch((err) => {
- console.warn(
+ devLogger.warn(
`[urlHealth] cached probe promise rejected for ${trimmedUrl}:`,
err
);
@@ -4292,7 +4257,7 @@ class Application {
return this.storeUrlHealth(eventId, trimmedUrl, entry, ttlOverride);
})
.catch((err) => {
- console.warn(`[urlHealth] probe failed for ${trimmedUrl}:`, err);
+ devLogger.warn(`[urlHealth] probe failed for ${trimmedUrl}:`, err);
const entry = {
status: "offline",
message: "❌ CDN",
@@ -4309,7 +4274,7 @@ class Application {
}
})
.catch((err) => {
- console.warn(
+ devLogger.warn(
`[urlHealth] probe promise rejected post-cache for ${trimmedUrl}:`,
err
);
@@ -4694,9 +4659,7 @@ class Application {
this.showSuccess(fragments.join(" ").trim());
} catch (error) {
- if (isDevMode) {
- console.warn("[app] Repost action failed:", error);
- }
+ devLogger.warn("[app] Repost action failed:", error);
this.showError("Failed to repost the video. Please try again later.");
}
}
@@ -4837,9 +4800,7 @@ class Application {
this.showSuccess(fragments.join(" ").trim());
} catch (error) {
- if (isDevMode) {
- console.warn("[app] Mirror action failed:", error);
- }
+ devLogger.warn("[app] Mirror action failed:", error);
this.showError("Failed to mirror the video. Please try again later.");
}
}
@@ -4920,9 +4881,7 @@ class Application {
this.showSuccess("Rebroadcast requested across relays.");
} catch (error) {
- if (isDevMode) {
- console.warn("[app] Rebroadcast action failed:", error);
- }
+ devLogger.warn("[app] Rebroadcast action failed:", error);
this.showError("Failed to rebroadcast. Please try again later.");
}
}
@@ -4933,10 +4892,10 @@ class Application {
* and do not re-trigger recursion here (no setTimeout).
*/
updateTorrentStatus(torrent) {
- console.log("[DEBUG] updateTorrentStatus called with torrent:", torrent);
+ devLogger.log("[DEBUG] updateTorrentStatus called with torrent:", torrent);
if (!torrent) {
- console.log("[DEBUG] torrent is null/undefined!");
+ devLogger.log("[DEBUG] torrent is null/undefined!");
return;
}
@@ -4950,12 +4909,12 @@ class Application {
}
// Log only fields that actually exist on the torrent:
- console.log("[DEBUG] torrent.progress =", torrent.progress);
- console.log("[DEBUG] torrent.numPeers =", torrent.numPeers);
- console.log("[DEBUG] torrent.downloadSpeed =", torrent.downloadSpeed);
- console.log("[DEBUG] torrent.downloaded =", torrent.downloaded);
- console.log("[DEBUG] torrent.length =", torrent.length);
- console.log("[DEBUG] torrent.ready =", torrent.ready);
+ devLogger.log("[DEBUG] torrent.progress =", torrent.progress);
+ devLogger.log("[DEBUG] torrent.numPeers =", torrent.numPeers);
+ devLogger.log("[DEBUG] torrent.downloadSpeed =", torrent.downloadSpeed);
+ devLogger.log("[DEBUG] torrent.downloaded =", torrent.downloaded);
+ devLogger.log("[DEBUG] torrent.length =", torrent.length);
+ devLogger.log("[DEBUG] torrent.ready =", torrent.ready);
// Use "Complete" vs. "Downloading" as the textual status.
if (this.videoModal) {
@@ -5137,7 +5096,7 @@ class Application {
this.editModal.close();
this.forceRefreshAllProfiles();
} catch (error) {
- console.error("Failed to edit video:", error);
+ devLogger.error("Failed to edit video:", error);
this.showError("Failed to edit video. Please try again.");
if (this.editModal?.setSubmitState) {
this.editModal.setSubmitState({ pending: false });
@@ -5173,7 +5132,7 @@ class Application {
try {
await this.editModal.load();
} catch (error) {
- console.error("Failed to load edit modal:", error);
+ devLogger.error("Failed to load edit modal:", error);
this.showError(`Failed to initialize edit modal: ${error.message}`);
return;
}
@@ -5181,11 +5140,11 @@ class Application {
try {
await this.editModal.open(video, { triggerElement });
} catch (error) {
- console.error("Failed to open edit modal:", error);
+ devLogger.error("Failed to open edit modal:", error);
this.showError("Edit modal is not available right now.");
}
} catch (err) {
- console.error("Failed to edit video:", err);
+ devLogger.error("Failed to edit video:", err);
this.showError("Failed to edit video. Please try again.");
}
}
@@ -5230,7 +5189,7 @@ class Application {
this.revertModal.setHistory(video, history);
this.revertModal.open({ video }, { triggerElement });
} catch (err) {
- console.error("Failed to revert video:", err);
+ devLogger.error("Failed to revert video:", err);
this.showError("Failed to load revision history. Please try again.");
}
}
@@ -5277,7 +5236,7 @@ class Application {
this.revertModal.close();
this.forceRefreshAllProfiles();
} catch (err) {
- console.error("Failed to revert video:", err);
+ devLogger.error("Failed to revert video:", err);
this.showError("Failed to revert video. Please try again.");
} finally {
this.revertModal.setBusy(false);
@@ -5332,7 +5291,7 @@ class Application {
this.showSuccess("All versions deleted successfully!");
this.forceRefreshAllProfiles();
} catch (err) {
- console.error("Failed to delete all versions:", err);
+ devLogger.error("Failed to delete all versions:", err);
this.showError("Failed to delete all versions. Please try again.");
}
}
@@ -5364,13 +5323,13 @@ class Application {
}
})
.catch((err) => {
- console.error("Error fetching older event by ID:", err);
+ devLogger.error("Error fetching older event by ID:", err);
this.showError("Could not load videos for the share link.");
});
}
}
} catch (err) {
- console.error("Error decoding nevent:", err);
+ devLogger.error("Error decoding nevent:", err);
this.showError("Invalid share link.");
}
}
@@ -5470,7 +5429,7 @@ class Application {
return result;
}
} catch (err) {
- console.warn(
+ devLogger.warn(
`[probeUrl] Video element probe threw for ${trimmed}:`,
err
);
@@ -5534,7 +5493,7 @@ class Application {
if (timeoutId) {
clearTimeout(timeoutId);
}
- console.warn(`[probeUrl] HEAD request failed for ${trimmed}:`, err);
+ devLogger.warn(`[probeUrl] HEAD request failed for ${trimmed}:`, err);
const fallback = await confirmWithVideoElement();
if (fallback) {
return fallback;
@@ -5614,7 +5573,7 @@ class Application {
await target.play();
return true;
} catch (err) {
- console.warn("[playHttp] Direct URL playback failed:", err);
+ devLogger.warn("[playHttp] Direct URL playback failed:", err);
return false;
}
}
@@ -5786,7 +5745,7 @@ class Application {
try {
this.videoModal.clearPosterCleanup();
} catch (err) {
- console.warn(
+ devLogger.warn(
"[playVideoWithFallback] video modal poster cleanup threw:",
err
);
@@ -5900,7 +5859,7 @@ class Application {
try {
off();
} catch (err) {
- console.warn(
+ devLogger.warn(
"[playVideoWithFallback] Listener cleanup error:",
err
);
@@ -5980,7 +5939,7 @@ class Application {
try {
await accessControl.ensureReady();
} catch (error) {
- console.warn(
+ devLogger.warn(
"Failed to ensure admin lists were loaded before playback:",
error
);
@@ -6266,12 +6225,10 @@ class Application {
try {
nostrClient.applyRootCreatedAt(video);
} catch (error) {
- if (isDevMode) {
- console.warn(
- "[Application] Failed to sync cached root timestamp with nostrClient:",
- error
- );
- }
+ devLogger.warn(
+ "[Application] Failed to sync cached root timestamp with nostrClient:",
+ error
+ );
}
}
}
@@ -6328,12 +6285,10 @@ class Application {
}
}
} catch (error) {
- if (isDevMode) {
- console.warn(
- "[Application] Failed to hydrate video history for timestamps:",
- error
- );
- }
+ devLogger.warn(
+ "[Application] Failed to hydrate video history for timestamps:",
+ error
+ );
}
const fallback = Number.isFinite(video.created_at)
@@ -6571,6 +6526,8 @@ class Application {
this.errorContainer.textContent = msg;
this.errorContainer.classList.remove("hidden");
+ userLogger.error(msg);
+
// Optional auto-hide after 5 seconds
setTimeout(() => {
this.errorContainer.textContent = "";
@@ -6615,8 +6572,18 @@ class Application {
}, 5000);
}
- log(msg) {
- console.log(msg);
+ log(message, ...args) {
+ if (arguments.length === 0) {
+ return;
+ }
+
+ if (typeof message === "string") {
+ const prefix = message.startsWith("[") ? message : `[app] ${message}`;
+ devLogger.log(prefix, ...args);
+ return;
+ }
+
+ devLogger.log("[app]", message, ...args);
}
destroy() {
@@ -6628,7 +6595,7 @@ class Application {
try {
this.watchHistoryTelemetry.destroy();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[Application] Failed to destroy watch history telemetry:",
error
);
@@ -6640,7 +6607,7 @@ class Application {
try {
this.unsubscribeFromPubkeyState();
} catch (error) {
- console.warn("[Application] Failed to unsubscribe pubkey state:", error);
+ devLogger.warn("[Application] Failed to unsubscribe pubkey state:", error);
}
this.unsubscribeFromPubkeyState = null;
}
@@ -6649,7 +6616,7 @@ class Application {
try {
this.unsubscribeFromCurrentUserState();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[Application] Failed to unsubscribe current user state:",
error
);
@@ -6681,7 +6648,7 @@ class Application {
try {
unsubscribe();
} catch (error) {
- console.warn("[Application] Auth listener unsubscribe failed:", error);
+ devLogger.warn("[Application] Auth listener unsubscribe failed:", error);
}
}
});
@@ -6691,7 +6658,7 @@ class Application {
try {
this.unsubscribeFromNostrService();
} catch (error) {
- console.warn("[Application] Failed to unsubscribe nostr service:", error);
+ devLogger.warn("[Application] Failed to unsubscribe nostr service:", error);
}
this.unsubscribeFromNostrService = null;
}
@@ -6707,7 +6674,7 @@ class Application {
try {
this.uploadModal.destroy();
} catch (error) {
- console.warn("[Application] Failed to destroy upload modal:", error);
+ devLogger.warn("[Application] Failed to destroy upload modal:", error);
}
}
@@ -6730,7 +6697,7 @@ class Application {
try {
this.editModal.destroy();
} catch (error) {
- console.warn("[Application] Failed to destroy edit modal:", error);
+ devLogger.warn("[Application] Failed to destroy edit modal:", error);
}
}
}
@@ -6746,7 +6713,7 @@ class Application {
try {
this.revertModal.destroy();
} catch (error) {
- console.warn("[Application] Failed to destroy revert modal:", error);
+ devLogger.warn("[Application] Failed to destroy revert modal:", error);
}
}
@@ -6825,7 +6792,7 @@ class Application {
try {
this.videoModal.destroy();
} catch (error) {
- console.warn("[Application] Failed to destroy video modal:", error);
+ devLogger.warn("[Application] Failed to destroy video modal:", error);
}
}
}
@@ -6845,7 +6812,7 @@ class Application {
try {
this.videoListView.destroy();
} catch (error) {
- console.warn("[Application] Failed to destroy VideoListView:", error);
+ devLogger.warn("[Application] Failed to destroy VideoListView:", error);
}
this.videoListView = null;
}
@@ -6861,7 +6828,7 @@ class Application {
try {
this.profileController.destroy();
} catch (error) {
- console.warn(
+ devLogger.warn(
"[Application] Failed to destroy profile controller:",
error,
);
@@ -6912,7 +6879,7 @@ class Application {
navigator.clipboard.writeText(this.currentVideo.magnet);
this.showSuccess("Magnet link copied to clipboard!");
} catch (err) {
- console.error("Failed to copy magnet link:", err);
+ devLogger.error("Failed to copy magnet link:", err);
this.showError("Could not copy magnet link. Please copy it manually.");
}
}
From f91c3dd8ed2910653e9cc53c993d772e719635c1 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Wed, 15 Oct 2025 12:33:31 -0400
Subject: [PATCH 2/4] Fix logger exports to avoid redeclaration
---
js/utils/logger.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/js/utils/logger.js b/js/utils/logger.js
index 9cad91cc..a819cd14 100644
--- a/js/utils/logger.js
+++ b/js/utils/logger.js
@@ -158,5 +158,4 @@ if (typeof window !== "undefined") {
Object.freeze(logger);
export default logger;
-export const devLogger = logger.dev;
-export const userLogger = logger.user;
+export { devLogger, userLogger };
From e1535e1d68d5680fd6a8c7a38c5b13ac4b5e80c1 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Wed, 15 Oct 2025 12:49:46 -0400
Subject: [PATCH 3/4] Ensure Beacon loads WebTorrent module
---
js/webtorrent-global.js | 16 ++++++++++++++++
torrent/beacon.html | 2 +-
torrent/dist/beacon.js | 2 +-
torrent/src/beacon-vendor.js | 11 ++++++++---
4 files changed, 26 insertions(+), 5 deletions(-)
create mode 100644 js/webtorrent-global.js
diff --git a/js/webtorrent-global.js b/js/webtorrent-global.js
new file mode 100644
index 00000000..0a58bfa1
--- /dev/null
+++ b/js/webtorrent-global.js
@@ -0,0 +1,16 @@
+import WebTorrent from "./webtorrent.min.js";
+
+const globalScope =
+ (typeof window !== "undefined" && window) ||
+ (typeof globalThis !== "undefined" && globalThis) ||
+ null;
+
+if (
+ globalScope &&
+ typeof globalScope.WebTorrent !== "function" &&
+ typeof WebTorrent === "function"
+) {
+ globalScope.WebTorrent = WebTorrent;
+}
+
+export {};
diff --git a/torrent/beacon.html b/torrent/beacon.html
index 77178107..ed01d346 100644
--- a/torrent/beacon.html
+++ b/torrent/beacon.html
@@ -16,7 +16,7 @@
-
+
diff --git a/torrent/dist/beacon.js b/torrent/dist/beacon.js
index 7a36c52d..8686d644 100644
--- a/torrent/dist/beacon.js
+++ b/torrent/dist/beacon.js
@@ -1 +1 @@
-var O={info:"torrent-toast torrent-toast--info",success:"torrent-toast torrent-toast--success",error:"torrent-toast torrent-toast--error",warn:"torrent-toast torrent-toast--warn"},V="transition duration-200 ease-out",Y="beacon-toast-motion",q="data-beacon-motion";function J(e,t){let a=e?.defaultView;if(a&&typeof a.requestAnimationFrame=="function"){a.requestAnimationFrame(t);return}setTimeout(t,16)}function Q(e){let t=e||(typeof document<"u"?document:null);if(!t)return null;let a=t.getElementById("beacon-toast-container");if(!a){a=t.createElement("div"),a.id="beacon-toast-container",a.setAttribute("role","region"),a.setAttribute("aria-live","polite"),a.className=["pointer-events-none","fixed","inset-x-0","top-4","z-[70]","flex","flex-col","items-center","gap-3","px-4"].join(" ");let i=t.body||t.getElementsByTagName?.("body")?.[0];i&&i.appendChild(a)}return a}function X(e,t,a){let i=e.createElement("div");i.className=[V,a].join(" "),i.setAttribute("role","status"),i.setAttribute("tabindex","0"),i.textContent=t;let o=e.createElement("button");o.type="button",o.className=["absolute","right-3","top-3","rounded","p-1","text-current/70","hover:text-current","focus-visible:outline","focus-visible:outline-2","focus-visible:outline-offset-2","focus-visible:outline-info"].join(" "),o.setAttribute("aria-label","Dismiss notification"),o.innerHTML='';let d=e.createElement("div");return d.className=["relative","w-full",V,Y].join(" "),d.setAttribute(q,"exit"),d.appendChild(i),i.appendChild(o),{wrapper:d,toast:i,closeButton:o}}function Z(e,t=setTimeout,a=clearTimeout){return{show(i,o={}){if(!i)return;let d=o.type&&O[o.type]?o.type:"info",n=Q(e);if(!n)return;let{wrapper:m,toast:c,closeButton:T}=X(e,i,O[d]);n.appendChild(m),J(e,()=>{m.setAttribute(q,"enter")});let w=typeof o.duration=="number"?o.duration:3500,p=()=>{m.setAttribute(q,"exit");let S=t(()=>{m.parentNode&&m.parentNode.removeChild(m)},200);return T.removeEventListener("click",E),c.removeEventListener("keydown",h),T.removeEventListener("keydown",h),S},E=()=>p(),h=S=>{S.key==="Escape"&&(S.stopPropagation(),p())};T.addEventListener("click",E),c.addEventListener("keydown",h),T.addEventListener("keydown",h);let N=null;return o.sticky||(N=t(()=>{p()},w)),{dismiss(){N&&a(N),p()}}},set(i,o){return typeof o=="string"?this.show(i,{type:o}):this.show(i,o)},info(i,o={}){return this.show(i,{...o,type:"info"})},success(i,o={}){return this.show(i,{...o,type:"success"})},error(i,o={}){return this.show(i,{...o,type:"error"})},warn(i,o={}){return this.show(i,{...o,type:"warn"})}}}function R(e,t=setTimeout,a=clearTimeout){return Z(e,t,a)}var $=[{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 ee(e){return e?e.infoHash||e.magnetURI||e.name||String(Math.random()):""}function te(e,t){return!e||!t?!1:e===t||e.magnetURI&&e.magnetURI===t.magnetURI||e.infoHash&&e.infoHash===t.infoHash}function ne(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function re(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function oe(e){return typeof e!="number"||!Number.isFinite(e)?"0":Math.max(0,Math.floor(e)).toString()}function ae(e,t){if(typeof t=="function")return t(e,!0)||"";if(typeof e!="number"||Number.isNaN(e)||e<1)return"";let a=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),a.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${a[i]}/s`}function se(e,t){let a=e.formatter;if(typeof a=="function")return a;let i=t?.[a];if(typeof i=="function")return i;switch(a){case"progress":return ne;case"ratio":return re;case"integer":return oe;case"bytesPerSecond":return o=>ae(o,t?.bytes);default:return o=>o}}function W({documentRef:e=typeof document<"u"?document:null,root:t,onSelect:a,formatters:i={}}={}){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 o=e;t.innerHTML="";let d=o.createElement("div");d.className="overflow-hidden rounded-xl border border-border-translucent bg-surface shadow-sm";let n=o.createElement("div");n.className="overflow-x-auto",d.appendChild(n);let m=o.createElement("table");m.className="min-w-full divide-y divide-border-translucent text-sm",n.appendChild(m);let c=o.createElement("thead");c.className="bg-surface-alt text-muted-strong";let T=o.createElement("tr");$.forEach(y=>{let x=o.createElement("th");x.setAttribute("scope","col"),x.className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide",y.headerClass&&(x.className+=` ${y.headerClass}`),x.textContent=y.label,T.appendChild(x)}),c.appendChild(T),m.appendChild(c);let w=o.createElement("tbody");w.className="divide-y divide-border-translucent",m.appendChild(w);let p=o.createElement("tbody"),E=o.createElement("tr"),h=o.createElement("td");h.className="px-4 py-8 text-center text-sm text-muted",h.colSpan=$.length,h.textContent="No active torrents yet. Add a magnet link to get started.",E.appendChild(h),p.appendChild(E),m.appendChild(p),t.appendChild(d);function N(y=[],x=null){let k=Array.isArray(y)?y:[];if(w.innerHTML="",!k.length){w.hidden=!0,p.hidden=!1;return}w.hidden=!1,p.hidden=!0;for(let B of k){let v=o.createElement("tr");v.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(" "),v.setAttribute("role","button"),v.setAttribute("tabindex","0"),v.dataset.torrentId=ee(B),te(x,B)?(v.classList.add("bg-info/10","text-text"),v.setAttribute("aria-pressed","true")):v.setAttribute("aria-pressed","false"),v.addEventListener("click",()=>{typeof a=="function"&&a(B)}),v.addEventListener("keydown",C=>{(C.key==="Enter"||C.key===" ")&&(C.preventDefault(),typeof a=="function"&&a(B))}),$.forEach(C=>{let A=o.createElement("td");A.className="px-4 py-3",C.cellClass&&(A.className+=` ${C.cellClass}`);let H=B?B[C.key]:void 0,r=se(C,i)(H,B,C),s=o.createElement("span");s.textContent=r==null?"":String(r),A.appendChild(s),v.appendChild(A)}),w.appendChild(v)}}function S(){t.innerHTML=""}return{render:N,destroy:S}}var ie="2.0",z=["wss://tracker.btorrent.xyz","wss://tracker.openwebtorrent.com"],j={announce:z},le={announce:z};function M(e,t=!1){if(typeof e!="number"||Number.isNaN(e))return"";if(e<1)return t?"":"0 B";let a=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),a.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${a[i]}${t?"/s":""}`}function ce(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function _(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function de(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 ue(e){if(!(!e||!Array.isArray(e.files))&&(e.files.forEach(t=>{typeof t.priority!="string"&&(t.priority="0"),typeof t.getBlobURL=="function"&&t.getBlobURL((a,i)=>{a||!i||(t.url=i)})}),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 a=e.name||"download";e.fileName=`${a}.torrent`}}catch(t){console.warn("[beacon] Failed to create torrent blob URL",t)}}function fe(e,t){if(!e)return;let a=typeof t=="string"?t:String(t??"0");if(e.priority=a,a==="-1"){typeof e.deselect=="function"&&e.deselect();return}let i=Number.parseInt(a,10);Number.isFinite(i)&&typeof e.select=="function"&&e.select(i)}function D(e,t){if(!Array.isArray(t)||t.length===0)return null;if(!e)return t[0];let a=t.find(i=>de(i,e));return a||t[0]}function G({documentRef:e=typeof document<"u"?document:null,WebTorrentCtor:t}={}){if(!e)throw new Error("createBeaconApp requires a document reference");let a=e.defaultView||globalThis,i=typeof t=="function"?t:typeof a?.WebTorrent=="function"?a.WebTorrent:null;if(!i)throw new Error("createBeaconApp requires a WebTorrent constructor");let o=R(e),d=new i({tracker:le}),n={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"]')},m=n.tableRoot?W({documentRef:e,root:n.tableRoot,formatters:{bytes:(r,s=!1)=>M(r,s),progress:r=>ce(r),ratio:r=>_(r),integer:r=>typeof r=="number"&&Number.isFinite(r)?Math.floor(r).toString():"0",bytesPerSecond:r=>M(r,!0)},onSelect(r){c.selectedTorrent=r,y()}}):null,c={selectedTorrent:null,processing:!1},T=[],w=[];function p(r){T.push(r)}function E(r){c.processing=!!r,n.overlay&&(n.overlay.hidden=!c.processing)}function h(){return Array.isArray(d.torrents)?[...d.torrents]:[]}function N(){if(!n.stats)return;let r=M(d.downloadSpeed,!0)||"0 B/s",s=M(d.uploadSpeed,!0)||"0 B/s",f=_(d.ratio);n.stats.textContent=`Client stats: \u2193 ${r} \xB7 \u2191 ${s} \xB7 Ratio: ${f}`}function S(r){n.fileList&&(n.fileList.innerHTML="",!(!r||!Array.isArray(r.files)||!r.files.length)&&r.files.forEach((s,f)=>{let u=e.createElement("tr");u.className="align-top";let l=e.createElement("td");if(l.className="px-3 py-2",s.done&&s.url){let g=e.createElement("a");g.className="text-info hover:text-info-strong",g.href=s.url,g.download=s.name||`file-${f+1}`,g.textContent=s.name||`File ${f+1}`,l.appendChild(g)}else{let g=e.createElement("span");g.textContent=s.name||`File ${f+1}`,l.appendChild(g)}u.appendChild(l);let b=e.createElement("td");b.className="px-3 py-2 text-muted-strong",b.textContent=M(s.length)||"0 B",u.appendChild(b);let I=e.createElement("td");I.className="px-3 py-2";let L=e.createElement("select");L.className="select",L.name=`${s.name||`file-${f+1}`}-priority`,L.setAttribute("aria-label",`Set priority for ${s.name||`file ${f+1}`}`),[{value:"1",label:"High priority"},{value:"0",label:"Low priority"},{value:"-1",label:"Don't download"}].forEach(g=>{let U=e.createElement("option");U.value=g.value,U.textContent=g.label,g.value===s.priority&&(U.selected=!0),L.appendChild(U)}),L.value=s.priority??"0",L.addEventListener("change",g=>{fe(s,g.target.value)}),I.appendChild(L),u.appendChild(I),n.fileList.appendChild(u)}))}function y(){if(!n.selectedSection)return;let r=h();c.selectedTorrent=D(c.selectedTorrent,r);let s=c.selectedTorrent;n.selectedSection.hidden=!s,s&&(n.selectedName&&(n.selectedName.textContent=s.name||"Untitled torrent"),n.pauseButton&&(n.pauseButton.hidden=s.paused===!0,n.pauseButton.disabled=s.paused===!0),n.resumeButton&&(n.resumeButton.hidden=s.paused!==!0,n.resumeButton.disabled=s.paused!==!0),n.downloadAllButton&&(n.downloadAllButton.disabled=!(s.progress>=1)),n.infoHashValue&&(n.infoHashValue.textContent=s.infoHash||""),S(s))}function x(r,{isSeed:s=!1}={}){E(!1),ue(r),s||o.info(`Received ${r.name||"torrent"} metadata`),c.selectedTorrent||(c.selectedTorrent=r),y(),m?.render(h(),c.selectedTorrent)}function k(r){let s=typeof r=="string"?r.trim():"";s&&(E(!0),d.add(s,j,f=>{x(f,{isSeed:!1})}))}function B(r){!Array.isArray(r)||r.length===0||(E(!0),d.seed(r,j,s=>{x(s,{isSeed:!0}),o.success(`Seeding ${s.files.length} file(s)`)}))}function v(r){if(r){if(r.progress<1){o.warn("Torrent is not finished downloading yet.");return}r.files.forEach(s=>{typeof s.getBlob=="function"&&s.getBlob((f,u)=>{if(f||!u){console.error("[beacon] Failed to get blob for file",s?.name,f),o.error("Failed to prepare file for download.");return}let l=URL.createObjectURL(u),b=e.createElement("a");b.className="beacon-hidden-download",b.setAttribute("data-beacon-hidden-download","true"),b.href=l,b.download=s.name||"download",e.body.appendChild(b),b.click(),e.body.removeChild(b),URL.revokeObjectURL(l)})})}}function C(r){if(!r?.magnetURI)return;let s=r.magnetURI,f=a?.navigator?.clipboard;if(f&&typeof f.writeText=="function"){f.writeText(s).then(()=>{o.success("Magnet URI copied to clipboard!")}).catch(u=>{console.error("[beacon] Clipboard error",u),o.error("Failed to copy magnet URI")});return}try{let u=e.createElement("textarea");u.value=s,u.className="beacon-clipboard-offscreen",u.setAttribute("data-beacon-clipboard-state","offscreen"),e.body.appendChild(u),u.select(),e.execCommand?.("copy"),e.body.removeChild(u),o.success("Magnet URI copied (fallback)!")}catch(u){console.error("[beacon] Clipboard fallback error",u),o.error("Failed to copy magnet URI")}}function A(r){r&&r.destroy(s=>{s&&(console.error("[beacon] Failed to destroy torrent",s),o.error("Failed to remove torrent"));let f=h();c.selectedTorrent=D(null,f),m?.render(f,c.selectedTorrent),y()})}function H(){if(console.log(`[beacon] Starting beacon runtime v${ie}`),o||console.warn("[beacon] Toast service unavailable"),i.WEBRTC_SUPPORT||o?.error("Please use a browser with WebRTC support.",{sticky:!0}),n.form){let l=b=>{b.preventDefault(),k(n.magnetInput?.value||""),n.magnetInput&&(n.magnetInput.value="")};n.form.addEventListener("submit",l),p(()=>n.form.removeEventListener("submit",l))}if(n.seedButton&&n.seedInput){let l=()=>{n.seedInput.click()},b=()=>{let I=Array.from(n.seedInput.files||[]);n.seedInput.value="",B(I)};n.seedButton.addEventListener("click",l),n.seedInput.addEventListener("change",b),p(()=>{n.seedButton.removeEventListener("click",l),n.seedInput.removeEventListener("change",b)})}if(n.pauseButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.pause=="function"&&(c.selectedTorrent.pause(),y())};n.pauseButton.addEventListener("click",l),p(()=>n.pauseButton.removeEventListener("click",l))}if(n.resumeButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.resume=="function"&&(c.selectedTorrent.resume(),y())};n.resumeButton.addEventListener("click",l),p(()=>n.resumeButton.removeEventListener("click",l))}if(n.downloadAllButton){let l=()=>v(c.selectedTorrent);n.downloadAllButton.addEventListener("click",l),p(()=>n.downloadAllButton.removeEventListener("click",l))}if(n.removeButton){let l=()=>A(c.selectedTorrent);n.removeButton.addEventListener("click",l),p(()=>n.removeButton.removeEventListener("click",l))}if(n.copyMagnetButton){let l=()=>C(c.selectedTorrent);n.copyMagnetButton.addEventListener("click",l),p(()=>n.copyMagnetButton.removeEventListener("click",l))}let r=l=>{console.error("[beacon] Torrent client error",l),o?.error(l?.message||String(l)),E(!1)};d.on("error",r),p(()=>d.removeListener("error",r));let s=l=>{if(d.torrents&&d.torrents.length>0)return l.preventDefault(),l.returnValue="Transfers are in progress. Are you sure you want to leave or refresh?",l.returnValue};a&&typeof a.addEventListener=="function"&&(a.addEventListener("beforeunload",s),p(()=>a.removeEventListener("beforeunload",s))),m?.render(h(),c.selectedTorrent),y(),N(),E(!1);let f=a.setInterval(()=>{m?.render(h(),c.selectedTorrent),y(),N()},1e3);w.push(f);let u=a?.location?.hash;typeof u=="string"&&u.length>1&&k(u.slice(1))}function P(){w.forEach(r=>a.clearInterval(r)),w.length=0,T.forEach(r=>{try{r()}catch(s){console.warn("[beacon] Cleanup task failed",s)}}),T.length=0,m?.destroy();try{d.destroy()}catch(r){console.warn("[beacon] Failed to destroy client",r)}}return{mount:H,destroy:P}}function pe(){let e=typeof window<"u"&&window||typeof globalThis<"u"&&globalThis||null;if(e&&typeof e.WebTorrent=="function")return e.WebTorrent;throw new Error("WebTorrent runtime is not available on the global scope")}var F=null;function K(){if(F)return F;if(typeof document>"u")throw new Error("Beacon runtime requires a document environment");let e=pe();return F=G({documentRef:document,WebTorrentCtor:e}),F.mount(),F}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}});else try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}export{G as createBeaconApp,K as mountBeaconApp};
+var O={info:"torrent-toast torrent-toast--info",success:"torrent-toast torrent-toast--success",error:"torrent-toast torrent-toast--error",warn:"torrent-toast torrent-toast--warn"},V="transition duration-200 ease-out",Y="beacon-toast-motion",q="data-beacon-motion";function J(e,t){let a=e?.defaultView;if(a&&typeof a.requestAnimationFrame=="function"){a.requestAnimationFrame(t);return}setTimeout(t,16)}function Q(e){let t=e||(typeof document<"u"?document:null);if(!t)return null;let a=t.getElementById("beacon-toast-container");if(!a){a=t.createElement("div"),a.id="beacon-toast-container",a.setAttribute("role","region"),a.setAttribute("aria-live","polite"),a.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 i=t.body||t.getElementsByTagName?.("body")?.[0];i&&i.appendChild(a)}return a}function X(e,t,a){let i=e.createElement("div");i.className=[V,a].join(" "),i.setAttribute("role","status"),i.setAttribute("tabindex","0"),i.textContent=t;let o=e.createElement("button");o.type="button",o.className=["notification-dismiss","focus-ring","absolute","right-3","top-3"].join(" "),o.setAttribute("aria-label","Dismiss notification"),o.innerHTML='';let d=e.createElement("div");return d.className=["relative","w-full",V,Y].join(" "),d.setAttribute(q,"exit"),d.appendChild(i),i.appendChild(o),{wrapper:d,toast:i,closeButton:o}}function Z(e,t=setTimeout,a=clearTimeout){return{show(i,o={}){if(!i)return;let d=o.type&&O[o.type]?o.type:"info",n=Q(e);if(!n)return;let{wrapper:m,toast:c,closeButton:T}=X(e,i,O[d]);n.appendChild(m),J(e,()=>{m.setAttribute(q,"enter")});let v=typeof o.duration=="number"?o.duration:3500,p=()=>{m.setAttribute(q,"exit");let N=t(()=>{m.parentNode&&m.parentNode.removeChild(m)},200);return T.removeEventListener("click",E),c.removeEventListener("keydown",h),T.removeEventListener("keydown",h),N},E=()=>p(),h=N=>{N.key==="Escape"&&(N.stopPropagation(),p())};T.addEventListener("click",E),c.addEventListener("keydown",h),T.addEventListener("keydown",h);let S=null;return o.sticky||(S=t(()=>{p()},v)),{dismiss(){S&&a(S),p()}}},set(i,o){return typeof o=="string"?this.show(i,{type:o}):this.show(i,o)},info(i,o={}){return this.show(i,{...o,type:"info"})},success(i,o={}){return this.show(i,{...o,type:"success"})},error(i,o={}){return this.show(i,{...o,type:"error"})},warn(i,o={}){return this.show(i,{...o,type:"warn"})}}}function R(e,t=setTimeout,a=clearTimeout){return Z(e,t,a)}var $=[{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 ee(e){return e?e.infoHash||e.magnetURI||e.name||String(Math.random()):""}function te(e,t){return!e||!t?!1:e===t||e.magnetURI&&e.magnetURI===t.magnetURI||e.infoHash&&e.infoHash===t.infoHash}function ne(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function re(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function oe(e){return typeof e!="number"||!Number.isFinite(e)?"0":Math.max(0,Math.floor(e)).toString()}function ae(e,t){if(typeof t=="function")return t(e,!0)||"";if(typeof e!="number"||Number.isNaN(e)||e<1)return"";let a=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),a.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${a[i]}/s`}function se(e,t){let a=e.formatter;if(typeof a=="function")return a;let i=t?.[a];if(typeof i=="function")return i;switch(a){case"progress":return ne;case"ratio":return re;case"integer":return oe;case"bytesPerSecond":return o=>ae(o,t?.bytes);default:return o=>o}}function W({documentRef:e=typeof document<"u"?document:null,root:t,onSelect:a,formatters:i={}}={}){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 o=e;t.innerHTML="";let d=o.createElement("div");d.className="overflow-hidden rounded-xl border border-border-translucent bg-surface shadow-sm";let n=o.createElement("div");n.className="overflow-x-auto",d.appendChild(n);let m=o.createElement("table");m.className="min-w-full divide-y divide-border-translucent text-sm",n.appendChild(m);let c=o.createElement("thead");c.className="bg-surface-alt text-muted-strong";let T=o.createElement("tr");$.forEach(y=>{let x=o.createElement("th");x.setAttribute("scope","col"),x.className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide",y.headerClass&&(x.className+=` ${y.headerClass}`),x.textContent=y.label,T.appendChild(x)}),c.appendChild(T),m.appendChild(c);let v=o.createElement("tbody");v.className="divide-y divide-border-translucent",m.appendChild(v);let p=o.createElement("tbody"),E=o.createElement("tr"),h=o.createElement("td");h.className="px-4 py-8 text-center text-sm text-muted",h.colSpan=$.length,h.textContent="No active torrents yet. Add a magnet link to get started.",E.appendChild(h),p.appendChild(E),m.appendChild(p),t.appendChild(d);function S(y=[],x=null){let k=Array.isArray(y)?y:[];if(v.innerHTML="",!k.length){v.hidden=!0,p.hidden=!1;return}v.hidden=!1,p.hidden=!0;for(let B of k){let w=o.createElement("tr");w.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(" "),w.setAttribute("role","button"),w.setAttribute("tabindex","0"),w.dataset.torrentId=ee(B),te(x,B)?(w.classList.add("bg-info/10","text-text"),w.setAttribute("aria-pressed","true")):w.setAttribute("aria-pressed","false"),w.addEventListener("click",()=>{typeof a=="function"&&a(B)}),w.addEventListener("keydown",C=>{(C.key==="Enter"||C.key===" ")&&(C.preventDefault(),typeof a=="function"&&a(B))}),$.forEach(C=>{let A=o.createElement("td");A.className="px-4 py-3",C.cellClass&&(A.className+=` ${C.cellClass}`);let H=B?B[C.key]:void 0,r=se(C,i)(H,B,C),s=o.createElement("span");s.textContent=r==null?"":String(r),A.appendChild(s),w.appendChild(A)}),v.appendChild(w)}}function N(){t.innerHTML=""}return{render:S,destroy:N}}var ie="2.0",z=["wss://tracker.btorrent.xyz","wss://tracker.openwebtorrent.com"],j={announce:z},le={announce:z};function M(e,t=!1){if(typeof e!="number"||Number.isNaN(e))return"";if(e<1)return t?"":"0 B";let a=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),a.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${a[i]}${t?"/s":""}`}function ce(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function _(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function de(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 ue(e){if(!(!e||!Array.isArray(e.files))&&(e.files.forEach(t=>{typeof t.priority!="string"&&(t.priority="0"),typeof t.getBlobURL=="function"&&t.getBlobURL((a,i)=>{a||!i||(t.url=i)})}),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 a=e.name||"download";e.fileName=`${a}.torrent`}}catch(t){console.warn("[beacon] Failed to create torrent blob URL",t)}}function fe(e,t){if(!e)return;let a=typeof t=="string"?t:String(t??"0");if(e.priority=a,a==="-1"){typeof e.deselect=="function"&&e.deselect();return}let i=Number.parseInt(a,10);Number.isFinite(i)&&typeof e.select=="function"&&e.select(i)}function D(e,t){if(!Array.isArray(t)||t.length===0)return null;if(!e)return t[0];let a=t.find(i=>de(i,e));return a||t[0]}function G({documentRef:e=typeof document<"u"?document:null,WebTorrentCtor:t}={}){if(!e)throw new Error("createBeaconApp requires a document reference");let a=e.defaultView||globalThis,i=typeof t=="function"?t:typeof a?.WebTorrent=="function"?a.WebTorrent:null;if(!i)throw new Error("createBeaconApp requires a WebTorrent constructor");let o=R(e),d=new i({tracker:le}),n={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"]')},m=n.tableRoot?W({documentRef:e,root:n.tableRoot,formatters:{bytes:(r,s=!1)=>M(r,s),progress:r=>ce(r),ratio:r=>_(r),integer:r=>typeof r=="number"&&Number.isFinite(r)?Math.floor(r).toString():"0",bytesPerSecond:r=>M(r,!0)},onSelect(r){c.selectedTorrent=r,y()}}):null,c={selectedTorrent:null,processing:!1},T=[],v=[];function p(r){T.push(r)}function E(r){c.processing=!!r,n.overlay&&(n.overlay.hidden=!c.processing)}function h(){return Array.isArray(d.torrents)?[...d.torrents]:[]}function S(){if(!n.stats)return;let r=M(d.downloadSpeed,!0)||"0 B/s",s=M(d.uploadSpeed,!0)||"0 B/s",f=_(d.ratio);n.stats.textContent=`Client stats: \u2193 ${r} \xB7 \u2191 ${s} \xB7 Ratio: ${f}`}function N(r){n.fileList&&(n.fileList.innerHTML="",!(!r||!Array.isArray(r.files)||!r.files.length)&&r.files.forEach((s,f)=>{let u=e.createElement("tr");u.className="align-top";let l=e.createElement("td");if(l.className="px-3 py-2",s.done&&s.url){let g=e.createElement("a");g.className="text-info hover:text-info-strong",g.href=s.url,g.download=s.name||`file-${f+1}`,g.textContent=s.name||`File ${f+1}`,l.appendChild(g)}else{let g=e.createElement("span");g.textContent=s.name||`File ${f+1}`,l.appendChild(g)}u.appendChild(l);let b=e.createElement("td");b.className="px-3 py-2 text-muted-strong",b.textContent=M(s.length)||"0 B",u.appendChild(b);let I=e.createElement("td");I.className="px-3 py-2";let L=e.createElement("select");L.className="select",L.name=`${s.name||`file-${f+1}`}-priority`,L.setAttribute("aria-label",`Set priority for ${s.name||`file ${f+1}`}`),[{value:"1",label:"High priority"},{value:"0",label:"Low priority"},{value:"-1",label:"Don't download"}].forEach(g=>{let U=e.createElement("option");U.value=g.value,U.textContent=g.label,g.value===s.priority&&(U.selected=!0),L.appendChild(U)}),L.value=s.priority??"0",L.addEventListener("change",g=>{fe(s,g.target.value)}),I.appendChild(L),u.appendChild(I),n.fileList.appendChild(u)}))}function y(){if(!n.selectedSection)return;let r=h();c.selectedTorrent=D(c.selectedTorrent,r);let s=c.selectedTorrent;n.selectedSection.hidden=!s,s&&(n.selectedName&&(n.selectedName.textContent=s.name||"Untitled torrent"),n.pauseButton&&(n.pauseButton.hidden=s.paused===!0,n.pauseButton.disabled=s.paused===!0),n.resumeButton&&(n.resumeButton.hidden=s.paused!==!0,n.resumeButton.disabled=s.paused!==!0),n.downloadAllButton&&(n.downloadAllButton.disabled=!(s.progress>=1)),n.infoHashValue&&(n.infoHashValue.textContent=s.infoHash||""),N(s))}function x(r,{isSeed:s=!1}={}){E(!1),ue(r),s||o.info(`Received ${r.name||"torrent"} metadata`),c.selectedTorrent||(c.selectedTorrent=r),y(),m?.render(h(),c.selectedTorrent)}function k(r){let s=typeof r=="string"?r.trim():"";s&&(E(!0),d.add(s,j,f=>{x(f,{isSeed:!1})}))}function B(r){!Array.isArray(r)||r.length===0||(E(!0),d.seed(r,j,s=>{x(s,{isSeed:!0}),o.success(`Seeding ${s.files.length} file(s)`)}))}function w(r){if(r){if(r.progress<1){o.warn("Torrent is not finished downloading yet.");return}r.files.forEach(s=>{typeof s.getBlob=="function"&&s.getBlob((f,u)=>{if(f||!u){console.error("[beacon] Failed to get blob for file",s?.name,f),o.error("Failed to prepare file for download.");return}let l=URL.createObjectURL(u),b=e.createElement("a");b.className="beacon-hidden-download",b.setAttribute("data-beacon-hidden-download","true"),b.href=l,b.download=s.name||"download",e.body.appendChild(b),b.click(),e.body.removeChild(b),URL.revokeObjectURL(l)})})}}function C(r){if(!r?.magnetURI)return;let s=r.magnetURI,f=a?.navigator?.clipboard;if(f&&typeof f.writeText=="function"){f.writeText(s).then(()=>{o.success("Magnet URI copied to clipboard!")}).catch(u=>{console.error("[beacon] Clipboard error",u),o.error("Failed to copy magnet URI")});return}try{let u=e.createElement("textarea");u.value=s,u.className="beacon-clipboard-offscreen",u.setAttribute("data-beacon-clipboard-state","offscreen"),e.body.appendChild(u),u.select(),e.execCommand?.("copy"),e.body.removeChild(u),o.success("Magnet URI copied (fallback)!")}catch(u){console.error("[beacon] Clipboard fallback error",u),o.error("Failed to copy magnet URI")}}function A(r){r&&r.destroy(s=>{s&&(console.error("[beacon] Failed to destroy torrent",s),o.error("Failed to remove torrent"));let f=h();c.selectedTorrent=D(null,f),m?.render(f,c.selectedTorrent),y()})}function H(){if(console.log(`[beacon] Starting beacon runtime v${ie}`),o||console.warn("[beacon] Toast service unavailable"),i.WEBRTC_SUPPORT||o?.error("Please use a browser with WebRTC support.",{sticky:!0}),n.form){let l=b=>{b.preventDefault(),k(n.magnetInput?.value||""),n.magnetInput&&(n.magnetInput.value="")};n.form.addEventListener("submit",l),p(()=>n.form.removeEventListener("submit",l))}if(n.seedButton&&n.seedInput){let l=()=>{n.seedInput.click()},b=()=>{let I=Array.from(n.seedInput.files||[]);n.seedInput.value="",B(I)};n.seedButton.addEventListener("click",l),n.seedInput.addEventListener("change",b),p(()=>{n.seedButton.removeEventListener("click",l),n.seedInput.removeEventListener("change",b)})}if(n.pauseButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.pause=="function"&&(c.selectedTorrent.pause(),y())};n.pauseButton.addEventListener("click",l),p(()=>n.pauseButton.removeEventListener("click",l))}if(n.resumeButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.resume=="function"&&(c.selectedTorrent.resume(),y())};n.resumeButton.addEventListener("click",l),p(()=>n.resumeButton.removeEventListener("click",l))}if(n.downloadAllButton){let l=()=>w(c.selectedTorrent);n.downloadAllButton.addEventListener("click",l),p(()=>n.downloadAllButton.removeEventListener("click",l))}if(n.removeButton){let l=()=>A(c.selectedTorrent);n.removeButton.addEventListener("click",l),p(()=>n.removeButton.removeEventListener("click",l))}if(n.copyMagnetButton){let l=()=>C(c.selectedTorrent);n.copyMagnetButton.addEventListener("click",l),p(()=>n.copyMagnetButton.removeEventListener("click",l))}let r=l=>{console.error("[beacon] Torrent client error",l),o?.error(l?.message||String(l)),E(!1)};d.on("error",r),p(()=>d.removeListener("error",r));let s=l=>{if(d.torrents&&d.torrents.length>0)return l.preventDefault(),l.returnValue="Transfers are in progress. Are you sure you want to leave or refresh?",l.returnValue};a&&typeof a.addEventListener=="function"&&(a.addEventListener("beforeunload",s),p(()=>a.removeEventListener("beforeunload",s))),m?.render(h(),c.selectedTorrent),y(),S(),E(!1);let f=a.setInterval(()=>{m?.render(h(),c.selectedTorrent),y(),S()},1e3);v.push(f);let u=a?.location?.hash;typeof u=="string"&&u.length>1&&k(u.slice(1))}function P(){v.forEach(r=>a.clearInterval(r)),v.length=0,T.forEach(r=>{try{r()}catch(s){console.warn("[beacon] Cleanup task failed",s)}}),T.length=0,m?.destroy();try{d.destroy()}catch(r){console.warn("[beacon] Failed to destroy client",r)}}return{mount:H,destroy:P}}function pe(){return typeof window<"u"&&window||typeof globalThis<"u"&&globalThis||null}function me(){let e=pe();if(e&&typeof e.WebTorrent=="function")return e.WebTorrent;throw new Error("WebTorrent runtime is not available on the global scope")}var F=null;function K(){if(F)return F;if(typeof document>"u")throw new Error("Beacon runtime requires a document environment");let e=me();return F=G({documentRef:document,WebTorrentCtor:e}),F.mount(),F}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}});else try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}export{G as createBeaconApp,K as mountBeaconApp};
diff --git a/torrent/src/beacon-vendor.js b/torrent/src/beacon-vendor.js
index 9f61b3a7..51e31443 100644
--- a/torrent/src/beacon-vendor.js
+++ b/torrent/src/beacon-vendor.js
@@ -1,10 +1,15 @@
import { createBeaconApp } from "../app.js";
-function resolveWebTorrent() {
- const globalScope =
+function resolveGlobalScope() {
+ return (
(typeof window !== "undefined" && window) ||
(typeof globalThis !== "undefined" && globalThis) ||
- null;
+ null
+ );
+}
+
+function resolveWebTorrent() {
+ const globalScope = resolveGlobalScope();
if (globalScope && typeof globalScope.WebTorrent === "function") {
return globalScope.WebTorrent;
From 13d8e1a4a4bacde17712a1e59505aa0cbfbe8b04 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Wed, 15 Oct 2025 14:03:22 -0400
Subject: [PATCH 4/4] Fix Beacon overlay hidden state
---
torrent/app.js | 16 ++++++++++++++--
torrent/dist/beacon.js | 2 +-
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/torrent/app.js b/torrent/app.js
index ac10efbd..a7fd30e5 100644
--- a/torrent/app.js
+++ b/torrent/app.js
@@ -214,8 +214,20 @@ export function createBeaconApp({
function setProcessing(flag) {
state.processing = Boolean(flag);
- if (elements.overlay) {
- elements.overlay.hidden = !state.processing;
+
+ if (!elements.overlay) {
+ return;
+ }
+
+ const isActive = state.processing;
+
+ elements.overlay.hidden = !isActive;
+ elements.overlay.setAttribute("aria-hidden", isActive ? "false" : "true");
+
+ if (isActive) {
+ elements.overlay.style.removeProperty("display");
+ } else {
+ elements.overlay.style.display = "none";
}
}
diff --git a/torrent/dist/beacon.js b/torrent/dist/beacon.js
index 8686d644..ad385566 100644
--- a/torrent/dist/beacon.js
+++ b/torrent/dist/beacon.js
@@ -1 +1 @@
-var O={info:"torrent-toast torrent-toast--info",success:"torrent-toast torrent-toast--success",error:"torrent-toast torrent-toast--error",warn:"torrent-toast torrent-toast--warn"},V="transition duration-200 ease-out",Y="beacon-toast-motion",q="data-beacon-motion";function J(e,t){let a=e?.defaultView;if(a&&typeof a.requestAnimationFrame=="function"){a.requestAnimationFrame(t);return}setTimeout(t,16)}function Q(e){let t=e||(typeof document<"u"?document:null);if(!t)return null;let a=t.getElementById("beacon-toast-container");if(!a){a=t.createElement("div"),a.id="beacon-toast-container",a.setAttribute("role","region"),a.setAttribute("aria-live","polite"),a.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 i=t.body||t.getElementsByTagName?.("body")?.[0];i&&i.appendChild(a)}return a}function X(e,t,a){let i=e.createElement("div");i.className=[V,a].join(" "),i.setAttribute("role","status"),i.setAttribute("tabindex","0"),i.textContent=t;let o=e.createElement("button");o.type="button",o.className=["notification-dismiss","focus-ring","absolute","right-3","top-3"].join(" "),o.setAttribute("aria-label","Dismiss notification"),o.innerHTML='';let d=e.createElement("div");return d.className=["relative","w-full",V,Y].join(" "),d.setAttribute(q,"exit"),d.appendChild(i),i.appendChild(o),{wrapper:d,toast:i,closeButton:o}}function Z(e,t=setTimeout,a=clearTimeout){return{show(i,o={}){if(!i)return;let d=o.type&&O[o.type]?o.type:"info",n=Q(e);if(!n)return;let{wrapper:m,toast:c,closeButton:T}=X(e,i,O[d]);n.appendChild(m),J(e,()=>{m.setAttribute(q,"enter")});let v=typeof o.duration=="number"?o.duration:3500,p=()=>{m.setAttribute(q,"exit");let N=t(()=>{m.parentNode&&m.parentNode.removeChild(m)},200);return T.removeEventListener("click",E),c.removeEventListener("keydown",h),T.removeEventListener("keydown",h),N},E=()=>p(),h=N=>{N.key==="Escape"&&(N.stopPropagation(),p())};T.addEventListener("click",E),c.addEventListener("keydown",h),T.addEventListener("keydown",h);let S=null;return o.sticky||(S=t(()=>{p()},v)),{dismiss(){S&&a(S),p()}}},set(i,o){return typeof o=="string"?this.show(i,{type:o}):this.show(i,o)},info(i,o={}){return this.show(i,{...o,type:"info"})},success(i,o={}){return this.show(i,{...o,type:"success"})},error(i,o={}){return this.show(i,{...o,type:"error"})},warn(i,o={}){return this.show(i,{...o,type:"warn"})}}}function R(e,t=setTimeout,a=clearTimeout){return Z(e,t,a)}var $=[{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 ee(e){return e?e.infoHash||e.magnetURI||e.name||String(Math.random()):""}function te(e,t){return!e||!t?!1:e===t||e.magnetURI&&e.magnetURI===t.magnetURI||e.infoHash&&e.infoHash===t.infoHash}function ne(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function re(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function oe(e){return typeof e!="number"||!Number.isFinite(e)?"0":Math.max(0,Math.floor(e)).toString()}function ae(e,t){if(typeof t=="function")return t(e,!0)||"";if(typeof e!="number"||Number.isNaN(e)||e<1)return"";let a=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),a.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${a[i]}/s`}function se(e,t){let a=e.formatter;if(typeof a=="function")return a;let i=t?.[a];if(typeof i=="function")return i;switch(a){case"progress":return ne;case"ratio":return re;case"integer":return oe;case"bytesPerSecond":return o=>ae(o,t?.bytes);default:return o=>o}}function W({documentRef:e=typeof document<"u"?document:null,root:t,onSelect:a,formatters:i={}}={}){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 o=e;t.innerHTML="";let d=o.createElement("div");d.className="overflow-hidden rounded-xl border border-border-translucent bg-surface shadow-sm";let n=o.createElement("div");n.className="overflow-x-auto",d.appendChild(n);let m=o.createElement("table");m.className="min-w-full divide-y divide-border-translucent text-sm",n.appendChild(m);let c=o.createElement("thead");c.className="bg-surface-alt text-muted-strong";let T=o.createElement("tr");$.forEach(y=>{let x=o.createElement("th");x.setAttribute("scope","col"),x.className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide",y.headerClass&&(x.className+=` ${y.headerClass}`),x.textContent=y.label,T.appendChild(x)}),c.appendChild(T),m.appendChild(c);let v=o.createElement("tbody");v.className="divide-y divide-border-translucent",m.appendChild(v);let p=o.createElement("tbody"),E=o.createElement("tr"),h=o.createElement("td");h.className="px-4 py-8 text-center text-sm text-muted",h.colSpan=$.length,h.textContent="No active torrents yet. Add a magnet link to get started.",E.appendChild(h),p.appendChild(E),m.appendChild(p),t.appendChild(d);function S(y=[],x=null){let k=Array.isArray(y)?y:[];if(v.innerHTML="",!k.length){v.hidden=!0,p.hidden=!1;return}v.hidden=!1,p.hidden=!0;for(let B of k){let w=o.createElement("tr");w.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(" "),w.setAttribute("role","button"),w.setAttribute("tabindex","0"),w.dataset.torrentId=ee(B),te(x,B)?(w.classList.add("bg-info/10","text-text"),w.setAttribute("aria-pressed","true")):w.setAttribute("aria-pressed","false"),w.addEventListener("click",()=>{typeof a=="function"&&a(B)}),w.addEventListener("keydown",C=>{(C.key==="Enter"||C.key===" ")&&(C.preventDefault(),typeof a=="function"&&a(B))}),$.forEach(C=>{let A=o.createElement("td");A.className="px-4 py-3",C.cellClass&&(A.className+=` ${C.cellClass}`);let H=B?B[C.key]:void 0,r=se(C,i)(H,B,C),s=o.createElement("span");s.textContent=r==null?"":String(r),A.appendChild(s),w.appendChild(A)}),v.appendChild(w)}}function N(){t.innerHTML=""}return{render:S,destroy:N}}var ie="2.0",z=["wss://tracker.btorrent.xyz","wss://tracker.openwebtorrent.com"],j={announce:z},le={announce:z};function M(e,t=!1){if(typeof e!="number"||Number.isNaN(e))return"";if(e<1)return t?"":"0 B";let a=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),a.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${a[i]}${t?"/s":""}`}function ce(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function _(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function de(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 ue(e){if(!(!e||!Array.isArray(e.files))&&(e.files.forEach(t=>{typeof t.priority!="string"&&(t.priority="0"),typeof t.getBlobURL=="function"&&t.getBlobURL((a,i)=>{a||!i||(t.url=i)})}),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 a=e.name||"download";e.fileName=`${a}.torrent`}}catch(t){console.warn("[beacon] Failed to create torrent blob URL",t)}}function fe(e,t){if(!e)return;let a=typeof t=="string"?t:String(t??"0");if(e.priority=a,a==="-1"){typeof e.deselect=="function"&&e.deselect();return}let i=Number.parseInt(a,10);Number.isFinite(i)&&typeof e.select=="function"&&e.select(i)}function D(e,t){if(!Array.isArray(t)||t.length===0)return null;if(!e)return t[0];let a=t.find(i=>de(i,e));return a||t[0]}function G({documentRef:e=typeof document<"u"?document:null,WebTorrentCtor:t}={}){if(!e)throw new Error("createBeaconApp requires a document reference");let a=e.defaultView||globalThis,i=typeof t=="function"?t:typeof a?.WebTorrent=="function"?a.WebTorrent:null;if(!i)throw new Error("createBeaconApp requires a WebTorrent constructor");let o=R(e),d=new i({tracker:le}),n={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"]')},m=n.tableRoot?W({documentRef:e,root:n.tableRoot,formatters:{bytes:(r,s=!1)=>M(r,s),progress:r=>ce(r),ratio:r=>_(r),integer:r=>typeof r=="number"&&Number.isFinite(r)?Math.floor(r).toString():"0",bytesPerSecond:r=>M(r,!0)},onSelect(r){c.selectedTorrent=r,y()}}):null,c={selectedTorrent:null,processing:!1},T=[],v=[];function p(r){T.push(r)}function E(r){c.processing=!!r,n.overlay&&(n.overlay.hidden=!c.processing)}function h(){return Array.isArray(d.torrents)?[...d.torrents]:[]}function S(){if(!n.stats)return;let r=M(d.downloadSpeed,!0)||"0 B/s",s=M(d.uploadSpeed,!0)||"0 B/s",f=_(d.ratio);n.stats.textContent=`Client stats: \u2193 ${r} \xB7 \u2191 ${s} \xB7 Ratio: ${f}`}function N(r){n.fileList&&(n.fileList.innerHTML="",!(!r||!Array.isArray(r.files)||!r.files.length)&&r.files.forEach((s,f)=>{let u=e.createElement("tr");u.className="align-top";let l=e.createElement("td");if(l.className="px-3 py-2",s.done&&s.url){let g=e.createElement("a");g.className="text-info hover:text-info-strong",g.href=s.url,g.download=s.name||`file-${f+1}`,g.textContent=s.name||`File ${f+1}`,l.appendChild(g)}else{let g=e.createElement("span");g.textContent=s.name||`File ${f+1}`,l.appendChild(g)}u.appendChild(l);let b=e.createElement("td");b.className="px-3 py-2 text-muted-strong",b.textContent=M(s.length)||"0 B",u.appendChild(b);let I=e.createElement("td");I.className="px-3 py-2";let L=e.createElement("select");L.className="select",L.name=`${s.name||`file-${f+1}`}-priority`,L.setAttribute("aria-label",`Set priority for ${s.name||`file ${f+1}`}`),[{value:"1",label:"High priority"},{value:"0",label:"Low priority"},{value:"-1",label:"Don't download"}].forEach(g=>{let U=e.createElement("option");U.value=g.value,U.textContent=g.label,g.value===s.priority&&(U.selected=!0),L.appendChild(U)}),L.value=s.priority??"0",L.addEventListener("change",g=>{fe(s,g.target.value)}),I.appendChild(L),u.appendChild(I),n.fileList.appendChild(u)}))}function y(){if(!n.selectedSection)return;let r=h();c.selectedTorrent=D(c.selectedTorrent,r);let s=c.selectedTorrent;n.selectedSection.hidden=!s,s&&(n.selectedName&&(n.selectedName.textContent=s.name||"Untitled torrent"),n.pauseButton&&(n.pauseButton.hidden=s.paused===!0,n.pauseButton.disabled=s.paused===!0),n.resumeButton&&(n.resumeButton.hidden=s.paused!==!0,n.resumeButton.disabled=s.paused!==!0),n.downloadAllButton&&(n.downloadAllButton.disabled=!(s.progress>=1)),n.infoHashValue&&(n.infoHashValue.textContent=s.infoHash||""),N(s))}function x(r,{isSeed:s=!1}={}){E(!1),ue(r),s||o.info(`Received ${r.name||"torrent"} metadata`),c.selectedTorrent||(c.selectedTorrent=r),y(),m?.render(h(),c.selectedTorrent)}function k(r){let s=typeof r=="string"?r.trim():"";s&&(E(!0),d.add(s,j,f=>{x(f,{isSeed:!1})}))}function B(r){!Array.isArray(r)||r.length===0||(E(!0),d.seed(r,j,s=>{x(s,{isSeed:!0}),o.success(`Seeding ${s.files.length} file(s)`)}))}function w(r){if(r){if(r.progress<1){o.warn("Torrent is not finished downloading yet.");return}r.files.forEach(s=>{typeof s.getBlob=="function"&&s.getBlob((f,u)=>{if(f||!u){console.error("[beacon] Failed to get blob for file",s?.name,f),o.error("Failed to prepare file for download.");return}let l=URL.createObjectURL(u),b=e.createElement("a");b.className="beacon-hidden-download",b.setAttribute("data-beacon-hidden-download","true"),b.href=l,b.download=s.name||"download",e.body.appendChild(b),b.click(),e.body.removeChild(b),URL.revokeObjectURL(l)})})}}function C(r){if(!r?.magnetURI)return;let s=r.magnetURI,f=a?.navigator?.clipboard;if(f&&typeof f.writeText=="function"){f.writeText(s).then(()=>{o.success("Magnet URI copied to clipboard!")}).catch(u=>{console.error("[beacon] Clipboard error",u),o.error("Failed to copy magnet URI")});return}try{let u=e.createElement("textarea");u.value=s,u.className="beacon-clipboard-offscreen",u.setAttribute("data-beacon-clipboard-state","offscreen"),e.body.appendChild(u),u.select(),e.execCommand?.("copy"),e.body.removeChild(u),o.success("Magnet URI copied (fallback)!")}catch(u){console.error("[beacon] Clipboard fallback error",u),o.error("Failed to copy magnet URI")}}function A(r){r&&r.destroy(s=>{s&&(console.error("[beacon] Failed to destroy torrent",s),o.error("Failed to remove torrent"));let f=h();c.selectedTorrent=D(null,f),m?.render(f,c.selectedTorrent),y()})}function H(){if(console.log(`[beacon] Starting beacon runtime v${ie}`),o||console.warn("[beacon] Toast service unavailable"),i.WEBRTC_SUPPORT||o?.error("Please use a browser with WebRTC support.",{sticky:!0}),n.form){let l=b=>{b.preventDefault(),k(n.magnetInput?.value||""),n.magnetInput&&(n.magnetInput.value="")};n.form.addEventListener("submit",l),p(()=>n.form.removeEventListener("submit",l))}if(n.seedButton&&n.seedInput){let l=()=>{n.seedInput.click()},b=()=>{let I=Array.from(n.seedInput.files||[]);n.seedInput.value="",B(I)};n.seedButton.addEventListener("click",l),n.seedInput.addEventListener("change",b),p(()=>{n.seedButton.removeEventListener("click",l),n.seedInput.removeEventListener("change",b)})}if(n.pauseButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.pause=="function"&&(c.selectedTorrent.pause(),y())};n.pauseButton.addEventListener("click",l),p(()=>n.pauseButton.removeEventListener("click",l))}if(n.resumeButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.resume=="function"&&(c.selectedTorrent.resume(),y())};n.resumeButton.addEventListener("click",l),p(()=>n.resumeButton.removeEventListener("click",l))}if(n.downloadAllButton){let l=()=>w(c.selectedTorrent);n.downloadAllButton.addEventListener("click",l),p(()=>n.downloadAllButton.removeEventListener("click",l))}if(n.removeButton){let l=()=>A(c.selectedTorrent);n.removeButton.addEventListener("click",l),p(()=>n.removeButton.removeEventListener("click",l))}if(n.copyMagnetButton){let l=()=>C(c.selectedTorrent);n.copyMagnetButton.addEventListener("click",l),p(()=>n.copyMagnetButton.removeEventListener("click",l))}let r=l=>{console.error("[beacon] Torrent client error",l),o?.error(l?.message||String(l)),E(!1)};d.on("error",r),p(()=>d.removeListener("error",r));let s=l=>{if(d.torrents&&d.torrents.length>0)return l.preventDefault(),l.returnValue="Transfers are in progress. Are you sure you want to leave or refresh?",l.returnValue};a&&typeof a.addEventListener=="function"&&(a.addEventListener("beforeunload",s),p(()=>a.removeEventListener("beforeunload",s))),m?.render(h(),c.selectedTorrent),y(),S(),E(!1);let f=a.setInterval(()=>{m?.render(h(),c.selectedTorrent),y(),S()},1e3);v.push(f);let u=a?.location?.hash;typeof u=="string"&&u.length>1&&k(u.slice(1))}function P(){v.forEach(r=>a.clearInterval(r)),v.length=0,T.forEach(r=>{try{r()}catch(s){console.warn("[beacon] Cleanup task failed",s)}}),T.length=0,m?.destroy();try{d.destroy()}catch(r){console.warn("[beacon] Failed to destroy client",r)}}return{mount:H,destroy:P}}function pe(){return typeof window<"u"&&window||typeof globalThis<"u"&&globalThis||null}function me(){let e=pe();if(e&&typeof e.WebTorrent=="function")return e.WebTorrent;throw new Error("WebTorrent runtime is not available on the global scope")}var F=null;function K(){if(F)return F;if(typeof document>"u")throw new Error("Beacon runtime requires a document environment");let e=me();return F=G({documentRef:document,WebTorrentCtor:e}),F.mount(),F}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}});else try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}export{G as createBeaconApp,K as mountBeaconApp};
+var O={info:"torrent-toast torrent-toast--info",success:"torrent-toast torrent-toast--success",error:"torrent-toast torrent-toast--error",warn:"torrent-toast torrent-toast--warn"},V="transition duration-200 ease-out",Y="beacon-toast-motion",q="data-beacon-motion";function J(e,t){let s=e?.defaultView;if(s&&typeof s.requestAnimationFrame=="function"){s.requestAnimationFrame(t);return}setTimeout(t,16)}function Q(e){let t=e||(typeof document<"u"?document:null);if(!t)return null;let s=t.getElementById("beacon-toast-container");if(!s){s=t.createElement("div"),s.id="beacon-toast-container",s.setAttribute("role","region"),s.setAttribute("aria-live","polite"),s.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 i=t.body||t.getElementsByTagName?.("body")?.[0];i&&i.appendChild(s)}return s}function X(e,t,s){let i=e.createElement("div");i.className=[V,s].join(" "),i.setAttribute("role","status"),i.setAttribute("tabindex","0"),i.textContent=t;let o=e.createElement("button");o.type="button",o.className=["notification-dismiss","focus-ring","absolute","right-3","top-3"].join(" "),o.setAttribute("aria-label","Dismiss notification"),o.innerHTML='';let d=e.createElement("div");return d.className=["relative","w-full",V,Y].join(" "),d.setAttribute(q,"exit"),d.appendChild(i),i.appendChild(o),{wrapper:d,toast:i,closeButton:o}}function Z(e,t=setTimeout,s=clearTimeout){return{show(i,o={}){if(!i)return;let d=o.type&&O[o.type]?o.type:"info",r=Q(e);if(!r)return;let{wrapper:m,toast:c,closeButton:T}=X(e,i,O[d]);r.appendChild(m),J(e,()=>{m.setAttribute(q,"enter")});let w=typeof o.duration=="number"?o.duration:3500,p=()=>{m.setAttribute(q,"exit");let N=t(()=>{m.parentNode&&m.parentNode.removeChild(m)},200);return T.removeEventListener("click",E),c.removeEventListener("keydown",y),T.removeEventListener("keydown",y),N},E=()=>p(),y=N=>{N.key==="Escape"&&(N.stopPropagation(),p())};T.addEventListener("click",E),c.addEventListener("keydown",y),T.addEventListener("keydown",y);let S=null;return o.sticky||(S=t(()=>{p()},w)),{dismiss(){S&&s(S),p()}}},set(i,o){return typeof o=="string"?this.show(i,{type:o}):this.show(i,o)},info(i,o={}){return this.show(i,{...o,type:"info"})},success(i,o={}){return this.show(i,{...o,type:"success"})},error(i,o={}){return this.show(i,{...o,type:"error"})},warn(i,o={}){return this.show(i,{...o,type:"warn"})}}}function R(e,t=setTimeout,s=clearTimeout){return Z(e,t,s)}var $=[{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 ee(e){return e?e.infoHash||e.magnetURI||e.name||String(Math.random()):""}function te(e,t){return!e||!t?!1:e===t||e.magnetURI&&e.magnetURI===t.magnetURI||e.infoHash&&e.infoHash===t.infoHash}function re(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function ne(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function oe(e){return typeof e!="number"||!Number.isFinite(e)?"0":Math.max(0,Math.floor(e)).toString()}function ae(e,t){if(typeof t=="function")return t(e,!0)||"";if(typeof e!="number"||Number.isNaN(e)||e<1)return"";let s=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),s.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${s[i]}/s`}function se(e,t){let s=e.formatter;if(typeof s=="function")return s;let i=t?.[s];if(typeof i=="function")return i;switch(s){case"progress":return re;case"ratio":return ne;case"integer":return oe;case"bytesPerSecond":return o=>ae(o,t?.bytes);default:return o=>o}}function W({documentRef:e=typeof document<"u"?document:null,root:t,onSelect:s,formatters:i={}}={}){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 o=e;t.innerHTML="";let d=o.createElement("div");d.className="overflow-hidden rounded-xl border border-border-translucent bg-surface shadow-sm";let r=o.createElement("div");r.className="overflow-x-auto",d.appendChild(r);let m=o.createElement("table");m.className="min-w-full divide-y divide-border-translucent text-sm",r.appendChild(m);let c=o.createElement("thead");c.className="bg-surface-alt text-muted-strong";let T=o.createElement("tr");$.forEach(h=>{let x=o.createElement("th");x.setAttribute("scope","col"),x.className="px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide",h.headerClass&&(x.className+=` ${h.headerClass}`),x.textContent=h.label,T.appendChild(x)}),c.appendChild(T),m.appendChild(c);let w=o.createElement("tbody");w.className="divide-y divide-border-translucent",m.appendChild(w);let p=o.createElement("tbody"),E=o.createElement("tr"),y=o.createElement("td");y.className="px-4 py-8 text-center text-sm text-muted",y.colSpan=$.length,y.textContent="No active torrents yet. Add a magnet link to get started.",E.appendChild(y),p.appendChild(E),m.appendChild(p),t.appendChild(d);function S(h=[],x=null){let k=Array.isArray(h)?h:[];if(w.innerHTML="",!k.length){w.hidden=!0,p.hidden=!1;return}w.hidden=!1,p.hidden=!0;for(let B of k){let v=o.createElement("tr");v.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(" "),v.setAttribute("role","button"),v.setAttribute("tabindex","0"),v.dataset.torrentId=ee(B),te(x,B)?(v.classList.add("bg-info/10","text-text"),v.setAttribute("aria-pressed","true")):v.setAttribute("aria-pressed","false"),v.addEventListener("click",()=>{typeof s=="function"&&s(B)}),v.addEventListener("keydown",C=>{(C.key==="Enter"||C.key===" ")&&(C.preventDefault(),typeof s=="function"&&s(B))}),$.forEach(C=>{let A=o.createElement("td");A.className="px-4 py-3",C.cellClass&&(A.className+=` ${C.cellClass}`);let H=B?B[C.key]:void 0,n=se(C,i)(H,B,C),a=o.createElement("span");a.textContent=n==null?"":String(n),A.appendChild(a),v.appendChild(A)}),w.appendChild(v)}}function N(){t.innerHTML=""}return{render:S,destroy:N}}var ie="2.0",z=["wss://tracker.btorrent.xyz","wss://tracker.openwebtorrent.com"],j={announce:z},le={announce:z};function M(e,t=!1){if(typeof e!="number"||Number.isNaN(e))return"";if(e<1)return t?"":"0 B";let s=["B","kB","MB","GB","TB"],i=Math.min(Math.floor(Math.log(e)/Math.log(1e3)),s.length-1);return`${(e/Math.pow(1e3,i)).toFixed(1)} ${s[i]}${t?"/s":""}`}function ce(e){return typeof e!="number"||Number.isNaN(e)?"":`${(e*100).toFixed(1)}%`}function _(e){return typeof e!="number"||!Number.isFinite(e)?"0.00":e.toFixed(2)}function de(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 ue(e){if(!(!e||!Array.isArray(e.files))&&(e.files.forEach(t=>{typeof t.priority!="string"&&(t.priority="0"),typeof t.getBlobURL=="function"&&t.getBlobURL((s,i)=>{s||!i||(t.url=i)})}),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 s=e.name||"download";e.fileName=`${s}.torrent`}}catch(t){console.warn("[beacon] Failed to create torrent blob URL",t)}}function fe(e,t){if(!e)return;let s=typeof t=="string"?t:String(t??"0");if(e.priority=s,s==="-1"){typeof e.deselect=="function"&&e.deselect();return}let i=Number.parseInt(s,10);Number.isFinite(i)&&typeof e.select=="function"&&e.select(i)}function D(e,t){if(!Array.isArray(t)||t.length===0)return null;if(!e)return t[0];let s=t.find(i=>de(i,e));return s||t[0]}function G({documentRef:e=typeof document<"u"?document:null,WebTorrentCtor:t}={}){if(!e)throw new Error("createBeaconApp requires a document reference");let s=e.defaultView||globalThis,i=typeof t=="function"?t:typeof s?.WebTorrent=="function"?s.WebTorrent:null;if(!i)throw new Error("createBeaconApp requires a WebTorrent constructor");let o=R(e),d=new i({tracker:le}),r={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"]')},m=r.tableRoot?W({documentRef:e,root:r.tableRoot,formatters:{bytes:(n,a=!1)=>M(n,a),progress:n=>ce(n),ratio:n=>_(n),integer:n=>typeof n=="number"&&Number.isFinite(n)?Math.floor(n).toString():"0",bytesPerSecond:n=>M(n,!0)},onSelect(n){c.selectedTorrent=n,h()}}):null,c={selectedTorrent:null,processing:!1},T=[],w=[];function p(n){T.push(n)}function E(n){if(c.processing=!!n,!r.overlay)return;let a=c.processing;r.overlay.hidden=!a,r.overlay.setAttribute("aria-hidden",a?"false":"true"),a?r.overlay.style.removeProperty("display"):r.overlay.style.display="none"}function y(){return Array.isArray(d.torrents)?[...d.torrents]:[]}function S(){if(!r.stats)return;let n=M(d.downloadSpeed,!0)||"0 B/s",a=M(d.uploadSpeed,!0)||"0 B/s",f=_(d.ratio);r.stats.textContent=`Client stats: \u2193 ${n} \xB7 \u2191 ${a} \xB7 Ratio: ${f}`}function N(n){r.fileList&&(r.fileList.innerHTML="",!(!n||!Array.isArray(n.files)||!n.files.length)&&n.files.forEach((a,f)=>{let u=e.createElement("tr");u.className="align-top";let l=e.createElement("td");if(l.className="px-3 py-2",a.done&&a.url){let g=e.createElement("a");g.className="text-info hover:text-info-strong",g.href=a.url,g.download=a.name||`file-${f+1}`,g.textContent=a.name||`File ${f+1}`,l.appendChild(g)}else{let g=e.createElement("span");g.textContent=a.name||`File ${f+1}`,l.appendChild(g)}u.appendChild(l);let b=e.createElement("td");b.className="px-3 py-2 text-muted-strong",b.textContent=M(a.length)||"0 B",u.appendChild(b);let I=e.createElement("td");I.className="px-3 py-2";let L=e.createElement("select");L.className="select",L.name=`${a.name||`file-${f+1}`}-priority`,L.setAttribute("aria-label",`Set priority for ${a.name||`file ${f+1}`}`),[{value:"1",label:"High priority"},{value:"0",label:"Low priority"},{value:"-1",label:"Don't download"}].forEach(g=>{let U=e.createElement("option");U.value=g.value,U.textContent=g.label,g.value===a.priority&&(U.selected=!0),L.appendChild(U)}),L.value=a.priority??"0",L.addEventListener("change",g=>{fe(a,g.target.value)}),I.appendChild(L),u.appendChild(I),r.fileList.appendChild(u)}))}function h(){if(!r.selectedSection)return;let n=y();c.selectedTorrent=D(c.selectedTorrent,n);let a=c.selectedTorrent;r.selectedSection.hidden=!a,a&&(r.selectedName&&(r.selectedName.textContent=a.name||"Untitled torrent"),r.pauseButton&&(r.pauseButton.hidden=a.paused===!0,r.pauseButton.disabled=a.paused===!0),r.resumeButton&&(r.resumeButton.hidden=a.paused!==!0,r.resumeButton.disabled=a.paused!==!0),r.downloadAllButton&&(r.downloadAllButton.disabled=!(a.progress>=1)),r.infoHashValue&&(r.infoHashValue.textContent=a.infoHash||""),N(a))}function x(n,{isSeed:a=!1}={}){E(!1),ue(n),a||o.info(`Received ${n.name||"torrent"} metadata`),c.selectedTorrent||(c.selectedTorrent=n),h(),m?.render(y(),c.selectedTorrent)}function k(n){let a=typeof n=="string"?n.trim():"";a&&(E(!0),d.add(a,j,f=>{x(f,{isSeed:!1})}))}function B(n){!Array.isArray(n)||n.length===0||(E(!0),d.seed(n,j,a=>{x(a,{isSeed:!0}),o.success(`Seeding ${a.files.length} file(s)`)}))}function v(n){if(n){if(n.progress<1){o.warn("Torrent is not finished downloading yet.");return}n.files.forEach(a=>{typeof a.getBlob=="function"&&a.getBlob((f,u)=>{if(f||!u){console.error("[beacon] Failed to get blob for file",a?.name,f),o.error("Failed to prepare file for download.");return}let l=URL.createObjectURL(u),b=e.createElement("a");b.className="beacon-hidden-download",b.setAttribute("data-beacon-hidden-download","true"),b.href=l,b.download=a.name||"download",e.body.appendChild(b),b.click(),e.body.removeChild(b),URL.revokeObjectURL(l)})})}}function C(n){if(!n?.magnetURI)return;let a=n.magnetURI,f=s?.navigator?.clipboard;if(f&&typeof f.writeText=="function"){f.writeText(a).then(()=>{o.success("Magnet URI copied to clipboard!")}).catch(u=>{console.error("[beacon] Clipboard error",u),o.error("Failed to copy magnet URI")});return}try{let u=e.createElement("textarea");u.value=a,u.className="beacon-clipboard-offscreen",u.setAttribute("data-beacon-clipboard-state","offscreen"),e.body.appendChild(u),u.select(),e.execCommand?.("copy"),e.body.removeChild(u),o.success("Magnet URI copied (fallback)!")}catch(u){console.error("[beacon] Clipboard fallback error",u),o.error("Failed to copy magnet URI")}}function A(n){n&&n.destroy(a=>{a&&(console.error("[beacon] Failed to destroy torrent",a),o.error("Failed to remove torrent"));let f=y();c.selectedTorrent=D(null,f),m?.render(f,c.selectedTorrent),h()})}function H(){if(console.log(`[beacon] Starting beacon runtime v${ie}`),o||console.warn("[beacon] Toast service unavailable"),i.WEBRTC_SUPPORT||o?.error("Please use a browser with WebRTC support.",{sticky:!0}),r.form){let l=b=>{b.preventDefault(),k(r.magnetInput?.value||""),r.magnetInput&&(r.magnetInput.value="")};r.form.addEventListener("submit",l),p(()=>r.form.removeEventListener("submit",l))}if(r.seedButton&&r.seedInput){let l=()=>{r.seedInput.click()},b=()=>{let I=Array.from(r.seedInput.files||[]);r.seedInput.value="",B(I)};r.seedButton.addEventListener("click",l),r.seedInput.addEventListener("change",b),p(()=>{r.seedButton.removeEventListener("click",l),r.seedInput.removeEventListener("change",b)})}if(r.pauseButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.pause=="function"&&(c.selectedTorrent.pause(),h())};r.pauseButton.addEventListener("click",l),p(()=>r.pauseButton.removeEventListener("click",l))}if(r.resumeButton){let l=()=>{c.selectedTorrent&&typeof c.selectedTorrent.resume=="function"&&(c.selectedTorrent.resume(),h())};r.resumeButton.addEventListener("click",l),p(()=>r.resumeButton.removeEventListener("click",l))}if(r.downloadAllButton){let l=()=>v(c.selectedTorrent);r.downloadAllButton.addEventListener("click",l),p(()=>r.downloadAllButton.removeEventListener("click",l))}if(r.removeButton){let l=()=>A(c.selectedTorrent);r.removeButton.addEventListener("click",l),p(()=>r.removeButton.removeEventListener("click",l))}if(r.copyMagnetButton){let l=()=>C(c.selectedTorrent);r.copyMagnetButton.addEventListener("click",l),p(()=>r.copyMagnetButton.removeEventListener("click",l))}let n=l=>{console.error("[beacon] Torrent client error",l),o?.error(l?.message||String(l)),E(!1)};d.on("error",n),p(()=>d.removeListener("error",n));let a=l=>{if(d.torrents&&d.torrents.length>0)return l.preventDefault(),l.returnValue="Transfers are in progress. Are you sure you want to leave or refresh?",l.returnValue};s&&typeof s.addEventListener=="function"&&(s.addEventListener("beforeunload",a),p(()=>s.removeEventListener("beforeunload",a))),m?.render(y(),c.selectedTorrent),h(),S(),E(!1);let f=s.setInterval(()=>{m?.render(y(),c.selectedTorrent),h(),S()},1e3);w.push(f);let u=s?.location?.hash;typeof u=="string"&&u.length>1&&k(u.slice(1))}function P(){w.forEach(n=>s.clearInterval(n)),w.length=0,T.forEach(n=>{try{n()}catch(a){console.warn("[beacon] Cleanup task failed",a)}}),T.length=0,m?.destroy();try{d.destroy()}catch(n){console.warn("[beacon] Failed to destroy client",n)}}return{mount:H,destroy:P}}function pe(){return typeof window<"u"&&window||typeof globalThis<"u"&&globalThis||null}function me(){let e=pe();if(e&&typeof e.WebTorrent=="function")return e.WebTorrent;throw new Error("WebTorrent runtime is not available on the global scope")}var F=null;function K(){if(F)return F;if(typeof document>"u")throw new Error("Beacon runtime requires a document environment");let e=me();return F=G({documentRef:document,WebTorrentCtor:e}),F.mount(),F}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>{try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}});else try{K()}catch(e){console.error("[beacon] Failed to mount app",e)}export{G as createBeaconApp,K as mountBeaconApp};