import { loadLib } from "../library/store.svelte.js"; import { offlineManager } from "../offline/offlineManager.svelte.js"; import { syncQueue } from "../playback/queue.svelte.js"; import { api, detectExtensions, extensions, persistCredentials, setCredentials, } from "./client.svelte.js"; import { type Credentials, createToken, deploymentConfig, getStoredCredentials, isAuthenticationError, isNetworkError, normalizeServer, } from "./connection.js"; export const getSessionCredentials = (): Credentials | null => { const stored = getStoredCredentials(); return stored && (!deploymentConfig.serverLock || !deploymentConfig.server || stored.server === deploymentConfig.server) ? stored : null; }; const loadSessionData = async (username: string) => { let admin = false; const tasks: Promise[] = [loadLib(), syncQueue()]; if (username) { tasks.push( api .getUser(username) .then((user) => { admin = user.adminRole === true || user.adminRole === "true"; }) .catch((err) => { console.error("failed to fetch user info:", err); }), ); } await Promise.all(tasks); return admin; }; export const auth = $state({ loggedIn: false, busy: false, err: null as string | null, server: "", user: "", admin: false, lastError: null as unknown, authFailed: false, autoLogin: !!getSessionCredentials(), }); export const markAuthFailed = () => { Object.assign(auth, { loggedIn: false, admin: false, autoLogin: false, authFailed: true, }); offlineManager.setOffline(); }; export const login = async ( server: string, username: string, secret?: string, authMode: "token" | "password" | "apikey" | "auto" = "auto", ) => { if (deploymentConfig.serverLock && deploymentConfig.server) { server = deploymentConfig.server; } if (authMode === "apikey") { username = ""; } server = normalizeServer(server); username = username.trim(); auth.busy = true; auth.err = null; auth.lastError = null; auth.authFailed = false; try { if (secret) { const tryAuth = async (mode: "token" | "password" | "apikey") => { const creds = mode === "apikey" ? { apiKey: secret } : mode === "password" ? { username, password: secret } : { username, ...(await createToken(secret)) }; setCredentials({ server, authMode: mode, ...creds }); await api.forcePing(); }; if (authMode === "auto") { try { await tryAuth("token"); } catch (err) { if (isNetworkError(err)) throw err; console.warn("token auth failed, trying cleartext:", err); await tryAuth("password"); } } else { await tryAuth(authMode); } } else { await api.forcePing(); } offlineManager.setOnline(true); await detectExtensions(); await offlineManager.autoSyncQueuedRequests(); if (authMode === "apikey" && extensions.has("apikeyauthentication")) { try { const info = await api.tokenInfo(); if (info?.tokenInfo?.username) { username = info.tokenInfo.username; } } catch (err) { console.warn("failed to fetch token info:", err); } } const admin = await loadSessionData(username); persistCredentials(); Object.assign(auth, { server, user: username, loggedIn: true, admin }); return true; } catch (err) { auth.lastError = err; auth.err = err instanceof Error ? err.message : String(err); return false; } finally { auth.busy = false; auth.autoLogin = false; } }; export const logout = async () => { setCredentials(null); localStorage.clear(); try { await offlineManager.wipeOfflineData(); } catch (err) { console.error("failed to wipe local cache on logout:", err); } finally { offlineManager.closeOfflineStore(); } location.reload(); }; export const isOwner = (owner?: string | null) => !owner || (!!auth.user && owner.toLowerCase() === auth.user.toLowerCase()); export const reconnectSession = async () => { try { await detectExtensions(); await offlineManager.autoSyncQueuedRequests(); auth.admin = await loadSessionData(auth.user); } catch (err) { if (isAuthenticationError(err)) { markAuthFailed(); } else { console.error("failed to reconnect session:", err); } } }; const tryStoredSession = async (credentials: Credentials) => { if ( await login( credentials.server, credentials.username || "", credentials.password || credentials.apiKey || "", credentials.authMode || "token", ) ) return true; if (isAuthenticationError(auth.lastError)) { markAuthFailed(); } else if (isNetworkError(auth.lastError)) { auth.err = null; auth.autoLogin = true; } return false; }; export const restoreSession = async (credentials: Credentials) => { setCredentials(credentials); Object.assign(auth, { server: credentials.server, user: credentials.username || "", err: null, autoLogin: true, authFailed: false, }); offlineManager.setOffline(); try { await Promise.all([loadLib(), syncQueue()]); } catch (err) { console.error("failed to load offline library during bootstrap:", err); } return tryStoredSession(credentials); }; export const retrySession = async (credentials: Credentials) => tryStoredSession(credentials);