This commit is contained in:
Arseniy Romenskiy 2025-12-27 05:27:39 +03:00
parent 676172adc5
commit 96fedf1d3b

View file

@ -9,21 +9,46 @@ function urlJoin(base, path) {
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();
// иногда сервер шлёт json как text/plain
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) throw new Error("NO_SESSION");
if (!s?.user_id || !s?.live_token) {
clearSession();
throw new Error("NO_SESSION");
}
// /renew/ ждёт user_id и live_token в headers :contentReference[oaicite:1]{index=1}
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)
"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),
}
});
@ -34,12 +59,17 @@ export async function renewTokens() {
if (!res.ok) {
const body = await parseResponse(res);
throw new Error(typeof body === "string" ? body : "RENEW_FAILED");
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) {
throw new Error("BAD_RENEW_RESPONSE");
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 };
@ -54,34 +84,32 @@ export async function apiFetch(path, {
auth = true
} = {}) {
const s = loadSession();
const reqHeaders = { ...headers };
if (auth) {
if (!s?.short_token) throw new Error("NO_AUTH");
// Все защищённые ручки проверяют short_token в headers :contentReference[oaicite:2]{index=2}
reqHeaders["short_token"] = String(s.short_token);
applyAuthHeaders(reqHeaders, s);
}
const doRequest = async () => {
const res = await fetch(urlJoin(CONFIG.API_BASE_URL, path), {
const finalHeaders = {
...reqHeaders,
...(body ? { "content-type": "application/json" } : {})
};
return await fetch(urlJoin(CONFIG.API_BASE_URL, path), {
method,
headers: {
...reqHeaders,
...(body ? { "content-type": "application/json" } : {})
},
headers: finalHeaders,
body: body ? JSON.stringify(body) : null
});
return res;
};
let res = await doRequest();
// Авто-обновление токенов при 426 :contentReference[oaicite:3]{index=3}
if (auth && res.status === CONFIG.AUTH_ERROR_STATUS) {
await renewTokens();
// обновим заголовок short_token и повторим один раз
const s2 = loadSession();
reqHeaders["short_token"] = String(s2.short_token);
applyAuthHeaders(reqHeaders, s2);
res = await doRequest();
}
@ -95,3 +123,45 @@ export async function apiFetch(path, {
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 };
}