import { useSyncExternalStore } from "react"; import * as Sentry from "@sentry/react"; import type { User } from "@rubis/shared"; /** * Auth store — token en mémoire seulement (pas localStorage). * Le refresh token vit en cookie httpOnly côté API, invisible ici. * Cf. ADR-017 et /docs/tech/frontend.md §7. * * Sentry user context : on attache `user.id` (pas l'email, pas le nom * — minimisation PII) sur les events Sentry au login, et on clear au * logout. Permet de corréler une stack trace à un user spécifique sans * leak d'info personnelle. */ type AuthState = { accessToken: string | null; user: User | null; }; class AuthStore { private state: AuthState = { accessToken: null, user: null }; private listeners = new Set<() => void>(); getSnapshot = (): AuthState => this.state; get token(): string | null { return this.state.accessToken; } get user(): User | null { return this.state.user; } isAuthenticated(): boolean { return this.state.accessToken !== null; } setSession(accessToken: string, user: User): void { this.state = { accessToken, user }; Sentry.setUser({ id: user.id }); this.notify(); } clear(): void { this.state = { accessToken: null, user: null }; Sentry.setUser(null); this.notify(); } subscribe = (fn: () => void): (() => void) => { this.listeners.add(fn); return () => { this.listeners.delete(fn); }; }; private notify(): void { this.listeners.forEach((fn) => fn()); } } export const authStore = new AuthStore(); /** Hook React pour s'abonner à l'état d'auth. */ export function useAuth(): AuthState { return useSyncExternalStore(authStore.subscribe, authStore.getSnapshot, authStore.getSnapshot); }