Spaces:
Sleeping
Sleeping
File size: 4,468 Bytes
bc45e54 034d344 bc45e54 034d344 bc45e54 034d344 bc45e54 034d344 bc45e54 c6bdd55 bc45e54 0c45ae1 bc45e54 0c45ae1 bc45e54 c2c3b04 bc45e54 f66acbf bc45e54 f66acbf bc45e54 25cb9ab bc45e54 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | import { API_BASE_URL } from "./config";
const TOKEN_KEY = "classlens_token";
// Student and teacher logins share one token slot, so we track which kind of
// account it belongs to — lets the app pick the right "am I logged in" check
// (student vs teacher) before the first API call comes back.
const ROLE_KEY = "classlens_role";
export type UserRole = "student" | "teacher";
export function getToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
}
export function setToken(token: string, role: UserRole) {
localStorage.setItem(TOKEN_KEY, token);
localStorage.setItem(ROLE_KEY, role);
}
export function getRole(): UserRole | null {
const role = localStorage.getItem(ROLE_KEY);
return role === "student" || role === "teacher" ? role : null;
}
export function clearToken() {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(ROLE_KEY);
}
function authHeaders(): Record<string, string> {
const token = getToken();
return token ? { Authorization: `Bearer ${token}` } : {};
}
async function handleResponse<T>(res: Response): Promise<T> {
if (res.status === 401) {
// Only treat this as an expired session (clear + reload) if the request
// actually carried a token. A login/register call sends no token, so a
// 401 there means "wrong credentials," not "session expired" — let it
// fall through so the caller can show the real error message instead of
// silently reloading the login page.
if (getToken()) {
clearToken();
window.location.reload();
throw new Error("Unauthorized");
}
}
if (!res.ok) {
const text = await res.text();
let msg = text;
try {
const j: unknown = JSON.parse(text);
if (j && typeof j === "object") {
const obj = j as { detail?: unknown; message?: unknown };
const detail = obj.detail ?? obj.message;
if (typeof detail === "string") msg = detail;
}
} catch {
// Response body wasn't JSON — fall back to the raw text already in msg.
}
throw new Error(msg);
}
return (await res.json()) as T;
}
export async function apiGet<T>(path: string): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
headers: { ...authHeaders() },
});
return handleResponse<T>(res);
}
export async function apiPost<T>(path: string, body?: unknown): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "POST",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: body ? JSON.stringify(body) : undefined,
});
return handleResponse<T>(res);
}
export async function apiPatch<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "PATCH",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: JSON.stringify(body),
});
return handleResponse<T>(res);
}
export async function apiPut<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "PUT",
headers: { "Content-Type": "application/json", ...authHeaders() },
body: JSON.stringify(body),
});
return handleResponse<T>(res);
}
export async function apiDelete<T = void>(path: string): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "DELETE",
headers: { ...authHeaders() },
});
if (res.status === 401) {
clearToken();
window.location.reload();
throw new Error("Unauthorized");
}
if (!res.ok) {
const text = await res.text();
throw new Error(text);
}
const ct = res.headers.get("content-type") || "";
if (ct.includes("application/json")) return res.json() as Promise<T>;
return undefined as T;
}
/** Fetches a binary response (e.g. a stored PDF/image) as a blob, attaching
* the same bearer auth header as the JSON helpers above — a plain <img>/
* <iframe> src can't carry that header, so callers create an object URL from
* this instead. */
export async function apiGetBlob(path: string): Promise<Blob> {
const res = await fetch(`${API_BASE_URL}${path}`, {
headers: { ...authHeaders() },
});
if (!res.ok) throw new Error(await res.text());
return res.blob();
}
export async function apiUpload<T>(path: string, formData: FormData): Promise<T> {
const res = await fetch(`${API_BASE_URL}${path}`, {
method: "POST",
headers: { ...authHeaders() },
body: formData,
});
return handleResponse<T>(res);
}
|