/** Stored API origin; trailing slashes stripped so paths join cleanly. */ export function apiBaseUrl(): string { return (localStorage.getItem("apiBase") || "").replace(/\/+$/, ""); } export function apiUrl(path: string): string { const b = apiBaseUrl(); const p = path.startsWith("/") ? path : `/${path}`; return b ? `${b}${p}` : p; } export async function apiGet(path: string): Promise { const r = await fetch(apiUrl(path)); if (!r.ok) { throw new Error(await r.text()); } return r.json() as Promise; } export async function apiPost(path: string, body: unknown): Promise { const r = await fetch(apiUrl(path), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!r.ok) { throw new Error(await r.text()); } return r.json() as Promise; } export type SessionInfo = { usedBytes: number; maxBytes: number; lines: number; full: boolean; deepCapture: boolean; }; export type Status = { app: string; uptimeMs: number; freeHeap: number; wifiMode: number; pn532?: { ic: number; fwHi: number; fwLo: number }; scanning: boolean; session?: SessionInfo; }; export type Tag = { uid: string; uidLen: number; atqa: number; sak: number; typeHint: number; /** ATQA as 4 hex digits (MSB first, same as firmware). */ atqaHex?: string; sakHex?: string; typeGuess?: string; /** PN532 `GetGeneralStatus` payload bytes (chip-dependent length). */ pn532GeneralStatus?: number[]; };