import { api, type User, type LoginResponse } from '#lib/api/client.js'; import { putMyTimezone } from '#lib/api/me.js'; import { queryClient } from '#lib/query/client.js'; import { signalSessionEnd } from './sessionEnd.svelte'; import { user, setUser } from './user.svelte'; // Re-export so existing `import { user } from '#lib/auth/store.svelte'` // callers keep working. New code can import directly from auth/user.svelte. export { user }; // Weekly client-driven cadence for sending the browser's current IANA // timezone to PUT /api/me/timezone (#392 Half B). Tracked in // localStorage so the cadence survives tab restarts; bumped after each // successful PUT. Failures are swallowed — server keeps its previous // value (or 'UTC' default). const TZ_LAST_SENT_KEY = 'minstrel.tz_last_sent_at'; const WEEKLY_MS = 7 * 24 * 60 * 60 * 1000; async function sendTimezoneIfStale(): Promise { if (typeof window === 'undefined') return; // SSR-safe no-op try { const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; if (!tz) return; const lastStr = window.localStorage.getItem(TZ_LAST_SENT_KEY); const last = lastStr ? Number(lastStr) : 0; if (Number.isFinite(last) && Date.now() - last < WEEKLY_MS) return; await putMyTimezone(tz); window.localStorage.setItem(TZ_LAST_SENT_KEY, String(Date.now())); } catch (err) { console.warn('tz send failed:', err); } } export async function bootstrap(): Promise { try { setUser(await api.get('/api/me')); // ignore: best-effort, runs in background void sendTimezoneIfStale(); } catch { setUser(null); } } export async function login(username: string, password: string): Promise { const res = await api.post('/api/auth/login', { username, password }); setUser(res.user); void sendTimezoneIfStale(); } /** * Whether the server has no accounts yet, in which case the first * registration must carry the setup token printed in the server log. */ export async function getSetupStatus(): Promise<{ setup_required: boolean }> { return api.get<{ setup_required: boolean }>('/api/auth/setup-status'); } export async function register(opts: { username: string; password: string; inviteToken?: string; setupToken?: string; displayName?: string; }): Promise { const body: Record = { username: opts.username, password: opts.password, }; if (opts.inviteToken) body.invite_token = opts.inviteToken; if (opts.setupToken) body.setup_token = opts.setupToken; if (opts.displayName) body.display_name = opts.displayName; const res = await api.post('/api/auth/register', body); setUser(res.user); await bootstrap(); void sendTimezoneIfStale(); } export async function forgotPassword(email: string): Promise { await api.post('/api/auth/forgot-password', { email }); } export async function resetPassword(token: string, newPassword: string): Promise { await api.post('/api/auth/reset-password', { token, new_password: newPassword }); } export async function logout(opts: { silent?: boolean } = {}): Promise { const userId = user.value?.id; if (!opts.silent) { try { await api.post('/api/auth/logout', {}); } catch { // best effort — server-side session may already be gone } } signalSessionEnd(userId ?? null); setUser(null); queryClient.clear(); }