fix
This commit is contained in:
parent
676172adc5
commit
96fedf1d3b
1 changed files with 88 additions and 18 deletions
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue