fabro/apps/fabro-web/app/api.ts
Bryan Helmkamp 84f3c80566
refactor(api): move features flags from /auth/me to /system/info
Features like session_sandboxes and retros are server-level capability
flags, not user settings. Expose them on GET /system/info where they
belong alongside other server metadata.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:51:22 -04:00

97 lines
2.6 KiB
TypeScript

export interface ApiOptions {
init?: RequestInit;
request?: Request;
}
export async function apiFetch(path: string, options?: ApiOptions): Promise<Response> {
const { init } = options ?? {};
const response = await fetch(`/api/v1${path}`, {
...init,
credentials: "include",
headers: init?.headers,
});
if (response.status === 401) {
window.location.href = "/login";
throw new Error("Unauthorized");
}
return response;
}
export async function apiJson<T>(path: string, options?: ApiOptions): Promise<T> {
const response = await apiFetch(path, options);
if (!response.ok) {
throw new Response(null, { status: response.status, statusText: response.statusText });
}
return response.json() as Promise<T>;
}
export function isNotAvailable(status: number): boolean {
return status === 404 || status === 501;
}
export async function apiJsonOrNull<T>(
path: string,
options?: ApiOptions,
): Promise<T | null> {
const response = await apiFetch(path, options);
if (isNotAvailable(response.status)) {
return null;
}
if (!response.ok) {
throw new Response(null, {
status: response.status,
statusText: response.statusText,
});
}
return response.json() as Promise<T>;
}
export async function getAuthConfig(): Promise<{ methods: string[] }> {
const response = await fetch("/api/v1/auth/config", { credentials: "include" });
if (!response.ok) {
throw new Response(null, { status: response.status, statusText: response.statusText });
}
return response.json();
}
export async function loginDevToken(token: string): Promise<{ ok: boolean }> {
const response = await fetch("/auth/login/dev-token", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token }),
});
if (!response.ok) {
throw new Response(null, { status: response.status, statusText: response.statusText });
}
return response.json();
}
export async function getAuthMe(): Promise<{
user: {
login: string;
name: string;
email: string;
avatarUrl: string;
userUrl: string;
};
provider: string;
demoMode: boolean;
}> {
const response = await fetch("/api/v1/auth/me", { credentials: "include" });
if (response.status === 401) {
throw new Response(null, { status: 401, statusText: "Unauthorized" });
}
if (!response.ok) {
throw new Response(null, { status: response.status, statusText: response.statusText });
}
return response.json();
}
export async function getSystemInfo(): Promise<{
features: { session_sandboxes: boolean; retros: boolean };
}> {
return apiJson("/system/info");
}