Skip to content

Commit

Permalink
Merge pull request #90 from Next-Room/feat/add-refresh-token
Browse files Browse the repository at this point in the history
feat: 리프레쉬 토큰 갱신 및 로그인 정보 로컬스토리지 -> 세션스토리지 이동
  • Loading branch information
lgrin-byte authored Dec 29, 2024
2 parents dbf6319 + 67b6e2d commit addb77e
Show file tree
Hide file tree
Showing 22 changed files with 137 additions and 187 deletions.
2 changes: 1 addition & 1 deletion app/admin/(components)/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
getStatus,
removeAccessToken,
removeThemeId,
} from "@/utils/localStorage";
} from "@/utils/storageUtil";
import { useSelectedThemeReset } from "@/components/atoms/selectedTheme.atom";
import { useIsLoggedInWrite } from "@/components/atoms/account.atom";
import { useDrawerState } from "@/components/atoms/drawer.atom";
Expand Down
2 changes: 1 addition & 1 deletion app/admin/(components)/ThemeDrawer/hooks/useImages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { useCreateHint } from "@/components/atoms/createHint.atom";
import { useSelectedHint } from "@/components/atoms/selectedHint.atom";
import { useToastWrite } from "@/components/atoms/toast.atom";
import { getStatus } from "@/utils/localStorage";
import { getStatus } from "@/utils/storageUtil";
import { subscribeLinkURL } from "@/admin/(consts)/sidebar";

import { compressImage, convertToPng } from "../helpers/imageHelpers";
Expand Down
2 changes: 1 addition & 1 deletion app/admin/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";

import useCheckSignIn from "@/hooks/useCheckSignIn";
import Loader from "@/components/Loader/Loader";
import { getLoginInfo, setSelectedThemeId } from "@/utils/localStorage";
import { getLoginInfo, setSelectedThemeId } from "@/utils/storageUtil";
import { useSelectedTheme } from "@/components/atoms/selectedTheme.atom";
import { useGetThemeList } from "@/queries/getThemeList";
import { useToastInfo } from "@/components/atoms/toast.atom";
Expand Down
9 changes: 6 additions & 3 deletions app/components/RequireAuth/RequireAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
import React, { ReactNode, useEffect, useMemo, useState } from "react";
import { useRouter, usePathname } from "next/navigation";

import { useTokenRefresh } from "@/mutations/useRefresh";
import { useGetThemeList } from "@/queries/getThemeList";
import {
useCurrentTheme,
useCurrentThemeReset,
} from "@/components/atoms/currentTheme.atom";
import { useSelectedThemeReset } from "@/components/atoms/selectedTheme.atom";
import { useIsLoggedIn } from "@/components/atoms/account.atom";
import { getSelectedThemeId } from "@/utils/localStorage";

import { getSelectedThemeId } from "@/utils/storageUtil";
import * as S from "@/home/HomeView.styled";
import Header from "@/components/common/Header/Header";
import MainDrawer from "@/components/common/Drawer/Drawer";

import Mobile from "../Mobile/Mobile";

Expand All @@ -29,7 +32,7 @@ function RequireAuth({ children }: RequireAuthProps) {
const [isMobile, setIsMobile] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const { data: categories = [] } = useGetThemeList();
const { refreshToken, error } = useTokenRefresh();

useEffect(() => {
if (typeof window !== "undefined") {
const { userAgent } = window.navigator;
Expand Down
4 changes: 0 additions & 4 deletions app/components/common/Dialog-new/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,4 @@ const Dialog = forwardRef<HTMLFormElement, DialogProps>((props) => {
);
});

Dialog.defaultProps = {
type: "",
};

export default Dialog;
2 changes: 1 addition & 1 deletion app/components/common/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
useSelectedTheme,
} from "@/components/atoms/selectedTheme.atom";
import { Theme, Themes } from "@/queries/getThemeList";
import { getLoginInfo } from "@/utils/localStorage";
import { getLoginInfo } from "@/utils/storageUtil";
import Dialog from "@/components/common/Dialog/Dialog";

import * as S from "./DrawerView.styled";
Expand Down
2 changes: 1 addition & 1 deletion app/components/common/EmptyHome/EmptyHomeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useRouter } from "next/navigation";

import { HOME_TITLE } from "@/consts/components/home";
import { useModalStateWrite } from "@/components/atoms/modalState.atom";
import { getLoginInfo } from "@/utils/localStorage";
import { getLoginInfo } from "@/utils/storageUtil";

import * as S from "./EmptyHomeView.styled";

Expand Down
2 changes: 1 addition & 1 deletion app/components/common/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, MouseEvent } from "react";

import { useIsLoggedInWrite } from "@/components/atoms/account.atom";
import { removeAccessToken } from "@/utils/localStorage";
import { removeAccessToken } from "@/utils/storageUtil";

import HeaderView from "./HeaderView";

Expand Down
4 changes: 0 additions & 4 deletions app/components/common/Hint-Dialog-new/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,4 @@ const Dialog = forwardRef<HTMLFormElement, DialogProps>((props) => {
);
});

Dialog.defaultProps = {
type: "",
};

export default Dialog;
2 changes: 1 addition & 1 deletion app/hooks/useCheckSignIn.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect } from "react";

import { apiClient } from "@/lib/reactQueryProvider";
import { getLoginInfo } from "@/utils/localStorage";
import { getLoginInfo } from "@/utils/storageUtil";
import { useIsLoggedIn } from "@/components/atoms/account.atom";
import { getSubscriptionPlan } from "@/queries/getSubscriptionPlan";

Expand Down
2 changes: 1 addition & 1 deletion app/hooks/useClickOutside.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useEffect, RefObject } from "react";
* @param {boolean} ignoreSiblings - 형제 요소 클릭 시에도 닫힐지 여부
*/
function useClickOutside(
ref: RefObject<HTMLElement | HTMLElement[]>,
ref: React.RefObject<HTMLElement | HTMLElement[] | HTMLFormElement | null>,
handler: (event: MouseEvent) => void,
isActive = true,
targetIndex = 0,
Expand Down
2 changes: 1 addition & 1 deletion app/landing/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRouter, usePathname } from "next/navigation";

import { useIsLoggedInWrite } from "@/components/atoms/account.atom";
import { useAsPathStateWrite } from "@/components/atoms/signup.atom";
import { removeAccessToken } from "@/utils/localStorage";
import { removeAccessToken } from "@/utils/storageUtil";
import useCheckSignIn from "@/hooks/useCheckSignIn";
import useChannelTalk from "@/hooks/useChannelTalk";

Expand Down
123 changes: 86 additions & 37 deletions app/lib/reactQueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,120 @@
"use client";

import axios from "axios";
import { PropsWithChildren, useState } from "react";
import axios from "axios";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

import { getLoginInfo, removeLocalStorageAll } from "@/utils/localStorage";
import { useIsLoggedInWrite } from "@/components/atoms/account.atom";
import { useSnackBarWrite } from "@/components/atoms/snackBar.atom";
import {
getLoginInfo,
removeLocalStorageAll,
setLoginInfo,
} from "@/utils/storageUtil";

// Axios 클라이언트 설정
export const apiClient = axios.create({
withCredentials: true,
});

let isRefreshing = false;
let failedQueue: any[] = [];

// 대기 중인 요청 처리
const processQueue = (error: any, token: string | null = null) => {
failedQueue.forEach((prom) => {
if (token) {
prom.resolve(token);
} else {
prom.reject(error);
}
});
failedQueue = [];
};

apiClient.interceptors.request.use(
(config) => {
const { accessToken } = getLoginInfo();

if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken.replace(
/^"(.*)"$/,
"$1"
)}`;
}

return config;
},
(error) => Promise.reject(error)
);

type ErrorResponse = {
response: {
data: {
code: number;
message: string;
};
status: number;
};
};
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const { response } = error;
const originalRequest = error.config;

export default function ReactQueryProvider({ children }: PropsWithChildren) {
const setIsLoggedIn = useIsLoggedInWrite();
const setSnackBar = useSnackBarWrite();

apiClient.interceptors.response.use(
(response) => response,
(error) => {
const { response } = error as ErrorResponse;
if (response?.data?.message) {
setSnackBar({
isOpen: true,
message: `${(error as any)?.response?.data?.message || error}`,
});
}
if (response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;

if (response && response.status === 401) {
delete apiClient.defaults.headers.Authorization;
delete apiClient.defaults.headers.common.Authorization;
removeLocalStorageAll();
setIsLoggedIn(false);
if (!isRefreshing) {
isRefreshing = true;

try {
const loginInfo = getLoginInfo();
const { refreshToken, accessToken } = loginInfo;

if (!refreshToken || accessToken === "undefined") {
throw new Error("리프레시 토큰이 없습니다.");
}

const { data } = await axios.post(
"/v1/auth/reissue",
{
refreshToken: refreshToken.replace(/^"(.*)"$/, "$1"),
accessToken: accessToken.replace(/^"(.*)"$/, "$1"),
},
{
headers: {
"Content-Type": "application/json",
},
}
);

const { data: response } = data;

setLoginInfo({
...loginInfo,
refreshToken: response.refreshToken,
accessToken: response.accessToken,
});
apiClient.defaults.headers.Authorization = `Bearer ${response.accessToken}`;

processQueue(null, response.accessToken);
return apiClient(originalRequest);
} catch (refreshError) {
processQueue(refreshError, null);
removeLocalStorageAll();
throw refreshError;
} finally {
isRefreshing = false;
}
}

return Promise.reject(error);
return new Promise((resolve, reject) => {
failedQueue.push({
resolve: (token: string) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(apiClient(originalRequest));
},
reject: (err: any) => reject(err),
});
});
}
);

const [queryClient] = useState(new QueryClient({}));
return Promise.reject(error);
}
);

export default function ReactQueryProvider({ children }: PropsWithChildren) {
const [queryClient] = useState(() => new QueryClient());

return (
<QueryClientProvider client={queryClient}>
Expand Down
2 changes: 1 addition & 1 deletion app/mutations/postLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AxiosError, AxiosResponse } from "axios";
import { useSnackBarWrite } from "@/components/atoms/snackBar.atom";
import { apiClient } from "@/lib/reactQueryProvider";
import { ApiError, ApiResponse, MutationConfigOptions } from "@/types";
import { setLoginInfo } from "@/utils/localStorage";
import { setLoginInfo } from "@/utils/storageUtil";
import { useIsLoggedInWrite } from "@/components/atoms/account.atom";

interface Request {
Expand Down
2 changes: 1 addition & 1 deletion app/mutations/postTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useToastWrite } from "@/components/atoms/toast.atom";
import { apiClient } from "@/lib/reactQueryProvider";
import { QUERY_KEY } from "@/queries/getThemeList";
import { MutationConfigOptions } from "@/types";
import { setSelectedThemeId } from "@/utils/localStorage";
import { setSelectedThemeId } from "@/utils/storageUtil";

interface Request {
title: string;
Expand Down
Loading

0 comments on commit addb77e

Please sign in to comment.