diff --git a/js/constants.js b/js/constants.js index 8efdc688..38600fe9 100644 --- a/js/constants.js +++ b/js/constants.js @@ -1,7 +1,159 @@ -export const URL_FIRST_ENABLED = true; -export const ACCEPT_LEGACY_V1 = true; -export const WSS_TRACKERS = [ - "wss://tracker.openwebtorrent.com", - "wss://tracker.btorrent.xyz", - "wss://tracker.webtorrent.dev", -]; +const DEFAULT_FLAGS = Object.freeze({ + URL_FIRST_ENABLED: true, // try URL before magnet in the player + ACCEPT_LEGACY_V1: true, // accept v1 magnet-only notes + WSS_TRACKERS: Object.freeze([ + "wss://tracker.openwebtorrent.com", + "wss://tracker.btorrent.xyz", + "wss://tracker.webtorrent.dev", + ]), +}); + +const globalScope = typeof globalThis === "object" && globalThis ? globalThis : undefined; + +const runtimeFlags = (() => { + if (globalScope && typeof globalScope.__BITVID_RUNTIME_FLAGS__ === "object") { + return globalScope.__BITVID_RUNTIME_FLAGS__; + } + const initial = { + URL_FIRST_ENABLED: DEFAULT_FLAGS.URL_FIRST_ENABLED, + ACCEPT_LEGACY_V1: DEFAULT_FLAGS.ACCEPT_LEGACY_V1, + WSS_TRACKERS: [...DEFAULT_FLAGS.WSS_TRACKERS], + }; + if (globalScope) { + globalScope.__BITVID_RUNTIME_FLAGS__ = initial; + } + return initial; +})(); + +function coerceBoolean(value, fallback) { + if (typeof value === "boolean") { + return value; + } + if (typeof value === "string") { + const normalized = value.trim().toLowerCase(); + if (!normalized) { + return fallback; + } + if (["false", "0", "off", "no"].includes(normalized)) { + return false; + } + if (["true", "1", "on", "yes"].includes(normalized)) { + return true; + } + } + if (value == null) { + return fallback; + } + return Boolean(value); +} + +function sanitizeTrackerList(candidate) { + const input = Array.isArray(candidate) ? candidate : DEFAULT_FLAGS.WSS_TRACKERS; + const seen = new Set(); + const sanitized = []; + + for (const tracker of input) { + if (typeof tracker !== "string") { + continue; + } + const trimmed = tracker.trim(); + if (!trimmed) { + continue; + } + if (!/^wss:\/\//i.test(trimmed)) { + continue; + } + const normalized = trimmed.toLowerCase(); + if (seen.has(normalized)) { + continue; + } + seen.add(normalized); + sanitized.push(trimmed); + } + + if (!sanitized.length) { + return [...DEFAULT_FLAGS.WSS_TRACKERS]; + } + + return sanitized; +} + +function freezeTrackers(list) { + return Object.freeze([...list]); +} + +export let URL_FIRST_ENABLED = coerceBoolean( + runtimeFlags.URL_FIRST_ENABLED, + DEFAULT_FLAGS.URL_FIRST_ENABLED +); + +export let ACCEPT_LEGACY_V1 = coerceBoolean( + runtimeFlags.ACCEPT_LEGACY_V1, + DEFAULT_FLAGS.ACCEPT_LEGACY_V1 +); + +export let WSS_TRACKERS = freezeTrackers( + sanitizeTrackerList(runtimeFlags.WSS_TRACKERS) +); + +Object.defineProperty(runtimeFlags, "URL_FIRST_ENABLED", { + configurable: true, + enumerable: true, + get() { + return URL_FIRST_ENABLED; + }, + set(next) { + URL_FIRST_ENABLED = coerceBoolean(next, DEFAULT_FLAGS.URL_FIRST_ENABLED); + }, +}); + +Object.defineProperty(runtimeFlags, "ACCEPT_LEGACY_V1", { + configurable: true, + enumerable: true, + get() { + return ACCEPT_LEGACY_V1; + }, + set(next) { + ACCEPT_LEGACY_V1 = coerceBoolean(next, DEFAULT_FLAGS.ACCEPT_LEGACY_V1); + }, +}); + +Object.defineProperty(runtimeFlags, "WSS_TRACKERS", { + configurable: true, + enumerable: true, + get() { + return [...WSS_TRACKERS]; + }, + set(next) { + WSS_TRACKERS = freezeTrackers(sanitizeTrackerList(next)); + }, +}); + +// Ensure the runtime object reflects the sanitized defaults immediately. +runtimeFlags.URL_FIRST_ENABLED = URL_FIRST_ENABLED; +runtimeFlags.ACCEPT_LEGACY_V1 = ACCEPT_LEGACY_V1; +runtimeFlags.WSS_TRACKERS = WSS_TRACKERS; + +export function setUrlFirstEnabled(next) { + runtimeFlags.URL_FIRST_ENABLED = next; + return URL_FIRST_ENABLED; +} + +export function setAcceptLegacyV1(next) { + runtimeFlags.ACCEPT_LEGACY_V1 = next; + return ACCEPT_LEGACY_V1; +} + +export function setWssTrackers(next) { + runtimeFlags.WSS_TRACKERS = next; + return WSS_TRACKERS; +} + +export function resetRuntimeFlags() { + setUrlFirstEnabled(DEFAULT_FLAGS.URL_FIRST_ENABLED); + setAcceptLegacyV1(DEFAULT_FLAGS.ACCEPT_LEGACY_V1); + setWssTrackers(DEFAULT_FLAGS.WSS_TRACKERS); +} + +export const RUNTIME_FLAGS = runtimeFlags; +