36 lines
1.6 KiB
TypeScript
36 lines
1.6 KiB
TypeScript
// клиентский токен хаба: 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); };
|
||
}
|