// authorization // load library, playlists, and favorites after successful login async function initializeApp() { const authModal = document.getElementById(DOM_IDS.AUTH_MODAL); if (authModal) hideModal(DOM_IDS.AUTH_MODAL); await loadQueue().catch(() => {}); updateQueueDisplay(); await loadLibrary(); await loadPlaylists(); await loadFavorites(); // restore song display and pause playback on startup if (hasValidTrack()) { playTrack(state.queue[state.queueIndex]); ui.player.pause(); } } // handle login form submission with validation async function handleLogin() { if (handleLogin.isInProgress) return; const server = ui.serverInput.value; const username = ui.usernameInput.value; const password = ui.passwordInput.value; ui.authError.textContent = ""; const urlValidation = validateServerUrl(server); if (!urlValidation.valid) { ui.authError.textContent = urlValidation.error; return; } const credValidation = validateCredentials(username, password); if (!credValidation.valid) { ui.authError.textContent = credValidation.error; return; } handleLogin.isInProgress = true; const { username: validUsername, password: validPassword } = credValidation.value; const validServerUrl = urlValidation.value; try { // Generate salt+token from password const salt = Math.random().toString(36).substring(2, 8); const token = SparkMD5.hash(validPassword + salt); // Test with token mode API state.api = new SubsonicAPI(validServerUrl, validUsername, token, salt); await state.api.ping(); // Save credentials for auto-login localStorage.setItem( "tinysub_credentials", JSON.stringify({ server: validServerUrl, username: validUsername, token, salt, }), ); await initializeApp(); } catch (error) { console.error("[Auth] Login failed:", error); ui.authError.textContent = `${STRINGS.CONNECTION_ERROR} ${error.message}`; state.api = null; } finally { handleLogin.isInProgress = false; } } // clear all storage and reload to logout async function handleLogout() { try { if (indexedDB.databases) { const dbs = await indexedDB.databases(); for (const db of dbs) { indexedDB.deleteDatabase(db.name); } } localStorage.clear(); alert("logging out, local databases and credentials cleared"); } catch (error) { console.error("[Auth] Logout cleanup failed:", error); } location.reload(); } // attempt auto-login on page load async function attemptAutoLogin() { const saved = localStorage.getItem("tinysub_credentials"); const creds = saved ? JSON.parse(saved) : { server: "", username: "", token: "", salt: "" }; // migration from 1.7.x if (saved) { try { const parsed = JSON.parse(saved); if (parsed.password) { console.warn("[Auth] Old password format detected, logging out"); alert( "tinysub 1.8+ now uses token-based auth and also has other incompatible changes from <1.8, logging out to clear old credentials", ); await handleLogout(); return; } } catch {} } // no stored credentials const hasCredentials = creds.server && creds.username && creds.token && creds.salt; if (!hasCredentials) { const authModal = document.getElementById(DOM_IDS.AUTH_MODAL); if (authModal) showModal(authModal, { focusSelector: "input" }); return; } try { // try to authenticate with stored salt+token state.api = new SubsonicAPI( creds.server, creds.username, creds.token, creds.salt, ); // validate credentials are still valid by calling ping() await state.api.ping(); await initializeApp(); } catch (error) { console.warn( "[Auth] Stored credentials invalid, showing login form:", error.message, ); state.api = null; // pre-populate form with stored server and username ui.serverInput.value = creds.server; ui.usernameInput.value = creds.username; const authModal = document.getElementById(DOM_IDS.AUTH_MODAL); if (authModal) showModal(authModal, { focusSelector: "input" }); } } // run auto-login when scripts are ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", attemptAutoLogin); } else { attemptAutoLogin(); }