refactor(plugins/multi-scrobbler/src/index.tsx): refactor the whole file
This commit is contained in:
parent
95f88652d9
commit
8d82323f65
1 changed files with 58 additions and 337 deletions
|
|
@ -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;
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue