music on atproto
plyr.fm
1// auth state management using Svelte 5 runes
2import { browser } from '$app/environment';
3import { API_URL } from '$lib/config';
4import { toast } from '$lib/toast.svelte';
5import type { User } from '$lib/types';
6
7export interface AuthState {
8 user: User | null;
9 isAuthenticated: boolean;
10 loading: boolean;
11 scopeUpgradeRequired: boolean;
12}
13
14class AuthManager {
15 user = $state<User | null>(null);
16 isAuthenticated = $state(false);
17 loading = $state(true);
18 scopeUpgradeRequired = $state(false);
19
20 async initialize(): Promise<void> {
21 if (!browser) {
22 this.loading = false;
23 return;
24 }
25
26 try {
27 const response = await fetch(`${API_URL}/auth/me`, {
28 credentials: 'include'
29 });
30
31 if (response.ok) {
32 this.user = await response.json();
33 this.isAuthenticated = true;
34 this.scopeUpgradeRequired = false;
35 } else if (response.status === 403) {
36 // check if this is a scope upgrade requirement
37 const data = await response.json().catch(() => ({}));
38 if (data.detail === 'scope_upgrade_required') {
39 this.scopeUpgradeRequired = true;
40 this.clearSession();
41 toast.info(
42 "plyr.fm's permissions have changed since you logged in. please log in again",
43 5000,
44 { label: 'see changes', href: 'https://github.com/zzstoatzz/plyr.fm/releases/latest' }
45 );
46 } else {
47 this.clearSession();
48 }
49 } else {
50 this.clearSession();
51 }
52 } catch (e) {
53 console.error('auth check failed:', e);
54 this.clearSession();
55 } finally {
56 this.loading = false;
57 }
58 }
59
60 clearSession(): void {
61 if (!browser) return;
62 this.user = null;
63 this.isAuthenticated = false;
64 }
65
66 async logout(): Promise<void> {
67 try {
68 await fetch(`${API_URL}/auth/logout`, {
69 method: 'POST',
70 credentials: 'include'
71 });
72 } catch (e) {
73 console.error('logout failed:', e);
74 }
75 this.clearSession();
76 }
77
78 getAuthHeaders(): Record<string, string> {
79 return {};
80 }
81}
82
83export const auth = new AuthManager();