/** * Хранилище токенов вне React-дерева. * * `client.ts` должен уметь читать access-токен и молча обновлять его по 401 * без импорта React-контекста (иначе получится цикл api -> auth-context -> * api). Поэтому токены живут в модульном синглтоне, а `AuthProvider` * подписывается на его изменения через `subscribe`. * * access-токен — только в памяти (переживает не дольше вкладки). * refresh-токен — в localStorage, иначе при обновлении страницы * пользователя выкидывало бы из системы каждый раз. */ const REFRESH_TOKEN_KEY = 'lux_fiscal.refresh_token' interface TokenState { accessToken: string | null refreshToken: string | null } let state: TokenState = { accessToken: null, refreshToken: readRefreshTokenFromStorage(), } type Listener = (state: TokenState) => void const listeners = new Set() function readRefreshTokenFromStorage(): string | null { try { return localStorage.getItem(REFRESH_TOKEN_KEY) } catch { // Приватный режим браузера может запрещать доступ к localStorage. return null } } function writeRefreshTokenToStorage(token: string | null): void { try { if (token) { localStorage.setItem(REFRESH_TOKEN_KEY, token) } else { localStorage.removeItem(REFRESH_TOKEN_KEY) } } catch { // Не критично: без localStorage просто не переживём перезагрузку страницы. } } function notify(): void { for (const listener of listeners) listener(state) } export function getAccessToken(): string | null { return state.accessToken } export function getRefreshToken(): string | null { return state.refreshToken } export function setTokens(accessToken: string, refreshToken: string): void { state = { accessToken, refreshToken } writeRefreshTokenToStorage(refreshToken) notify() } export function setAccessToken(accessToken: string): void { state = { ...state, accessToken } notify() } export function clearTokens(): void { state = { accessToken: null, refreshToken: null } writeRefreshTokenToStorage(null) notify() } /** Вызывается при монтировании AuthProvider и при каждом изменении токенов. */ export function subscribeToTokens(listener: Listener): () => void { listeners.add(listener) return () => listeners.delete(listener) }