2f8a5d84dc
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 1m41s
- Fix WEBAUTHN_ORIGIN -> WEBAUTHN_ORIGINS env var name - Add null check for cancelled passkey creation - Use finally block to ensure loading state is always reset - Add console.error for better debugging
200 lines
4.8 KiB
JavaScript
200 lines
4.8 KiB
JavaScript
const BASE = "/api";
|
|
let token = "";
|
|
|
|
// Current user info populated after auth
|
|
export let currentUser = { username: "", role: "", pages: [] };
|
|
|
|
export function setCurrentUser(u) {
|
|
currentUser = u;
|
|
}
|
|
|
|
export function setToken(t) {
|
|
token = t;
|
|
if (t) localStorage.setItem("token", t);
|
|
else localStorage.removeItem("token");
|
|
}
|
|
|
|
export function getToken() {
|
|
return token;
|
|
}
|
|
|
|
// Refresh token is cookie-managed server-side.
|
|
export function setRefreshToken() {}
|
|
|
|
let refreshPromise = null;
|
|
|
|
function getLegacyRefreshToken() {
|
|
return localStorage.getItem("refresh_token") || "";
|
|
}
|
|
|
|
function clearLegacyRefreshToken() {
|
|
localStorage.removeItem("refresh_token");
|
|
}
|
|
|
|
async function requestRefresh(body) {
|
|
const r = await fetch(BASE + "/auth/refresh", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "same-origin",
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (!r.ok) return false;
|
|
const data = await r.json();
|
|
setToken(data.access_token || "");
|
|
return !!data.access_token;
|
|
}
|
|
|
|
export async function tryRefreshSession() {
|
|
if (refreshPromise) return refreshPromise;
|
|
refreshPromise = (async () => {
|
|
try {
|
|
const cookieRefreshed = await requestRefresh();
|
|
if (cookieRefreshed) {
|
|
clearLegacyRefreshToken();
|
|
return true;
|
|
}
|
|
|
|
const legacyRefreshToken = getLegacyRefreshToken();
|
|
if (!legacyRefreshToken) return false;
|
|
const legacyRefreshed = await requestRefresh({ refresh_token: legacyRefreshToken });
|
|
if (legacyRefreshed) {
|
|
clearLegacyRefreshToken();
|
|
return true;
|
|
}
|
|
clearLegacyRefreshToken();
|
|
return false;
|
|
} catch {
|
|
return false;
|
|
} finally {
|
|
refreshPromise = null;
|
|
}
|
|
})();
|
|
return refreshPromise;
|
|
}
|
|
|
|
async function request(path, opts = {}) {
|
|
const headers = opts.headers || {};
|
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
|
|
if (opts.json) {
|
|
headers["Content-Type"] = "application/json";
|
|
opts.body = JSON.stringify(opts.json);
|
|
delete opts.json;
|
|
}
|
|
|
|
const fetchOpts = { ...opts, headers };
|
|
if (path.startsWith("/auth/")) fetchOpts.credentials = "same-origin";
|
|
|
|
try {
|
|
let r = await fetch(BASE + path, fetchOpts);
|
|
|
|
if (r.status === 401 && !path.includes("/auth/refresh")) {
|
|
const refreshed = await tryRefreshSession();
|
|
if (refreshed) {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
r = await fetch(BASE + path, fetchOpts);
|
|
}
|
|
}
|
|
|
|
if (r.status === 401) {
|
|
setToken("");
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
if (!r.ok) {
|
|
const error = new Error(`HTTP ${r.status}`);
|
|
error.status = r.status;
|
|
try { error.body = await r.json(); } catch {}
|
|
throw error;
|
|
}
|
|
return r.json();
|
|
} catch (e) {
|
|
console.error(`API ${opts.method || "GET"} ${path}:`, e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export function get(path) {
|
|
return request(path);
|
|
}
|
|
|
|
export function post(path, data) {
|
|
return request(path, { method: "POST", json: data });
|
|
}
|
|
|
|
export function del(path) {
|
|
return request(path, { method: "DELETE" });
|
|
}
|
|
|
|
export async function upload(path, file) {
|
|
const headers = {};
|
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
|
|
const form = new FormData();
|
|
form.append("file", file);
|
|
|
|
const r = await fetch(`${BASE}/files/upload?path=${encodeURIComponent(path)}`, {
|
|
method: "POST",
|
|
headers,
|
|
body: form,
|
|
});
|
|
|
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
return r.json();
|
|
}
|
|
|
|
export function login(creds) {
|
|
return request("/auth/login", { method: "POST", json: creds });
|
|
}
|
|
|
|
export function checkAuth() {
|
|
return request("/auth/me");
|
|
}
|
|
|
|
export function logout() {
|
|
return request("/auth/logout", { method: "POST" });
|
|
}
|
|
|
|
export function getPreferences() {
|
|
return get("/auth/preferences");
|
|
}
|
|
|
|
export function savePreferences(data) {
|
|
return request("/auth/preferences", { method: "PUT", json: data });
|
|
}
|
|
|
|
export function getLiteLLMHealth() {
|
|
return get("/litellm/health");
|
|
}
|
|
|
|
export function getCcConnectHealth() {
|
|
return get("/cc-connect/health");
|
|
}
|
|
|
|
export function startCcConnect() {
|
|
return post("/cc-connect/start");
|
|
}
|
|
|
|
export function stopCcConnect() {
|
|
return post("/cc-connect/stop");
|
|
}
|
|
|
|
export function getInfoEngineItems(params = {}) {
|
|
const query = new URLSearchParams();
|
|
if (params.limit !== undefined) query.set("limit", String(params.limit));
|
|
if (params.offset !== undefined) query.set("offset", String(params.offset));
|
|
if (params.source) query.set("source", params.source);
|
|
if (params.tag) query.set("tag", params.tag);
|
|
if (params.since) query.set("since", params.since);
|
|
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
return get(`/info-engine/items${suffix}`);
|
|
}
|
|
|
|
export function getInfoEngineItem(id) {
|
|
return get(`/info-engine/items/${id}`);
|
|
}
|
|
|
|
export function put(path, data) {
|
|
return request(path, { method: "PUT", json: data });
|
|
}
|