refactor(web/auth): extract user state to break auth<->player cycle (B2)
This commit is contained in:
@@ -2,26 +2,23 @@ import { api, type User, type LoginResponse } from '$lib/api/client';
|
|||||||
import { queryClient } from '$lib/query/client';
|
import { queryClient } from '$lib/query/client';
|
||||||
import { clearPersistedQueue } from '$lib/player/persisted';
|
import { clearPersistedQueue } from '$lib/player/persisted';
|
||||||
import { playQueue, closeQueueDrawer } from '$lib/player/store.svelte';
|
import { playQueue, closeQueueDrawer } from '$lib/player/store.svelte';
|
||||||
|
import { user, setUser } from './user.svelte';
|
||||||
|
|
||||||
let _user = $state<User | null>(null);
|
// Re-export so existing `import { user } from '$lib/auth/store.svelte'`
|
||||||
|
// callers keep working. New code can import directly from auth/user.svelte.
|
||||||
export const user = {
|
export { user };
|
||||||
get value(): User | null {
|
|
||||||
return _user;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function bootstrap(): Promise<void> {
|
export async function bootstrap(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
_user = await api.get<User>('/api/me');
|
setUser(await api.get<User>('/api/me'));
|
||||||
} catch {
|
} catch {
|
||||||
_user = null;
|
setUser(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function login(username: string, password: string): Promise<void> {
|
export async function login(username: string, password: string): Promise<void> {
|
||||||
const res = await api.post<LoginResponse>('/api/auth/login', { username, password });
|
const res = await api.post<LoginResponse>('/api/auth/login', { username, password });
|
||||||
_user = res.user;
|
setUser(res.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function register(opts: {
|
export async function register(opts: {
|
||||||
@@ -37,7 +34,7 @@ export async function register(opts: {
|
|||||||
if (opts.inviteToken) body.invite_token = opts.inviteToken;
|
if (opts.inviteToken) body.invite_token = opts.inviteToken;
|
||||||
if (opts.displayName) body.display_name = opts.displayName;
|
if (opts.displayName) body.display_name = opts.displayName;
|
||||||
const res = await api.post<LoginResponse>('/api/auth/register', body);
|
const res = await api.post<LoginResponse>('/api/auth/register', body);
|
||||||
_user = res.user;
|
setUser(res.user);
|
||||||
await bootstrap();
|
await bootstrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +47,7 @@ export async function resetPassword(token: string, newPassword: string): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function logout(opts: { silent?: boolean } = {}): Promise<void> {
|
export async function logout(opts: { silent?: boolean } = {}): Promise<void> {
|
||||||
const userId = _user?.id;
|
const userId = user.value?.id;
|
||||||
if (!opts.silent) {
|
if (!opts.silent) {
|
||||||
try {
|
try {
|
||||||
await api.post('/api/auth/logout', {});
|
await api.post('/api/auth/logout', {});
|
||||||
@@ -58,7 +55,7 @@ export async function logout(opts: { silent?: boolean } = {}): Promise<void> {
|
|||||||
// best effort — server-side session may already be gone
|
// best effort — server-side session may already be gone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_user = null;
|
setUser(null);
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
// M7 #364: clear queue persistence + reset in-memory queue + close drawer
|
// M7 #364: clear queue persistence + reset in-memory queue + close drawer
|
||||||
// so a subsequent login doesn't inherit the previous user's playback state.
|
// so a subsequent login doesn't inherit the previous user's playback state.
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// User state lives here so auth/store and player/store can share it
|
||||||
|
// without a circular import. auth/store mutates via setUser; player/store
|
||||||
|
// reads only. Both auth-flow consumers (login/register/logout) and any
|
||||||
|
// component that needs the current user can import `user` from either
|
||||||
|
// this module or auth/store — auth/store re-exports it for backward
|
||||||
|
// compatibility.
|
||||||
|
import type { User } from '$lib/api/client';
|
||||||
|
|
||||||
|
let _user = $state<User | null>(null);
|
||||||
|
|
||||||
|
export const user = {
|
||||||
|
get value(): User | null {
|
||||||
|
return _user;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setUser(u: User | null): void {
|
||||||
|
_user = u;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { TrackRef, RadioResponse } from '$lib/api/types';
|
import type { TrackRef, RadioResponse } from '$lib/api/types';
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
import { api } from '$lib/api/client';
|
import { api } from '$lib/api/client';
|
||||||
import { user } from '$lib/auth/store.svelte';
|
import { user } from '$lib/auth/user.svelte';
|
||||||
import { readPersistedQueue, writePersistedQueue } from './persisted';
|
import { readPersistedQueue, writePersistedQueue } from './persisted';
|
||||||
|
|
||||||
export type PlayerState = 'idle' | 'loading' | 'playing' | 'paused' | 'error';
|
export type PlayerState = 'idle' | 'loading' | 'playing' | 'paused' | 'error';
|
||||||
@@ -397,10 +397,7 @@ $effect.root(() => {
|
|||||||
// queue/index changes drive it. The throttled-write effect below
|
// queue/index changes drive it. The throttled-write effect below
|
||||||
// covers position drift.
|
// covers position drift.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// `user?.value` (not `user.value`) guards against module-init order during
|
const userId = user.value?.id;
|
||||||
// tests: auth/store.svelte.ts imports from this file, so when this module
|
|
||||||
// loads via that chain, `user` may not yet be assigned. See I2 follow-up.
|
|
||||||
const userId = user?.value?.id;
|
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
// Reading these registers the reactive dependency; the values
|
// Reading these registers the reactive dependency; the values
|
||||||
// themselves are unused — writePersistedQueue re-reads live state.
|
// themselves are unused — writePersistedQueue re-reads live state.
|
||||||
@@ -416,7 +413,7 @@ $effect.root(() => {
|
|||||||
|
|
||||||
// Throttled write on position changes.
|
// Throttled write on position changes.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const userId = user?.value?.id;
|
const userId = user.value?.id;
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
const _p = _position;
|
const _p = _position;
|
||||||
void _p;
|
void _p;
|
||||||
|
|||||||
Reference in New Issue
Block a user