import { CONFIG } from "./config.js"; import { loadSession, saveSession, clearSession } from "./storage.js"; function urlJoin(base, path) { if (!base) return path; return base.replace(/\/+$/, "") + "/" + path.replace(/^\/+/, ""); } async function parseResponse(res) { const ct = (res.headers.get("content-type") || "").toLowerCase(); if (ct.includes("application/json")) return await res.json(); const text = await res.text(); try { return JSON.parse(text); } catch { return text; } } function applyAuthHeaders(reqHeaders, session) { const t = String(session.short_token); reqHeaders["short_token"] = t; // контракт твоего API reqHeaders["Authorization"] = `Bearer ${t}`; // дубль (не мешает, пригодится) } function filenameFromDisposition(cd) { if (!cd) return null; let m = cd.match(/filename\*\s*=\s*UTF-8''([^;]+)/i); if (m?.[1]) return decodeURIComponent(m[1].replace(/"/g, "").trim()); m = cd.match(/filename\s*=\s*"?([^";]+)"?/i); if (m?.[1]) return m[1].trim(); return null; } export async function renewTokens() { const s = loadSession(); if (!s?.user_id || !s?.live_token) { clearSession(); throw new Error("NO_SESSION"); } const res = await fetch(urlJoin(CONFIG.API_BASE_URL, CONFIG.ENDPOINTS.renew), { method: "GET", headers: { "user_id": String(s.user_id), "live_token": String(s.live_token), // дубли (на случай фильтрации) — сервер может игнорировать "user-id": String(s.user_id), "live-token": String(s.live_token), "X-User-Id": String(s.user_id), "X-Live-Token": String(s.live_token), } }); if (res.status === CONFIG.AUTH_ERROR_STATUS) { clearSession(); throw new Error("RENEW_DENIED"); } if (!res.ok) { const body = await parseResponse(res); const err = new Error("RENEW_FAILED"); err.status = res.status; err.payload = body; throw err; } const data = await parseResponse(res); if (!data?.short_token || !data?.live_token) { const err = new Error("BAD_RENEW_RESPONSE"); err.payload = data; throw err; } const next = { ...s, short_token: data.short_token, live_token: data.live_token }; saveSession(next); return next; } export async function apiFetch(path, { method = "GET", headers = {}, body = null, auth = true } = {}) { const s = loadSession(); const reqHeaders = { ...headers }; if (auth) { if (!s?.short_token) throw new Error("NO_AUTH"); applyAuthHeaders(reqHeaders, s); } const doRequest = async () => { const finalHeaders = { ...reqHeaders, ...(body ? { "content-type": "application/json" } : {}) }; return await fetch(urlJoin(CONFIG.API_BASE_URL, path), { method, headers: finalHeaders, body: body ? JSON.stringify(body) : null }); }; let res = await doRequest(); if (auth && res.status === CONFIG.AUTH_ERROR_STATUS) { await renewTokens(); const s2 = loadSession(); applyAuthHeaders(reqHeaders, s2); res = await doRequest(); } const payload = await parseResponse(res); if (!res.ok) { const err = new Error("API_ERROR"); err.status = res.status; err.payload = payload; throw err; } return payload; } export async function apiFetchBlob(path, { method = "GET", headers = {}, auth = true } = {}) { const s = loadSession(); const reqHeaders = { ...headers }; if (auth) { if (!s?.short_token) throw new Error("NO_AUTH"); applyAuthHeaders(reqHeaders, s); } const doRequest = async () => { return await fetch(urlJoin(CONFIG.API_BASE_URL, path), { method, headers: reqHeaders }); }; let res = await doRequest(); if (auth && res.status === CONFIG.AUTH_ERROR_STATUS) { await renewTokens(); const s2 = loadSession(); applyAuthHeaders(reqHeaders, s2); res = await doRequest(); } if (!res.ok) { const text = await res.text().catch(() => ""); const err = new Error("API_BLOB_ERROR"); err.status = res.status; err.payload = text; throw err; } const blob = await res.blob(); const filename = filenameFromDisposition(res.headers.get("content-disposition")); return { blob, filename }; }