Something went wrong. Try again.
a simple but full-featured web player for OpenSubsonic music servers (Navidrome, Gonic, Ampache, etc) devins.page/tinysub
svelte typescript javascript subsonic gonic navidrome
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218import { 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<unknown>[] = [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);