Refactor: Axios config 리팩토링

This commit is contained in:
jhynsoo 2024-09-19 13:10:29 +09:00
parent 97816e30db
commit eb30abefcc
2 changed files with 26 additions and 69 deletions

View File

@ -1,32 +1,17 @@
import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import axios, { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import useAuthStore from '@/stores/useAuthStore'; import useAuthStore from '@/stores/useAuthStore';
import { RefreshTokenResponse, ErrorResponse } from '../types'; import { RefreshTokenResponse } from '../types';
const REFRESH_URL = '/auth/reissue';
const api = axios.create({ const api = axios.create({
baseURL: `${import.meta.env.VITE_API_URL}/api`, baseURL: `${import.meta.env.VITE_API_URL}`,
withCredentials: true, withCredentials: true,
}); });
const refreshApi = axios.create({
baseURL: `${import.meta.env.VITE_API_URL}/api`,
withCredentials: true,
});
let isRefreshing = false;
let refreshSubscribers: Array<(token: string) => void> = [];
const subscribeTokenRefresh = (cb: (token: string) => void) => {
refreshSubscribers.push(cb);
};
const onRefreshed = (token: string) => {
refreshSubscribers.forEach((cb) => cb(token));
refreshSubscribers = [];
};
api.interceptors.request.use((config: InternalAxiosRequestConfig) => { api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const accessToken = useAuthStore.getState().accessToken; const accessToken = useAuthStore.getState().accessToken;
if (accessToken && config.headers) {
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`; config.headers.Authorization = `Bearer ${accessToken}`;
} }
return config; return config;
@ -34,44 +19,27 @@ api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
api.interceptors.response.use( api.interceptors.response.use(
(response: AxiosResponse) => response, (response: AxiosResponse) => response,
(error: AxiosError<ErrorResponse>) => { (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }; if (error.response?.status !== 401 || error.request.responseURL?.includes(REFRESH_URL)) {
return Promise.reject(error);
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
if (isRefreshing) {
return new Promise<void>((resolve) => {
subscribeTokenRefresh((token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(api(originalRequest));
});
});
}
isRefreshing = true;
return refreshApi
.post<RefreshTokenResponse>('/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;
});
} }
return Promise.reject(error);
return api
.post<RefreshTokenResponse>(REFRESH_URL)
.then(({ data }) => {
console.log(data);
const { accessToken } = data;
useAuthStore.getState().setLoggedIn(true, accessToken);
if (error.config) {
return api(error.config);
}
return Promise.reject(error);
})
.catch((error) => {
useAuthStore.getState().clearAuth();
window.location.href = '/';
return Promise.reject(error);
});
} }
); );

View File

@ -18,7 +18,7 @@ export default function Home() {
}); });
} }
const handleGoogleSignIn = () => { const handleGoogleSignIn = () => {
window.location.href = `${BASE_URL}/api/login/oauth2/authorization/google`; window.location.href = `${BASE_URL}/login/oauth2/authorization/google`;
}; };
return ( return (
@ -43,17 +43,6 @@ export default function Home() {
</div> </div>
{!isLoggedIn ? ( {!isLoggedIn ? (
// <Link
// to={`${BASE_URL}/api/login/oauth2/authorization/google`}
// // onClick={handleGoogleSignIn}
// className="mb-4 transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-gray-300 active:opacity-80"
// >
// <img
// src={GoogleLogo}
// alt="Sign in with Google"
// className="h-auto w-full"
// />
// </Link>
<button <button
onClick={handleGoogleSignIn} onClick={handleGoogleSignIn}
className="mb-4 transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-gray-300 active:opacity-80" className="mb-4 transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-gray-300 active:opacity-80"