type ApiError = { status: number; bodyText: string; }; function buildUrl(path: string) { const base = (import.meta.env.VITE_API_BASE_URL as string | undefined) ?? "/api"; if (path.startsWith("http://") || path.startsWith("https://")) return path; if (path.startsWith("/")) return `${base}${path}`; return `${base}/${path}`; } export async function apiJson(path: string, init?: RequestInit): Promise { const url = buildUrl(path); const res = await fetch(url, { ...init, headers: { "Content-Type": "application/json", ...(init?.headers ?? {}), }, }); if (!res.ok) { const bodyText = await res.text().catch(() => ""); const err: ApiError = { status: res.status, bodyText }; throw err; } return (await res.json()) as T; } function normalizeLoopbackHost(rawUrl: string): string { const cleaned = rawUrl.trim().replace(/^['"`\s]+|['"`\s]+$/g, ""); try { const url = new URL(cleaned, window.location.origin); const loopbacks = new Set(["127.0.0.1", "localhost", "::1"]); if (loopbacks.has(url.hostname) && !loopbacks.has(window.location.hostname)) { url.hostname = window.location.hostname; } return url.toString().replace(/\/+$/, ""); } catch { return cleaned.replace(/\/+$/, ""); } } export function getMediamtxWebrtcBaseUrl(configUrl?: string) { const env = import.meta.env.VITE_MEDIAMTX_WEBRTC_URL as string | undefined; return normalizeLoopbackHost(env ?? configUrl ?? "http://localhost:8889"); }