// src/lib/api-base.ts // Server-only helper: resolve the backend API base URL at runtime. // // Why: // The frontend proxies /api/* to the backend via NEXT_PUBLIC_API_URL / // INTERNAL_API_URL. In Docker Swarm deployments these may point to a // task-instance hostname like `backone-inspect-backend-bun-0:3001`. When the // Swarm service is redeployed the task index can change (`-0` -> `-1`), orphaning // the configured URL and breaking every proxied request ("fetch failed"). // // Solution: // Probe candidates in order and cache the first healthy one. Also auto-derive // the stable service alias by stripping a trailing `-` suffix, so a // stale `-0` URL transparently recovers to the plain service DNS name. let cachedBase: string | null = null; function pushUnique(list: string[], value?: string | null) { if (!value) return; const clean = value.replace(/\/+$/, ''); if (clean && !list.includes(clean)) list.push(clean); } function buildCandidates(): string[] { const list: string[] = []; pushUnique(list, process.env.INTERNAL_API_URL); pushUnique(list, process.env.NEXT_PUBLIC_API_URL); // Derive stable Swarm service alias (strip task index: ...-0 -> ...) for (const base of [...list]) { try { const u = new URL(base); const strippedHost = u.host.replace(/-\d+$/, ''); if (strippedHost && strippedHost !== u.host) { pushUnique(list, `${u.protocol}//${strippedHost}`); } } catch { // ignore malformed URLs } } pushUnique(list, 'http://127.0.0.1:3001'); return list; } async function isHealthy(base: string): Promise { try { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 2500); const res = await fetch(`${base}/api/health`, { signal: ctrl.signal }); clearTimeout(timer); return res.ok; } catch { return false; } } export async function getApiBase(): Promise { if (cachedBase) return cachedBase; for (const base of buildCandidates()) { if (await isHealthy(base)) { cachedBase = base; return base; } } // Fallback: use the first configured candidate even if the probe failed. cachedBase = buildCandidates()[0] || 'http://127.0.0.1:3001'; return cachedBase; } export function resetApiBase() { cachedBase = null; }