import axios, { AxiosInstance, AxiosResponse, AxiosError, AxiosRequestConfig } from 'axios'; import { ApiResponse, ApiErrorResponse, FileUploadProgress, FileUploadResponse } from '@/types'; import { tokenManager } from './tokenManager'; import config from '@/config'; import { useAppBridge } from '@/hooks'; class ApiClient { private instance: AxiosInstance; private isRefreshing = false; private failedQueue: Array<{ resolve: (value?: unknown) => void; reject: (error?: unknown) => void; }> = []; private isNativeEnvironment: any; private requestRefreshToken: any; constructor() { this.instance = axios.create({ baseURL: config.api.baseURL, timeout: config.api.timeout, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, }); this.setupInterceptors(); } private setupInterceptors(): void { // 요청 인터셉터 this.instance.interceptors.request.use( (config) => { const token = tokenManager.getAccessToken(); console.log('setupInterceptors request ==> ', token) if(token){ config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // 응답 인터셉터 this.instance.interceptors.response.use( (response: AxiosResponse) => response, async (error: AxiosError) => { console.log(' this.instance.interceptors.response' ); const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean }; console.log('originalRequest ==> ', JSON.stringify(originalRequest)); if (error.response?.status === 401 && !originalRequest._retry) { if (this.isRefreshing) { return new Promise((resolve, reject) => { this.failedQueue.push({ resolve, reject }); }).then(token => { originalRequest.headers!.Authorization = `Bearer ${token}`; return this.instance(originalRequest); }).catch(err => { return Promise.reject(err); }); } originalRequest._retry = true; this.isRefreshing = true; console.log('refreshToken!!'); /* this.requestRefreshToken1().then((token) => { console.log('requestRefreshToken +[' + JSON.stringify(token) + ']' ); }); try { const newToken = await this.refreshAccessToken(); this.processQueue(null, newToken); originalRequest.headers!.Authorization = `Bearer ${newToken}`; return this.instance(originalRequest); } catch (refreshError) { this.processQueue(refreshError, null); tokenManager.clearTokens(); // 로그인 페이지로 리디렉션 또는 로그아웃 처리 window.location.href = '/login'; return Promise.reject(refreshError); } finally { this.isRefreshing = false; } */ } return Promise.reject(this.handleError(error)); } ); } private processQueue(error: unknown, token: string | null): void { this.failedQueue.forEach(({ resolve, reject }) => { if (error) { reject(error); } else { resolve(token); } }); this.failedQueue = []; } /* private async requestRefreshAccessToken(): Promise { const { isNativeEnvironment, requestRefreshToken } = useAppBridge(); } */ private async refreshAccessToken(): Promise { const refreshToken = tokenManager.getRefreshToken(); if (!refreshToken) { throw new Error('No refresh token available'); } try { const response = await axios.post(`${config.api.baseURL}/auth/refresh`, { refresh_token: refreshToken }); const { access_token, refresh_token: newRefreshToken } = response.data.data; tokenManager.setTokens(access_token, newRefreshToken); return access_token; } catch { throw new Error('Token refresh failed'); } } private handleError(error: AxiosError): ApiErrorResponse { const errorResponse: ApiErrorResponse = { success: false, error: { code: 'UNKNOWN_ERROR', message: '알 수 없는 오류가 발생했습니다.', }, timestamp: new Date().toISOString(), }; if (error.response) { // 서버 응답이 있는 경우 const { status, data } = error.response; errorResponse.error.code = `HTTP_${status}`; if (data && typeof data === 'object' && 'message' in data) { errorResponse.error.message = (data as { message: string }).message; } else { errorResponse.error.message = this.getErrorMessage(status); } errorResponse.error.details = data; } else if (error.request) { // 네트워크 오류 errorResponse.error.code = 'NETWORK_ERROR'; errorResponse.error.message = '네트워크 연결을 확인해주세요.'; } else { // 요청 설정 오류 errorResponse.error.code = 'REQUEST_ERROR'; errorResponse.error.message = '요청 처리 중 오류가 발생했습니다.'; } return errorResponse; } private getErrorMessage(status: number): string { const messages: Record = { 400: '잘못된 요청입니다.', 401: '인증이 필요합니다.', 403: '접근 권한이 없습니다.', 404: '요청한 리소스를 찾을 수 없습니다.', 409: '데이터 충돌이 발생했습니다.', 422: '입력 데이터를 확인해주세요.', 500: '서버 오류가 발생했습니다.', 502: '서버 연결에 문제가 있습니다.', 503: '서비스를 일시적으로 사용할 수 없습니다.', }; return messages[status] || '알 수 없는 오류가 발생했습니다.'; } async get(url: string, config?: AxiosRequestConfig): Promise> { const response = await this.instance.get(url, config); return response.data; } async post(url: string, data?: unknown, config?: AxiosRequestConfig): Promise> { const response = await this.instance.post(url, data, config); return response.data; } async put(url: string, data?: unknown, config?: AxiosRequestConfig): Promise> { const response = await this.instance.put(url, data, config); return response.data; } async patch(url: string, data?: unknown, config?: AxiosRequestConfig): Promise> { const response = await this.instance.patch(url, data, config); return response.data; } async delete(url: string, config?: AxiosRequestConfig): Promise> { const response = await this.instance.delete(url, config); return response.data; } async uploadFile( url: string, file: File, onProgress?: (progress: FileUploadProgress) => void ): Promise> { const formData = new FormData(); formData.append('file', file); const config: AxiosRequestConfig = { headers: { 'Content-Type': 'multipart/form-data', }, onUploadProgress: (progressEvent) => { if (onProgress && progressEvent.total) { const progress: FileUploadProgress = { loaded: progressEvent.loaded, total: progressEvent.total, percentage: Math.round((progressEvent.loaded * 100) / progressEvent.total), }; onProgress(progress); } }, }; const response = await this.instance.post(url, formData, config); return response.data; } async downloadFile(url: string, filename: string): Promise { const response = await this.instance.get(url, { responseType: 'blob', }); const blob = new Blob([response.data]); const downloadUrl = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = downloadUrl; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(downloadUrl); } } export const apiClient = new ApiClient(); export default apiClient;