import { fetchProjectInfo } from "./localClient.js"; import { getSessionCredentials, sessionCredentials, setMcpSessionCredentials, } from "./credentials.js"; const refreshInFlight = new Map(); export function isJwtExpiring(token, marginSeconds = 300) { try { const payload = token.split(".")[1]; if (!payload) return true; const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8")); return !data.exp || Math.floor(Date.now() / 1000) >= data.exp - marginSeconds; } catch { return true; } } export async function refreshSessionCredentials( sessionId, { force = false, fetcher = fetchProjectInfo } = {}, ) { const current = await getSessionCredentials(sessionId); if (!current?.project_dir) { throw new Error("Project dir no disponible en esta sesion"); } if (force && !current.mcp_secret && !process.env.ACAI_AUTH_HEADER) { throw new Error("No hay autenticacion interna para autorizar la renovacion forzada"); } const key = current.project_dir; const existing = refreshInFlight.get(key); if (existing) return existing; const operation = (async () => { let info; try { info = await fetcher( { project_dir: current.project_dir }, current.acai_user || null, { forceTokenRefresh: force, mcpSecret: force ? current.mcp_secret : null, }, ); } catch (error) { throw new Error(error.response?.data?.error || error.message); } if (!info?.success) { throw new Error(info?.error || "No se pudieron renovar las credenciales Acai"); } if (!info.token || isJwtExpiring(info.token, 0)) { throw new Error("El backend no devolvio un token Acai valido"); } const fresh = { ...current, token: info.token, tokenHash: info.tokenHash || "", website: info.domain || current.website, web_url: info.web_url || current.web_url, api_web_url: info.api_web_url || info.web_url || current.api_web_url, forge_host: info.forge_host ?? current.forge_host, project_dir: info.project_dir || current.project_dir, mode: info.mode || current.mode, }; sessionCredentials.set(sessionId, fresh); setMcpSessionCredentials(sessionId, fresh); return fresh; })(); refreshInFlight.set(key, operation); try { return await operation; } finally { if (refreshInFlight.get(key) === operation) { refreshInFlight.delete(key); } } } export async function ensureFreshSessionCredentials(sessionId) { const current = await getSessionCredentials(sessionId); if (!current?.token || !current.project_dir || !isJwtExpiring(current.token)) { return current; } return refreshSessionCredentials(sessionId); }