import axios, {type AxiosRequestConfig, type AxiosResponse} from 'axios'; import type {ApiResponse} from "../type/apiResponse.ts"; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? ''; const ADMIN_LOGIN_URL = '/uat/uia/actionSecurityLogin.do'; const ADMIN_ID = 'admin'; const ADMIN_PASSWORD = '1'; const MAX_RETRY_COUNT = 3; class ApiClient { private loginPromise: Promise | null = null; private readonly client = axios.create({ baseURL: API_BASE_URL, withCredentials: true, headers: { 'X-Requested-With': 'XMLHttpRequest', }, validateStatus: () => true, }); private async login() { if (!this.loginPromise) { this.loginPromise = this.client.post>( ADMIN_LOGIN_URL, new URLSearchParams({ id: ADMIN_ID, password: ADMIN_PASSWORD, }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }, ).then((response) => { if (response.status < 200 || response.status >= 300) { throw new Error(`Login failed: ${response.status}`); } const contentType = String(response.headers['content-type'] ?? ''); if (contentType.includes('application/json')) { const result = response.data; if (!result.success) { throw new Error(result.message); } } }).finally(() => { this.loginPromise = null; }); } return this.loginPromise; } private needsLogin(response: AxiosResponse) { if (response.status === 401 || response.status === 403) { return true; } const contentType = String(response.headers['content-type'] ?? ''); if (contentType.includes('text/html') && typeof response.data === 'string') { return ( response.data.includes('/uat/uia/actionMain.do') || response.data.includes('actionSecurityLogin.do') ); } if (contentType.includes('application/json') && this.isApiResponse(response.data)) { return !response.data.success && /login|로그인/i.test(response.data.message ?? ''); } return false; } private async request( config: AxiosRequestConfig, retryCount = 0, ): Promise>> { const response = await this.client.request>(config); if (this.needsLogin(response)) { if (retryCount >= MAX_RETRY_COUNT) { throw new Error('Authentication retry count exceeded'); } await this.login(); return this.request(config, retryCount + 1); } return response; } async get(path: string, params?: AxiosRequestConfig['params']): Promise { const response = await this.request({ url: path, method: 'GET', params, }); return this.parseJson(response); } async post( path: string, body?: unknown, ): Promise { const response = await this.request({ url: path, method: 'POST', data: body, headers: { 'Content-Type': 'application/json', }, }); return this.parseJson(response); } private parseJson(response: AxiosResponse>): T { if (response.status < 200 || response.status >= 300) { throw new Error(`API request failed: ${response.status}`); } const result = response.data; if (!this.isApiResponse(result)) { throw new Error('Invalid API response'); } if (!result.success) { throw new Error(result.message ?? 'API request failed'); } return result.data; } private isApiResponse(data: unknown): data is ApiResponse { return typeof data === 'object' && data !== null && 'success' in data; } } export const apiClient = new ApiClient();