Files
nas-tools/dashboard/frontend/src/lib/api.js
T
Gan, Jimmy 2f8a5d84dc
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 1m41s
fix: WebAuthn config typo and improve error handling
- 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
2026-03-11 00:53:28 +08:00

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 });
}