/** Cliente HTTP único do painel: token, JSON, tempo limite e erro legível. */ const CHAVE_TOKEN = 'carinha_token'; export class ErroApi extends Error { constructor(public status: number, mensagem: string) { super(mensagem); } } export function lerToken(): string { try { return localStorage.getItem(CHAVE_TOKEN) || ''; } catch { return ''; } } export function gravarToken(token: string) { try { if (token) localStorage.setItem(CHAVE_TOKEN, token); else localStorage.removeItem(CHAVE_TOKEN); } catch { /* navegação privada: vale só nesta aba */ } } /** Avisado quando o servidor recusa o token (abre o diálogo de token). */ let aoNegar: () => void = () => {}; export function quandoNegado(fn: () => void) { aoNegar = fn; } export async function pedir(caminho: string, dados?: unknown, tempo = 8000): Promise { const cabecalhos: Record = {}; const token = lerToken(); if (token) cabecalhos.Authorization = 'Bearer ' + token; const opcoes: RequestInit = { cache: 'no-store', headers: cabecalhos, signal: AbortSignal.timeout(tempo) }; if (dados !== undefined) { opcoes.method = 'POST'; cabecalhos['Content-Type'] = 'application/json'; opcoes.body = JSON.stringify(dados); } let resposta: Response; try { resposta = await fetch(caminho, opcoes); } catch (e) { const nome = (e as Error).name; throw new ErroApi(0, nome === 'TimeoutError' ? 'O servidor demorou a responder' : 'Sem conexão com o servidor'); } const texto = await resposta.text(); let corpo: any = null; try { corpo = texto ? JSON.parse(texto) : null; } catch { corpo = null; } if (!resposta.ok) { if (resposta.status === 401) aoNegar(); throw new ErroApi(resposta.status, corpo?.erro || `Erro ${resposta.status}`); } return corpo as T; } export const postar = (caminho: string, dados: unknown = {}) => pedir(caminho, dados); export function urlEventos(): string { const token = lerToken(); return token ? '/eventos?t=' + encodeURIComponent(token) : '/eventos'; }