diff --git a/plugins/multi-scrobbler/src/index.tsx b/plugins/multi-scrobbler/src/index.tsx index 207b10d..873b497 100644 --- a/plugins/multi-scrobbler/src/index.tsx +++ b/plugins/multi-scrobbler/src/index.tsx @@ -10,191 +10,6 @@ import { serviceFactory } from "./services/ServiceFactory"; import Settings from "./ui/pages/Settings"; import patchSidebar from "./sidebar"; -try { - console.log("[Scrobble Plugin] module loaded (index.tsx)"); - - (function installBridge() { - try { - const g: any = typeof globalThis !== "undefined" ? globalThis : (typeof window !== "undefined" ? window : undefined); - if (!g) return; - - g.__scrobbleBridge = g.__scrobbleBridge || {}; - - // Force invocation of the plugin's onLoad (if the plugin export is available at runtime). - g.__scrobbleBridge.forceOnLoad = () => { - try { - const pluginExport = - g.__scrobble_plugin || - (typeof module !== "undefined" && (module as any).exports && (module as any).exports.default) || - null; - - if (pluginExport && typeof pluginExport.onLoad === "function") { - try { - pluginExport.onLoad(); - console.log("[Scrobble Plugin] bridge: onLoad invoked"); - } catch (e) { - console.error("[Scrobble Plugin] bridge: onLoad threw an error:", e); - } - } else { - console.warn("[Scrobble Plugin] bridge: plugin export or onLoad not available"); - } - } catch (e) { - console.error("[Scrobble Plugin] bridge: failed to invoke onLoad:", e); - } - }; - - // Return whatever we can find for programmatic inspection - g.__scrobbleBridge.inspect = () => { - try { - return g.__scrobble_plugin || (typeof module !== "undefined" && (module as any).exports && (module as any).exports.default) || null; - } catch (e) { - console.error("[Scrobble Plugin] bridge.inspect error:", e); - return null; - } - }; - - // Small helper to allow reloading the plugin module (best-effort - may be limited by runtime) - g.__scrobbleBridge.tryReload = () => { - try { - // Attempt to clear a require cache if present and re-require the module - if (typeof require !== "undefined" && require.cache) { - try { - const path = require.resolve && require.resolve("./index.tsx"); - if (path && require.cache[path]) delete require.cache[path]; - } catch {} - try { - const re = require("./index.tsx"); - const exported = re && re.__esModule && typeof re.default !== "undefined" ? re.default : re; - (g as any).__scrobble_plugin = exported; - console.log("[Scrobble Plugin] bridge: reloaded plugin module"); - return exported; - } catch (e) { - console.error("[Scrobble Plugin] bridge: reload failed:", e); - return null; - } - } else { - console.warn("[Scrobble Plugin] bridge.tryReload not supported in this runtime"); - return null; - } - } catch (e) { - console.error("[Scrobble Plugin] bridge.tryReload error:", e); - return null; - } - }; - } catch (e) { - try { - console.error("[Scrobble Plugin] Failed to install debug bridge:", e); - } catch {} - } - })(); -} catch (e) { - try { - console.error("[Scrobble Plugin] Top-level init error:", e); - } catch {} -} - -(function installUnhandledRejectionHandler() { - try { - const g = typeof globalThis !== "undefined" ? (globalThis as any) : {}; - - // Avoid installing multiple times - if (g && !g.__scrobble_unhandled_rejection_installed) { - const handler = (ev: any) => { - try { - // ev may be an event or a reason object depending on the runtime - const reason = ev && (ev.reason ?? ev) ? (ev.reason ?? ev) : ev; - console.error("[Scrobble Plugin] Unhandled rejection:", reason); - } catch (inner) { - // Defensive: if formatting the event fails, still log something - console.error("[Scrobble Plugin] Unhandled rejection (unknown format)", ev); - } - }; - - // Prefer globalThis.addEventListener - if (g && typeof g.addEventListener === "function") { - try { - g.addEventListener("unhandledrejection", handler); - g.__scrobble_unhandled_rejection_installed = true; - return; - } catch (e) { - // continue to other fallbacks - console.error("[Scrobble Plugin] Failed to register via globalThis.addEventListener:", e); - } - } - - // If window exists and supports addEventListener, use it - if (typeof (globalThis as any).window !== "undefined") { - const w = (globalThis as any).window; - if (w && typeof w.addEventListener === "function") { - try { - w.addEventListener("unhandledrejection", handler); - g.__scrobble_unhandled_rejection_installed = true; - return; - } catch (e) { - console.error("[Scrobble Plugin] Failed to register via window.addEventListener:", e); - } - } else if (w) { - // fallback to assigning onunhandledrejection if available and not a function call - try { - if (typeof w.onunhandledrejection === "undefined") { - w.onunhandledrejection = (ev: any) => handler(ev); - g.__scrobble_unhandled_rejection_installed = true; - return; - } - } catch (e) { - console.error("[Scrobble Plugin] Failed to assign window.onunhandledrejection:", e); - } - } - } - - // As a last resort, try process.on (Node-style). Guard existence and function type. - try { - const proc = (globalThis as any).process; - if (proc && typeof proc.on === "function") { - proc.on("unhandledRejection", (reason: any) => handler({ reason })); - g.__scrobble_unhandled_rejection_installed = true; - return; - } - } catch (e) { - // ignore - } - - // If none of the above worked, set a marker so we don't keep trying on repeated loads - g.__scrobble_unhandled_rejection_installed = true; - } - } catch (e) { - // Most defensive: don't let any error here block plugin loading - try { - console.error("[Scrobble Plugin] Error while installing unhandled rejection handler:", e); - } catch { - /* swallow */ - } - } -})(); - -// Heartbeat helpers -let __scrobbleHeartbeat: any; -function startHeartbeat() { - if (__scrobbleHeartbeat) return; - try { - __scrobbleHeartbeat = setInterval(() => { - console.log("[Scrobble Plugin] Heartbeat (alive)"); - }, 60_000); - } catch (e) { - console.error("[Scrobble Plugin] Failed to start heartbeat:", e); - } -} -function stopHeartbeat() { - if (!__scrobbleHeartbeat) return; - try { - clearInterval(__scrobbleHeartbeat); - } catch (e) { - console.error("[Scrobble Plugin] Failed to clear heartbeat:", e); - } finally { - __scrobbleHeartbeat = undefined; - } -} - export const pluginState = { pluginStopped: false, lastActivity: undefined, @@ -207,69 +22,57 @@ export const pluginState = { lastTrackUrl?: string; }; -const cleanupFunctions: (() => void)[] = []; let sidebarUnpatch: (() => void) | undefined; -// Set up default plugin settings +// Initialize default settings const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS; -for (const key of Object.keys(defaultSettings)) { - // Defensive: plugin.storage may not accept arbitrary keys in some environments, - // but assignment should generally be fine in Vendetta. - try { - plugin.storage[key] = - plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings]; - } catch (e) { - console.error("[Scrobble Plugin] Failed to initialize default setting", key, e); - } -} +Object.keys(defaultSettings).forEach((key) => { + plugin.storage[key] = + plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings]; +}); -try { - // safe default for addToSidebar - plugin.storage.addToSidebar ??= false; -} catch (e) { - // ignore if storage access fails -} +plugin.storage.addToSidebar ??= false; export const currentSettings = new Proxy(plugin.storage, { - get(target, prop) { - return (target as any)[prop]; + get(target, prop: string) { + return target[prop]; }, - set(target, prop, value) { - (target as any)[prop] = value; + set(target, prop: string, value) { + target[prop] = value; return true; }, }); -// Keep track of connection attempts +// Connection status tracking let connectionAttempts = 0; const MAX_CONNECTION_ATTEMPTS = 3; const RECONNECT_DELAY = 5000; -const log = (...args: any[]) => console.log("[Scrobble Plugin]", ...args); -const logError = (...args: any[]) => console.error("[Scrobble Plugin] Error:", ...args); - async function tryInitialize() { try { await initialize(); connectionAttempts = 0; - log("Plugin initialized successfully"); + console.log("[Multi-Scrobbler] Successfully connected"); } catch (error) { - logError("Initialization failed:", error); + console.error("[Multi-Scrobbler] Initialization error:", error); connectionAttempts++; if (connectionAttempts < MAX_CONNECTION_ATTEMPTS) { - log(`Retrying connection (${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS})...`); + console.log( + `[Multi-Scrobbler] Retrying connection... (attempt ${connectionAttempts})`, + ); setTimeout(tryInitialize, RECONNECT_DELAY); } else { - logError(`Failed to connect after ${MAX_CONNECTION_ATTEMPTS} attempts`); + console.error( + "[Multi-Scrobbler] Failed to connect after multiple attempts", + ); } } } async function validateAndInitialize() { - // Check if user has selected a service first if (!currentSettings.service) { - log("No service selected. Please configure a service in plugin settings."); + console.log("[Multi-Scrobbler] No service selected. Please configure a service in settings."); return; } @@ -277,10 +80,9 @@ async function validateAndInitialize() { try { serviceName = serviceFactory.getCurrentService().getServiceName(); } catch (e) { - logError("Failed to determine current service name:", e); + console.error("[Multi-Scrobbler] Failed to determine current service name:", e); } - // Make sure we have credentials for whatever service is selected const service = currentSettings.service; let hasCredentials = false; @@ -297,198 +99,117 @@ async function validateAndInitialize() { } if (!hasCredentials) { - logError(`Missing credentials for ${serviceName}. Please configure in plugin settings.`); + console.error(`[Multi-Scrobbler] Missing credentials for ${serviceName}. Please configure in settings.`); return; } - log(`Starting with ${serviceName}...`); + console.log(`[Multi-Scrobbler] Starting with ${serviceName}...`); if (UserStore.getCurrentUser()) { - log("Discord is already connected, initializing immediately..."); - await tryInitialize(); + tryInitialize(); } else { - log("Waiting for Discord connection..."); const waitForUser = () => { - try { - if (UserStore.getCurrentUser()) { - log("Discord connection established"); - tryInitialize(); - FluxDispatcher.unsubscribe("CONNECTION_OPEN", waitForUser); - } - } catch (e) { - logError("Error while waiting for Discord connection:", e); + if (UserStore.getCurrentUser()) { + tryInitialize(); + FluxDispatcher.unsubscribe("CONNECTION_OPEN", waitForUser); } }; - try { - FluxDispatcher.subscribe("CONNECTION_OPEN", waitForUser); - } catch (e) { - logError("Failed to subscribe to CONNECTION_OPEN:", e); - } + FluxDispatcher.subscribe("CONNECTION_OPEN", waitForUser); } } -const __scrobble_plugin_export = { +export default { onLoad() { - log("Plugin loading..."); + console.log("[Multi-Scrobbler] Loading..."); pluginState.pluginStopped = false; - try { - startHeartbeat(); - log("Heartbeat started"); - } catch (e) { - logError("Failed to start heartbeat:", e); - } - - // Check if service is configured - if (!currentSettings.service) { - log("No service configured. Please select a service in plugin settings."); - } else { + // Patch sidebar if enabled + if (currentSettings.addToSidebar !== false) { try { - const serviceName = serviceFactory.getCurrentService().getServiceName(); - log(`Configuration: Service=${serviceName}, Update Interval=${currentSettings.timeInterval}s, Verbose=${currentSettings.verboseLogging}`); - } catch (e) { - logError("Failed to read service configuration:", e); + sidebarUnpatch = patchSidebar(); + console.log("[Multi-Scrobbler] Sidebar patched successfully"); + } catch (error) { + console.error("[Multi-Scrobbler] Failed to patch sidebar:", error); } } - // Add to sidebar if user wants it - try { - sidebarUnpatch = patchSidebar(); - cleanupFunctions.push(() => { - if (sidebarUnpatch) { - try { - sidebarUnpatch(); - } catch (e) { - // ignore individual cleanup errors - } - sidebarUnpatch = undefined; - } - }); - } catch (error) { - log("Sidebar setup failed:", error); - } - - try { - validateAndInitialize(); - log("onLoad complete - validateAndInitialize triggered"); - } catch (e) { - logError("validateAndInitialize threw an error:", e); - } + validateAndInitialize(); }, onUnload() { - log("Plugin unloading..."); + console.log("[Multi-Scrobbler] Unloading..."); pluginState.pluginStopped = true; - try { - stopHeartbeat(); - log("Heartbeat stopped"); - } catch (e) { - logError("Failed to stop heartbeat:", e); - } - - // Run all cleanup functions - cleanupFunctions.forEach((cleanup) => { + // Unpatch sidebar + if (sidebarUnpatch) { try { - cleanup(); + sidebarUnpatch(); + sidebarUnpatch = undefined; + console.log("[Multi-Scrobbler] Sidebar unpatched"); } catch (error) { - // ignore cleanup errors + console.error("[Multi-Scrobbler] Failed to unpatch sidebar:", error); } - }); - cleanupFunctions.length = 0; - - try { - stop(); - } catch (e) { - logError("Error while stopping plugin manager:", e); } - log("Plugin unloaded"); + + stop(); }, - // When user changes settings async onSettingsUpdate(newSettings: any) { const oldService = currentSettings.service; const newService = newSettings.service; const oldSidebar = currentSettings.addToSidebar; const newSidebar = newSettings.addToSidebar; - try { - Object.assign(currentSettings, newSettings); - } catch (e) { - logError("Failed to apply new settings:", e); - } - - log("Settings updated:", Object.keys(newSettings || {})); + Object.assign(currentSettings, newSettings); // Check if sidebar setting changed if (oldSidebar !== newSidebar) { if (newSidebar) { try { sidebarUnpatch = patchSidebar(); - log("Sidebar enabled"); + console.log("[Multi-Scrobbler] Sidebar enabled"); } catch (error) { - log("Failed to enable sidebar:", error); + console.error("[Multi-Scrobbler] Failed to enable sidebar:", error); } } else { if (sidebarUnpatch) { try { sidebarUnpatch(); } catch (e) { - logError("Failed to unpatch sidebar:", e); + console.error("[Multi-Scrobbler] Failed to unpatch sidebar:", e); } sidebarUnpatch = undefined; - log("Sidebar disabled"); + console.log("[Multi-Scrobbler] Sidebar disabled"); } } } if (oldService !== newService && newService) { - log(`Service changed from ${oldService || "none"} to ${newService}`); + console.log(`[Multi-Scrobbler] Service changed from ${oldService || "none"} to ${newService}`); try { await switchService(newService); } catch (e) { - logError("Failed to switch service:", e); + console.error("[Multi-Scrobbler] Failed to switch service:", e); } } else if (!pluginState.pluginStopped && currentSettings.service) { - log("Restarting with updated settings..."); - await tryInitialize(); + tryInitialize(); } else if (!currentSettings.service) { - log("Service unselected, stopping plugin..."); + console.log("[Multi-Scrobbler] Service unselected, stopping plugin..."); try { stop(); } catch (e) { - logError("Error while stopping due to service unselected:", e); + console.error("[Multi-Scrobbler] Error while stopping due to service unselected:", e); } } }, onDiscordReconnect() { if (!pluginState.pluginStopped) { - log("Discord reconnected, reinitializing plugin..."); + console.log("[Multi-Scrobbler] Discord reconnected, reinitializing..."); tryInitialize(); } }, settings: Settings, -}; - -try { - // Expose the resolved plugin export to the global bridge so console can access it - const g: any = typeof globalThis !== "undefined" ? globalThis : (typeof window !== "undefined" ? window : undefined); - if (g) { - try { - g.__scrobble_plugin = __scrobble_plugin_export; - if (g.__scrobbleBridge && typeof g.__scrobbleBridge.inspect === "function") { - // allow immediate inspection - console.log("[Scrobble Plugin] global plugin export attached for inspection"); - } - } catch (e) { - console.error("[Scrobble Plugin] Failed to assign global plugin export:", e); - } - } -} catch (e) { - // ignore -} - -export default __scrobble_plugin_export; +}; \ No newline at end of file