// клиентский токен хаба: localStorage["sdlc-hub-token"] (со старого "sdlc-hub-token" переносится при чтении) и копия в памяти на случай // закрытого хранилища. reason — почему показан экран входа: "bad" — токен не подошёл (ответ 401) import { readMigrated } from "../api/legacyStorage"; export const TOKEN_KEY = "sdlc-hub-token"; export const TOKEN_RE = /^[\x21-\x7e]+$/; // печатный ASCII без пробелов: иначе fetch бросит TypeError на заголовке export type LoginReason = "none" | "bad"; export interface AuthSnapshot { token: string; reason: LoginReason } const readStored = (): string => { return readMigrated(TOKEN_KEY) ?? ""; }; let state: AuthSnapshot = { token: readStored(), reason: "none" }; const listeners = new Set<() => void>(); const emit = (next: AuthSnapshot) => { state = next; listeners.forEach((l) => l()); }; export const getAuth = (): AuthSnapshot => state; export const getToken = (): string => state.token; export function setToken(token: string): void { try { localStorage.setItem(TOKEN_KEY, token); } catch { /* закрытое хранилище: токен только в памяти */ } emit({ token, reason: "none" }); } export function clearToken(reason: LoginReason = "none"): void { try { localStorage.removeItem(TOKEN_KEY); } catch { /* закрытое хранилище */ } emit({ token: "", reason }); } export function subscribe(fn: () => void): () => void { listeners.add(fn); return () => { listeners.delete(fn); }; }