import { computed, ref } from "vue"; import { loginRequest, logoutRequest, refreshTokenRequest } from "../api/auth.js"; import { setAccessToken, setRefreshCallback, setAuthErrorCallback } from "../api/client.js"; import { resetChatSingleton } from "./useChat.js"; import { resetConversasSingleton } from "./useConversas.js"; import { resetUsersSingleton } from "./useUsers.js"; const LOCAL_STORAGE_KEY = "auth.session"; const SESSION_STORAGE_KEY = "auth.session.temp"; function getStorage(kind) { if (typeof window === "undefined") return null; return kind === "local" ? window.localStorage : window.sessionStorage; } function loadStoredSession() { for (const kind of ["local", "session"]) { try { const storage = getStorage(kind); const raw = storage?.getItem(kind === "local" ? LOCAL_STORAGE_KEY : SESSION_STORAGE_KEY); if (!raw) continue; const parsed = JSON.parse(raw); if (parsed?.usuario) return parsed; } catch {} } return null; } function persistSession(sess) { try { getStorage("local")?.removeItem(LOCAL_STORAGE_KEY); getStorage("session")?.removeItem(SESSION_STORAGE_KEY); if (sess?.rememberMe) { getStorage("local")?.setItem(LOCAL_STORAGE_KEY, JSON.stringify(sess)); return; } if (sess) { getStorage("session")?.setItem(SESSION_STORAGE_KEY, JSON.stringify(sess)); } } catch {} } export const NIVEL_ADMIN = 3; const session = ref(loadStoredSession()); const user = computed(() => session.value?.usuario ?? null); const isAuthenticated = computed(() => Boolean(user.value?.Id)); const nivel = computed(() => Number(user.value?.Nivel ?? 0)); const isAdmin = computed(() => nivel.value >= NIVEL_ADMIN); let _proactiveRefreshTimer = null; let _refreshInFlight = null; let _authEpoch = 0; function doRefresh() { if (_refreshInFlight) return _refreshInFlight; const epoch = _authEpoch; _refreshInFlight = (async () => { const rt = session.value?.refreshToken; if (!rt) throw new Error("no_refresh_token"); const result = await refreshTokenRequest({ refreshToken: rt }); // logout()/authErrorCallback podem ter rodado enquanto o refresh estava em // voo — não reaplicar sessão/token de um ciclo de auth que já terminou if (epoch !== _authEpoch) throw new Error("auth_epoch_stale"); session.value = { ...session.value, accessToken: result.accessToken, refreshToken: result.refreshToken ?? session.value?.refreshToken ?? null }; persistSession(session.value); setAccessToken(result.accessToken); scheduleProactiveRefresh(result.accessToken); return result.accessToken; })().finally(() => { _refreshInFlight = null; }); return _refreshInFlight; } function scheduleProactiveRefresh(token) { clearTimeout(_proactiveRefreshTimer); if (!token) return; try { const payload = JSON.parse(atob(token.split(".")[1])); const delay = payload.exp * 1000 - Date.now() - 60_000; if (delay <= 0) return; _proactiveRefreshTimer = setTimeout(() => { doRefresh().catch(() => { }); }, delay); } catch { } } function setupAuth() { const token = session.value?.accessToken ?? null; setAccessToken(token); if (!token) return; scheduleProactiveRefresh(token); setRefreshCallback(doRefresh); setAuthErrorCallback(() => { _authEpoch++; clearTimeout(_proactiveRefreshTimer); resetChatSingleton(); resetConversasSingleton(); resetUsersSingleton(); session.value = null; persistSession(null); setAccessToken(null); setRefreshCallback(null); setAuthErrorCallback(null); window.location.href = "/login?redirect=" + encodeURIComponent(window.location.pathname + window.location.search); }); } setupAuth(); async function login({ login, senha, rememberMe = false }) { const payload = await loginRequest({ login, senha, rememberMe }); session.value = { usuario: payload.usuario, accessToken: payload.accessToken ?? null, refreshToken: payload.refreshToken ?? null, rememberMe, savedAt: new Date().toISOString() }; persistSession(session.value); setupAuth(); return payload; } async function logout() { _authEpoch++; clearTimeout(_proactiveRefreshTimer); const rt = session.value?.refreshToken; try { await logoutRequest({ refreshToken: rt }); } finally { resetChatSingleton(); resetConversasSingleton(); resetUsersSingleton(); session.value = null; persistSession(null); setAccessToken(null); setRefreshCallback(null); setAuthErrorCallback(null); } } export function useAuth() { return { user, nivel, isAdmin, isAuthenticated, login, logout }; }