Skip to content

Commit

Permalink
feat: token reissue 에러 핸들링 및 session Handler wrapper component 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
sh981013s committed Sep 20, 2023
1 parent 144783b commit 3d3d321
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 83 deletions.
108 changes: 57 additions & 51 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import MainPage from '@pages/mainPage/MainPage';
import useToast from '@hooks/_common/useToast';
import OAuthRedirect from './components/loginPage/OAuthRedirect';
import AsyncBoundary from './components/_common/errorBoundary/AsyncBoundary';
import SessionHandler from '@components/_common/sessionHandler/SessionHandler';

const PrivateRouter = (props: PropsWithChildren) => {
const { children } = props;
Expand All @@ -50,57 +51,62 @@ const App = () => {
<BrowserRouter>
<ResponsiveContainer>
<PageLayout>
<AsyncBoundary>
<Routes>
<Route path='/' element={<MainPage />} />
<Route path='/login' element={<LoginPage />} />
<Route path='/join' element={<SignUpPage />} />
<Route path='/roadmap-list' element={<RoadmapListPage />}>
<Route path=':category/:search' element={<RoadmapSearchResult />} />
</Route>
<Route
path='/roadmap/:id'
element={
<Suspense fallback={<Fallback />}>
<RoadmapDetailPage />
</Suspense>
}
/>
<Route
path='/roadmap/:id/goalroom-list'
element={<GoalRoomListPage />}
/>
<Route
path='/roadmap-create'
element={
<PrivateRouter>
<RoadmapCreatePage />
</PrivateRouter>
}
/>
<Route
path='/roadmap/:id/goalroom-create'
element={
<PrivateRouter>
<GoalRoomCreatePage />
</PrivateRouter>
}
/>
<Route
path='/goalroom-dashboard/:goalroomId'
element={<GoalRoomDashboardPage />}
/>
<Route
path='/myPage'
element={
<PrivateRouter>
<MyPage />
</PrivateRouter>
}
/>
<Route path='/oauth/redirect' element={<OAuthRedirect />} />
</Routes>
</AsyncBoundary>
<SessionHandler>
<AsyncBoundary>
<Routes>
<Route path='/' element={<MainPage />} />
<Route path='/login' element={<LoginPage />} />
<Route path='/join' element={<SignUpPage />} />
<Route path='/roadmap-list' element={<RoadmapListPage />}>
<Route
path=':category/:search'
element={<RoadmapSearchResult />}
/>
</Route>
<Route
path='/roadmap/:id'
element={
<Suspense fallback={<Fallback />}>
<RoadmapDetailPage />
</Suspense>
}
/>
<Route
path='/roadmap/:id/goalroom-list'
element={<GoalRoomListPage />}
/>
<Route
path='/roadmap-create'
element={
<PrivateRouter>
<RoadmapCreatePage />
</PrivateRouter>
}
/>
<Route
path='/roadmap/:id/goalroom-create'
element={
<PrivateRouter>
<GoalRoomCreatePage />
</PrivateRouter>
}
/>
<Route
path='/goalroom-dashboard/:goalroomId'
element={<GoalRoomDashboardPage />}
/>
<Route
path='/myPage'
element={
<PrivateRouter>
<MyPage />
</PrivateRouter>
}
/>
<Route path='/oauth/redirect' element={<OAuthRedirect />} />
</Routes>
</AsyncBoundary>
</SessionHandler>
</PageLayout>
</ResponsiveContainer>
</BrowserRouter>
Expand Down
69 changes: 37 additions & 32 deletions client/src/apis/axios/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,57 @@ client.interceptors.request.use((config) => {
return config;
});

let globalRetryCount = 0;

client.interceptors.response.use(
(response) => response,
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;

if (!originalRequest.retryCount) {
originalRequest.retryCount = 0;
if (globalRetryCount >= 3) {
window.dispatchEvent(new Event('unauthorized'));
return Promise.reject(error);
}

if (error.response) {
if (
error.response.status === 401 &&
error.response.data.message === 'Expired Token'
) {
if (originalRequest.retryCount >= 3) {
window.location.href = '/login';
return;
}
originalRequest.retryCount++;
if (
error.response &&
error.response.status === 401 &&
error.response.data.message === 'Expired Token'
) {
globalRetryCount++;

const refreshToken = getCookie('refresh_token');
const refreshToken = getCookie('refresh_token');

try {
const { data } = await client.post('/auth/reissue', { refreshToken });
try {
const { data } = await client.post('/auth/reissue', { refreshToken });

setCookie('access_token', data.accessToken);
setCookie('refresh_token', data.refreshToken);
setCookie('access_token', data.accessToken);
setCookie('refresh_token', data.refreshToken);

return client(originalRequest);
} catch (reissueError) {
const axiosError = reissueError as AxiosError<ErrorData>;
return client(originalRequest);
} catch (reissueError) {
const axiosError = reissueError as AxiosError<ErrorData>;

if (
axiosError.response?.status === 401 &&
axiosError.response?.data?.message === '토큰이 유효하지 않습니다.'
) {
return client(originalRequest);
} else {
throw reissueError;
}
if (axiosError.response?.status === 401) {
window.dispatchEvent(new Event('unauthorized'));
return Promise.reject(reissueError);
} else {
return Promise.reject(reissueError);
}
} else {
throw error;
}
} else if (
error.response &&
error.response.status === 501 &&
error.response.data.message.includes(
'Row was updated or deleted by another transaction'
)
) {
return null;
} else {
return Promise.reject(error);
}
throw error;
}
);

Expand Down
33 changes: 33 additions & 0 deletions client/src/components/_common/sessionHandler/SessionHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PropsWithChildren, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import useToast from '@hooks/_common/useToast';
import {
defaultUserInfo,
useUserInfoContext,
} from '@components/_providers/UserInfoProvider';
import { deleteCookie } from '@utils/_common/cookies';

const SessionHandler = (props: PropsWithChildren) => {
const navigate = useNavigate();
const { triggerToast } = useToast();
const { setUserInfo } = useUserInfoContext();

useEffect(() => {
const handleUnauthorized = () => {
deleteCookie('access_token');
deleteCookie('refresh_token');
setUserInfo(defaultUserInfo);
triggerToast({ message: '세션이 만료되었습니다. 재로그인 해주세요.' });
navigate('/login');
};

window.addEventListener('unauthorized', handleUnauthorized);
return () => {
window.removeEventListener('unauthorized', handleUnauthorized);
};
}, []);

return <>{props.children}</>;
};

export default SessionHandler;

0 comments on commit 3d3d321

Please sign in to comment.