import { ApiError, toApiError } from "./errors"; const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL as string | undefined)?.trim() || "http://localhost:3001"; function buildUrl(path: string): string { const normalizedPath = path.startsWith("/") ? path : `/${path}`; return `${API_BASE_URL}${normalizedPath}`; } async function parseResponseBody(response: Response): Promise { const contentType = response.headers.get("content-type") ?? ""; if (!contentType.includes("application/json")) { return null; } try { return await response.json(); } catch { return null; } } const GATEWAY_RETRY_STATUSES = new Set([502, 503, 504]); function delay(ms: number): Promise { return new Promise((resolve) => { setTimeout(resolve, ms); }); } export async function requestJson(path: string, init?: RequestInit): Promise { const method = (init?.method ?? "GET").toUpperCase(); const idempotent = method === "GET" || method === "HEAD"; const maxAttempts = idempotent ? 3 : 1; for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { try { const response = await fetch(buildUrl(path), { ...init, headers: { "Content-Type": "application/json", ...(init?.headers ?? {}), }, }); if (response.status === 204) { return undefined as T; } const payload = await parseResponseBody(response); if (!response.ok) { const retryable = idempotent && GATEWAY_RETRY_STATUSES.has(response.status) && attempt < maxAttempts; if (retryable) { await delay(80 * attempt); continue; } // #region agent log fetch("http://127.0.0.1:7488/ingest/a18f912f-72c6-4a58-866b-17810a6b89d2", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "587ee5" }, body: JSON.stringify({ sessionId: "587ee5", hypothesisId: "H-http-not-ok", location: "http.ts:requestJson", message: "HTTP error response", data: { path, status: response.status, contentType: response.headers.get("content-type"), payloadIsObject: payload !== null && typeof payload === "object", }, timestamp: Date.now(), }), }).catch(() => {}); // #endregion throw toApiError(response.status, payload); } return payload as T; } catch (error) { if (error instanceof ApiError) { throw error; } if (error instanceof DOMException && error.name === "AbortError") { throw error; } const retryable = idempotent && attempt < maxAttempts; if (retryable) { await delay(80 * attempt); continue; } throw new ApiError({ code: "network_error", status: null, message: "Не удалось связаться с сервером.", }); } } throw new ApiError({ code: "network_error", status: null, message: "Не удалось связаться с сервером.", }); }