97 lines
3 KiB
JavaScript
Executable file
97 lines
3 KiB
JavaScript
Executable file
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();
|
||
// иногда сервер шлёт json как text/plain
|
||
try { return JSON.parse(text); } catch { return text; }
|
||
}
|
||
|
||
export async function renewTokens() {
|
||
const s = loadSession();
|
||
if (!s) 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)
|
||
}
|
||
});
|
||
|
||
if (res.status === CONFIG.AUTH_ERROR_STATUS) {
|
||
clearSession();
|
||
throw new Error("RENEW_DENIED");
|
||
}
|
||
|
||
if (!res.ok) {
|
||
const body = await parseResponse(res);
|
||
throw new Error(typeof body === "string" ? body : "RENEW_FAILED");
|
||
}
|
||
|
||
const data = await parseResponse(res);
|
||
if (!data?.short_token || !data?.live_token) {
|
||
throw new Error("BAD_RENEW_RESPONSE");
|
||
}
|
||
|
||
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");
|
||
// Все защищённые ручки проверяют short_token в headers :contentReference[oaicite:2]{index=2}
|
||
reqHeaders["short_token"] = String(s.short_token);
|
||
}
|
||
|
||
const doRequest = async () => {
|
||
const res = await fetch(urlJoin(CONFIG.API_BASE_URL, path), {
|
||
method,
|
||
headers: {
|
||
...reqHeaders,
|
||
...(body ? { "content-type": "application/json" } : {})
|
||
},
|
||
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);
|
||
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;
|
||
}
|