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 { 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>) => { 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(promise: Promise<{ data: ApiEnvelope }>): Promise { 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: (url: string, params?: Record) => unwrap(client.get(url, { params })), post: (url: string, body?: B) => unwrap(client.post(url, body)), patch: (url: string, body?: B) => unwrap(client.patch(url, body)), delete: (url: string) => unwrap(client.delete(url)), }