init
This commit is contained in:
83
frontend/src/utils/http.ts
Normal file
83
frontend/src/utils/http.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { getAccessToken } from "./storage";
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "/api";
|
||||
const DEFAULT_TIMEOUT = 10000;
|
||||
|
||||
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
|
||||
interface RequestOptions {
|
||||
method?: HttpMethod;
|
||||
body?: unknown;
|
||||
headers?: Record<string, string>;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
function buildUrl(path: string) {
|
||||
if (/^https?:\/\//.test(path)) {
|
||||
return path;
|
||||
}
|
||||
return `${BASE_URL}${path.startsWith("/") ? path : `/${path}`}`;
|
||||
}
|
||||
|
||||
async function parseResponse(response: Response) {
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type") ?? "";
|
||||
if (contentType.includes("application/json")) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = window.setTimeout(() => controller.abort(), options.timeout ?? DEFAULT_TIMEOUT);
|
||||
const token = getAccessToken();
|
||||
|
||||
try {
|
||||
const response = await fetch(buildUrl(path), {
|
||||
method: options.method ?? "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...options.headers
|
||||
},
|
||||
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
const payload = await parseResponse(response);
|
||||
if (!response.ok) {
|
||||
const message =
|
||||
typeof payload === "object" && payload !== null && "message" in payload
|
||||
? String(payload.message)
|
||||
: `Request failed with status ${response.status}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return payload as T;
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
export const http = {
|
||||
get<T>(path: string, options?: Omit<RequestOptions, "method" | "body">) {
|
||||
return request<T>(path, { ...options, method: "GET" });
|
||||
},
|
||||
post<T>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) {
|
||||
return request<T>(path, { ...options, method: "POST", body });
|
||||
},
|
||||
put<T>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) {
|
||||
return request<T>(path, { ...options, method: "PUT", body });
|
||||
},
|
||||
patch<T>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) {
|
||||
return request<T>(path, { ...options, method: "PATCH", body });
|
||||
},
|
||||
delete<T>(path: string, options?: Omit<RequestOptions, "method" | "body">) {
|
||||
return request<T>(path, { ...options, method: "DELETE" });
|
||||
}
|
||||
};
|
||||
13
frontend/src/utils/storage.ts
Normal file
13
frontend/src/utils/storage.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
const ACCESS_TOKEN_KEY = "k12study.access-token";
|
||||
|
||||
export function getAccessToken() {
|
||||
return localStorage.getItem(ACCESS_TOKEN_KEY);
|
||||
}
|
||||
|
||||
export function setAccessToken(token: string) {
|
||||
localStorage.setItem(ACCESS_TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
export function clearAccessToken() {
|
||||
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
||||
}
|
||||
Reference in New Issue
Block a user