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