167 lines
4.7 KiB
JavaScript
Executable file
167 lines
4.7 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();
|
|
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 };
|
|
}
|