Files
lux_fiscal/frontend/src/api/tokenStore.ts
T
lauadminandClaude Sonnet 5 2664cb8213 Initial commit: backend scaffold, auth, frontend login
- FastAPI + SQLAlchemy async + Alembic + Postgres backend
- Auth: JWT access + rotating refresh tokens, argon2, roles, audit log
- React 19 + Vite frontend: login page, protected route, auth context
- Docker Compose: postgres, redis, migrate, api, worker (placeholder), frontend/nginx

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 15:07:15 +03:00

84 lines
2.6 KiB
TypeScript

/**
* Хранилище токенов вне 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<Listener>()
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)
}