From 1547e93a845e7e9b690e3c98e266474eff976b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Wed, 18 Sep 2024 16:54:39 +0900 Subject: [PATCH] =?UTF-8?q?Refactor:=20axios=20=EC=BB=A8=ED=94=BC=EA=B7=B8?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81,=20=EA=B7=B8=EB=9F=AC?= =?UTF-8?q?=EB=82=98=20=EB=8D=9C=20=EB=90=90=EC=9D=8C.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/axiosConfig.ts | 114 ++++++++++++-------------------- 1 file changed, 43 insertions(+), 71 deletions(-) diff --git a/frontend/src/api/axiosConfig.ts b/frontend/src/api/axiosConfig.ts index 1fefc5d..8110a0a 100644 --- a/frontend/src/api/axiosConfig.ts +++ b/frontend/src/api/axiosConfig.ts @@ -1,32 +1,27 @@ import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import useAuthStore from '@/stores/useAuthStore'; -import { BaseResponse, CustomError, SuccessResponse, RefreshTokenResponse } from '@/types'; - -const baseURL = import.meta.env.VITE_API_URL; +import { RefreshTokenResponse, ErrorResponse } from '../types'; const api = axios.create({ - baseURL, + baseURL: `${import.meta.env.VITE_API_URL}/api`, withCredentials: true, }); -let isTokenRefreshing = false; +const refreshApi = axios.create({ + baseURL: `${import.meta.env.VITE_API_URL}/api`, + withCredentials: true, +}); -type FailedRequest = { - resolve: (value?: string | undefined) => void; - reject: (reason?: unknown) => void; +let isRefreshing = false; +let refreshSubscribers: Array<(token: string) => void> = []; + +const subscribeTokenRefresh = (cb: (token: string) => void) => { + refreshSubscribers.push(cb); }; -let failedQueue: FailedRequest[] = []; - -const processQueue = (error: Error | null, token: string | undefined = undefined): void => { - failedQueue.forEach((prom) => { - if (error) { - prom.reject(error); - } else { - prom.resolve(token); - } - }); - failedQueue = []; +const onRefreshed = (token: string) => { + refreshSubscribers.forEach((cb) => cb(token)); + refreshSubscribers = []; }; api.interceptors.request.use((config: InternalAxiosRequestConfig) => { @@ -39,68 +34,45 @@ api.interceptors.request.use((config: InternalAxiosRequestConfig) => { api.interceptors.response.use( (response: AxiosResponse) => response, - async (error: AxiosError>) => { + (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }; if (error.response?.status === 401 && !originalRequest._retry) { - if (isTokenRefreshing) { - return new Promise((resolve, reject) => { - failedQueue.push({ resolve, reject }); - }) - .then((token) => { - if (token && originalRequest.headers) { - originalRequest.headers.Authorization = `Bearer ${token}`; - } - return api(originalRequest); - }) - .catch((err) => Promise.reject(err)); - } - originalRequest._retry = true; - isTokenRefreshing = true; - try { - const response: AxiosResponse> = await api.post('/auth/reissue', null, { - withCredentials: true, + if (isRefreshing) { + return new Promise((resolve) => { + subscribeTokenRefresh((token: string) => { + originalRequest.headers.Authorization = `Bearer ${token}`; + resolve(api(originalRequest)); + }); }); - - const newAccessToken = response.data.data?.accessToken; - if (!newAccessToken) { - throw new Error('Invalid token reissue response'); - } - - useAuthStore.getState().setLoggedIn(true, newAccessToken); - processQueue(null, newAccessToken); - - const redirectUri = `/redirect/oauth2?accessToken=${newAccessToken}`; - window.location.href = redirectUri; - - return Promise.reject(new Error('Redirecting to retrieve cookies')); - } catch (reissueError: unknown) { - processQueue(reissueError as Error, undefined); - console.error('토큰 재발급 실패:', reissueError); - useAuthStore.getState().clearAuth(); - window.location.href = '/'; - return Promise.reject(reissueError); - } finally { - isTokenRefreshing = false; } + + isRefreshing = true; + + return refreshApi + .post('/auth/reissue', null, { withCredentials: true }) + .then(({ data }) => { + const newAccessToken = data.accessToken; + useAuthStore.getState().setLoggedIn(true, newAccessToken); + onRefreshed(newAccessToken); + + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + return api(originalRequest); + }) + .catch(() => { + useAuthStore.getState().clearAuth(); + console.log('리이슈 실패'); + window.location.href = '/'; + return Promise.reject(error); + }) + .finally(() => { + isRefreshing = false; + }); } - - handleCommonErrors(error); - return Promise.reject(error); } ); -const handleCommonErrors = (error: AxiosError>) => { - if (error.response?.status === 400) { - alert('잘못된 요청입니다. 다시 시도해 주세요.'); - } else if (error.response?.status === 403) { - alert(error.response?.data?.message); - } else { - console.error('오류 발생:', error.response?.data?.message || '알 수 없는 오류'); - } -}; - export default api;