debounce
// debounce 사용
import { useEffect, useCallback } from "react";
import { useRecoilState } from "recoil";
import { userState, isLoggedInState } from "@/recoil/userState";
import { useLogout } from "@/hooks/useLogout";
import { basicAxios } from "@/api/axios";
import axios, { AxiosResponse, AxiosError } from "axios";
// API 응답 데이터 타입 정의
interface RefreshResponse {
accessToken: string;
}
// LocalStorage에서 가져오는 데이터의 타입 정의
type LocalStorageItem = string | null;
// debounce 함수 정의
const debounce = <F extends (...args: any[]) => void>(
func: F,
delay: number
): ((...args: Parameters<F>) => void) => {
let timer: ReturnType<typeof setTimeout>;
return (...args: Parameters<F>) => {
clearTimeout(timer);
timer = setTimeout(() => {
func(...args);
}, delay);
};
};
const useAuth = () => {
const [user, setUser] = useRecoilState(userState);
const [isLoggedIn, setIsLoggedIn] = useRecoilState(isLoggedInState);
const { logout } = useLogout();
const refreshAccessToken = useCallback(async (): Promise<string | null> => {
try {
console.log("Starting token refresh...");
const response: AxiosResponse<RefreshResponse> = await basicAxios.post(
"/users/refresh"
);
console.log("Refresh response:", response);
const { accessToken } = response.data;
localStorage.setItem("AccessToken", accessToken);
// 액세스 토큰 만료 시간 갱신 (예시: 5분 후 만료)
const accessTokenExpiration = new Date().getTime() + 5 * 60 * 1000;
localStorage.setItem(
"AccessTokenExpiration",
accessTokenExpiration.toString()
);
return accessToken;
} catch (error) {
console.error("액세스 토큰 갱신 실패:", error);
if (axios.isAxiosError(error)) {
const response = (error as AxiosError).response;
console.log("Axios 오류 응답:", response);
if (response?.status === 401) {
console.log("Unauthorized 오류 응답:", response.data);
}
}
return null;
}
}, []);
const debounceCheckAndRefreshToken = useCallback(
debounce(async () => {
await checkAndRefreshToken();
}, 1000),
[]
);
const checkAndRefreshToken = useCallback(async (): Promise<void> => {
const accessToken: LocalStorageItem = localStorage.getItem("AccessToken");
const accessTokenExpiration: LocalStorageItem = localStorage.getItem(
"AccessTokenExpiration"
);
console.log("Checking and refreshing token...");
if (accessToken && accessTokenExpiration) {
const now = new Date().getTime();
const expirationTime = Number(accessTokenExpiration);
if (now >= expirationTime) {
console.log("Token expired. Refreshing...");
const newAccessToken = await refreshAccessToken();
if (newAccessToken) {
setIsLoggedIn(true);
} else {
logout();
}
} else {
setIsLoggedIn(true); // AccessToken이 유효하면 로그인 상태로 설정
console.log("Token is valid. Setting isLoggedIn to true.");
}
} else {
console.log("No access token or expiration time found. Logging out...");
logout(); // AccessToken이 없으면 로그아웃 처리
}
}, [refreshAccessToken, setIsLoggedIn, logout]);
useEffect(() => {
const checkTokenAndRefresh = async (): Promise<void> => {
const accessToken: LocalStorageItem = localStorage.getItem("AccessToken");
const accessTokenExpiration: LocalStorageItem = localStorage.getItem(
"AccessTokenExpiration"
);
console.log(
"Executing useEffect to check token and refresh if necessary..."
);
if (accessToken && accessTokenExpiration) {
const now = new Date().getTime();
const expirationTime = Number(accessTokenExpiration);
if (now < expirationTime) {
setIsLoggedIn(true); // AccessToken이 유효하면 로그인 상태로 설정
console.log("Token is valid. Setting isLoggedIn to true.");
const storedUser: LocalStorageItem = localStorage.getItem("user");
if (storedUser) {
const parsedUser = JSON.parse(storedUser);
setUser(parsedUser);
}
} else {
console.log("Token expired. Calling debounceCheckAndRefreshToken...");
debounceCheckAndRefreshToken(); // debounce를 통해 호출
}
} else {
console.log("No access token or expiration time found. Logging out...");
logout(); // AccessToken이 없으면 로그아웃 처리
}
};
checkTokenAndRefresh();
}, [setIsLoggedIn, setUser, debounceCheckAndRefreshToken, logout]);
return { user, isLoggedIn, checkAndRefreshToken };
};
export default useAuth;