Files
geo/apps/ops-web/src/lib/http.ts
T

98 lines
2.5 KiB
TypeScript
Raw Normal View History

import axios, { type AxiosError, type AxiosInstance } from "axios";
import { clearStoredSession, readStoredSession } from "@/lib/storage";
export class OpsApiError extends Error {
status?: number;
code: number;
detail?: string;
requestId?: string;
constructor(input: {
message: string;
code?: number;
status?: number;
detail?: string;
requestId?: string;
}) {
super(input.message);
this.name = "OpsApiError";
this.code = input.code ?? 50000;
this.status = input.status;
this.detail = input.detail;
this.requestId = input.requestId;
}
}
interface ApiEnvelope<T> {
code: number;
message: string;
detail?: string;
data: T;
request_id?: string;
}
let onUnauthorized: (() => void) | null = null;
export function bindUnauthorizedHandler(handler: () => void): void {
onUnauthorized = handler;
}
const client: AxiosInstance = axios.create({
baseURL: "/api/ops",
timeout: 30000,
});
client.interceptors.request.use((config) => {
const { session } = readStoredSession();
if (session.accessToken) {
config.headers = config.headers ?? {};
config.headers["Authorization"] = `Bearer ${session.accessToken}`;
}
return config;
});
client.interceptors.response.use(
(response) => response,
(error: AxiosError<ApiEnvelope<unknown>>) => {
const status = error.response?.status;
const payload = error.response?.data;
if (status === 401) {
clearStoredSession();
onUnauthorized?.();
}
return Promise.reject(
new OpsApiError({
message: payload?.message ?? error.message ?? "request_failed",
code: typeof payload?.code === "number" ? payload.code : undefined,
status,
detail: typeof payload?.detail === "string" ? payload.detail : undefined,
requestId: typeof payload?.request_id === "string" ? payload.request_id : undefined,
}),
);
},
);
async function unwrap<T>(promise: Promise<{ data: ApiEnvelope<T> }>): Promise<T> {
const { data } = await promise;
if (data.code !== 0) {
throw new OpsApiError({
message: data.message,
code: data.code,
detail: data.detail,
requestId: data.request_id,
});
}
return data.data;
}
export const http = {
get: <T>(url: string, params?: Record<string, unknown>) =>
unwrap<T>(client.get(url, { params })),
post: <T, B = unknown>(url: string, body?: B) => unwrap<T>(client.post(url, body)),
patch: <T, B = unknown>(url: string, body?: B) => unwrap<T>(client.patch(url, body)),
delete: <T>(url: string) => unwrap<T>(client.delete(url)),
};