import axios, { AxiosError, type AxiosRequestConfig } from 'axios' import { isKbxProblem, kbxUnexpectedProblem, type KbxProblem, type KbxSystemProblem } from '@kbx/contracts' export const kbxHttp = axios.create({ timeout: 30_000, headers: { 'X-KBX-Client': 'web' }, }) kbxHttp.interceptors.request.use(config => { config.headers['X-Correlation-Id'] ??= crypto.randomUUID() return config }) kbxHttp.interceptors.response.use( response => response, (error: AxiosError) => { const correlationId = String(error.config?.headers?.['X-Correlation-Id'] ?? crypto.randomUUID()) if (!error.response) { const problem: KbxSystemProblem = { type: 'system', code: 'NETWORK_UNAVAILABLE', title: '서버와 연결할 수 없습니다.', detail: '입력한 내용은 유지됩니다. 네트워크 상태를 확인한 후 다시 시도하세요.', correlationId, retryable: true, } return Promise.reject(problem) } const data = error.response.data if (isKbxProblem(data)) { const problem: KbxProblem = { ...data, correlationId: data.correlationId ?? correlationId, } return Promise.reject(problem) } if (error.response.status === 403) { return Promise.reject({ type: 'permission', code: 'PERMISSION_DENIED', title: '이 작업을 수행할 권한이 없습니다.', correlationId, } satisfies KbxProblem) } if (error.response.status === 404) { return Promise.reject({ type: 'not-found', code: 'RESOURCE_NOT_FOUND', title: '요청한 데이터를 찾을 수 없습니다.', correlationId, } satisfies KbxProblem) } return Promise.reject(kbxUnexpectedProblem(correlationId, `HTTP ${error.response.status}`, error.response.status >= 500)) }, ) export function withIdempotency(config: AxiosRequestConfig = {}, key = crypto.randomUUID()): AxiosRequestConfig { return { ...config, headers: { ...(config.headers ?? {}), 'Idempotency-Key': key, }, } }