/** Тонкий клиент к API Synapse. Токен — opaque Bearer от gnexus-auth. */
const TOKEN_KEY = "synapse.access_token";
export function getToken() {
try {
return localStorage.getItem(TOKEN_KEY) || "";
} catch {
return "";
}
}
export function setToken(token) {
try {
localStorage.setItem(TOKEN_KEY, token);
} catch {
/* приватный режим — жить без сохранения сессии */
}
}
export function clearToken() {
try {
localStorage.removeItem(TOKEN_KEY);
} catch {
/* игнорируем */
}
}
async function apiFetch(path, options = {}) {
const headers = { ...(options.headers || {}) };
const token = getToken();
if (token) headers.Authorization = `Bearer ${token}`;
if (options.body) headers["Content-Type"] = "application/json";
const response = await fetch(path, { ...options, headers });
if (!response.ok) {
let detail = response.statusText;
try {
const payload = await response.json();
if (payload.detail) detail = payload.detail;
} catch {
/* не JSON — берём statusText */
}
const error = new Error(detail);
error.status = response.status;
throw error;
}
return response.json();
}
export function healthz() {
return apiFetch("/api/healthz");
}
/** Профиль текущего пользователя. 401 — токена нет/невалиден, 403 — роль ниже admin. */
export function adminMe() {
return apiFetch("/api/v1/admin/me");
}