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

97 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)),
}