import { useLoadingStore } from '@/stores/loading' import { useAuthStore } from '@/stores/auth' import axios from 'axios' let instance = null let isRefreshing = false; let failedQueue = []; function processQueue(error, token = null) { failedQueue.forEach(prom => { if (error) { prom.reject(error); } else { prom.resolve(token); } }); failedQueue = []; } // interceptor가 없는 별도의 axios 인스턴스 생성 const refreshAxios = axios.create({ baseURL: import.meta.env.VITE_APP_API_URL, // 최종 API URL withCredentials: false, timeout: 60 * 1000, responseType: 'json', responseEncoding: 'utf8', xsrfHeaderName: 'X-XSRF-TOKEN', progress: false, }); const useAxios = () => { /************************************************************************ | Axios ************************************************************************/ const { $log } = useNuxtApp() const store = useLoadingStore() if (!instance) { // 환경 변수에서 API URL과 포트를 가져옵니다 const apiBaseUrl = import.meta.env.VITE_APP_API_URL const apiPort = import.meta.env.VITE_APP_API_PORT const fullApiUrl = `${apiBaseUrl}:${apiPort}` let loadingPassUrl = [ '/p5g/fm/eventViewer' ] instance = axios.create({ baseURL: apiBaseUrl, // 최종 API URL withCredentials: false, timeout: 60 * 1000, responseType: 'json', responseEncoding: 'utf8', xsrfHeaderName: 'X-XSRF-TOKEN', progress: false, }); /** * 요청 인터셉터 */ instance.interceptors.request.use(function (config) { $log.debug("[REQ]" + config.url) let accessToken = useAuthStore().getAccessToken; // 개발 모드일 때는 env에서 VITE_APP_DEV_TOKEN 사용 if (import.meta.env.MODE === 'development' && import.meta.env.VITE_APP_DEV_TOKEN) { accessToken = import.meta.env.VITE_APP_DEV_TOKEN; } config.headers = { ...config.headers, // 기존 헤더 유지 'Accept': 'application/json', 'Access-Token': accessToken ? accessToken : '', // 동적으로 토큰 세팅 }; // 멀티파트 요청이면 Content-Type을 자동으로 설정하지 않음 if (config.headers['Content-Type'] !== 'multipart/form-data') { if (!config.headers['Content-Type']) { config.headers['Content-Type'] = 'application/json;charset=UTF-8'; } } if(!loadingPassUrl.includes(config.url)) { store.plusCount() } return config }, function (error) { $log.error("[REQ][ERR]" + error) if (!loadingPassUrl.includes(config.url)) { store.minusCount() } // 요청 에러에도 로딩카운트 감소 if (error.config && !loadingPassUrl.includes(error.config.url)) { store.minusCount() } return Promise.reject(error) } ) /** * 응답 인터셉터 */ instance.interceptors.response.use( response => { if(!loadingPassUrl.includes(response.config.url)) { store.minusCount() } return response; }, async error => { // 응답 에러에도 로딩카운트 감소(최대한 항상 호출) if (error.config && !loadingPassUrl.includes(error.config.url)) { store.minusCount() } if (error.response && error.response.status === 401) { const authStore = useAuthStore(); const originalRequest = error.config; // refreshToken이 있고, 재발급 시도가 아닌 경우 if (authStore.getRefreshToken && !originalRequest._retry) { if (isRefreshing) { // 이미 재발급 중이면 큐에 쌓았다가 처리 return new Promise(function(resolve, reject) { failedQueue.push({resolve, reject}); }).then(token => { originalRequest.headers['Access-Token'] = `${token}`; return instance(originalRequest); }).catch(err => { return Promise.reject(err); }); } originalRequest._retry = true; isRefreshing = true; store.plusCount(); // refreshToken 요청 로딩 시작 try { let __REQ = { refreshToken: authStore.getRefreshToken } // refreshAxios로 refreshToken 요청 const res = await refreshAxios.post('/roulette/refreshToken', __REQ); // 다양한 응답 구조에서 accessToken 추출 let newAccessToken = res.data.accessToken; if (!newAccessToken && res.data.data && res.data.data.accessToken) { newAccessToken = res.data.data.accessToken; } if (!newAccessToken && res.data.token) { newAccessToken = res.data.token; } if (!newAccessToken) { if (typeof window !== 'undefined') { alert('세션이 만료되었습니다. 다시 로그인 해주세요.'); window.location.href = '/'; } authStore.setLogout(); throw new Error('No accessToken in refreshToken response'); } authStore.setAccessToken(newAccessToken); processQueue(null, newAccessToken); isRefreshing = false; originalRequest.headers['Access-Token'] = `${newAccessToken}`; store.minusCount(); // instance로 원래 요청 재시도 return instance(originalRequest); } catch (refreshError) { processQueue(refreshError, null); isRefreshing = false; store.minusCount(); // refreshToken 요청 로딩 끝 // refreshToken 만료(401, 403)만 로그아웃, 그 외는 안내 if (refreshError.response && (refreshError.response.status === 401 || refreshError.response.status === 403)) { authStore.setLogout(); if (typeof window !== 'undefined') { alert('로그인 세션이 만료되었습니다. 다시 로그인 해주세요.'); window.location.href = '/'; } } else { if (typeof window !== 'undefined') { alert('일시적인 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); //window.location.href = '/'; } } return Promise.reject(refreshError); } } else { if(!error.response.data.messages.errorCode){ authStore.setLogout(); if (typeof window !== 'undefined') { window.location.href = '/'; } } } } return Promise.reject(error); } ); } return instance } export default useAxios