diff --git a/src/App.js b/src/App.js index 35f2d12a991eb51e711a5cf119cdcbe8015e0417..2dc813fa3195eb4cea67f96b0ee2773102f2ac1f 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { useState, createContext, useContext } from 'react'; +import React, { useState, createContext, useContext, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { ToastContainer, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; @@ -61,45 +61,190 @@ function App() { return null; }); - // 토큰 만료 체크 함수 - const checkTokenExpiration = () => { - const token = localStorage.getItem('token'); - if (token) { - try { - const decoded = jwt_decode(token); - if (decoded.exp * 1000 < Date.now()) { - // 토큰 만료시 로그아웃과 동일한 효과 - localStorage.removeItem('token'); - setUser(null); - setIsAuthenticated(false); - toast.info('세션이 만료되었습니다. 다시 로그인해주세요.'); + const [lastActivity, setLastActivity] = useState(Date.now()); + const INACTIVITY_TIMEOUT = 60000; // 1분 + + // 사용자 활동 감지 함수 + const updateLastActivity = () => { + setLastActivity(Date.now()); + }; + + // // 토큰 만료 체크 함수 + // const checkTokenExpiration = () => { + // const token = localStorage.getItem('token'); + // if (token) { + // try { + // const decoded = jwt_decode(token); + // if (decoded.exp * 1000 < Date.now()) { + // // 토큰 만료시 로그아웃과 동일한 효과 + // localStorage.removeItem('token'); + // setUser(null); + // setIsAuthenticated(false); + // toast.info('세션이 만료되었습니다. 다시 로그인해주세요.'); + // } + // } catch (error) { + // localStorage.removeItem('token'); + // setUser(null); + // setIsAuthenticated(false); + // } + // } + // }; + + // // 주기적으로 토큰 체크 (30초마다) + // React.useEffect(() => { + // const timer = setInterval(checkTokenExpiration, 30000); + // // 컴포넌트가 언마운트될 때 타이머 정리 + // return () => clearInterval(timer); + // }, []); + + // 추가-------------------------------- + // 토큰 갱신 함수 + const refreshToken = async () => { + try { + console.log('토큰 갱신 시도...'); + const currentToken = localStorage.getItem('token'); + + if (!currentToken) { + console.log('토큰이 없습니다.'); + return; + } + + const response = await fetch('http://localhost:8000/refresh-token', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${currentToken}`, + 'Content-Type': 'application/json' } - } catch (error) { - localStorage.removeItem('token'); - setUser(null); - setIsAuthenticated(false); + }); + + if (response.ok) { + const data = await response.json(); + localStorage.setItem('token', data.token); + const decoded = jwt_decode(data.token); + console.log('토큰 갱신 성공:', { + 만료시간: new Date(decoded.exp * 1000).toLocaleString(), + 현재시간: new Date().toLocaleString(), + 남은시간: Math.floor((decoded.exp * 1000 - Date.now()) / 1000 / 60) + '분' + }); + } else { + console.error('토큰 갱신 실패:', response.status); } + } catch (error) { + console.error('토큰 갱신 실패:', error); } }; - // 주기적으로 토큰 체크 (30초마다) - React.useEffect(() => { - const timer = setInterval(checkTokenExpiration, 30000); - // 컴포넌트가 언마운트될 때 타이머 정리 - return () => clearInterval(timer); - }, []); + // 사용자 활동 감지 - 안되면 다시 활성화 + // useEffect(() => { + // const updateActivity = async (e) => { + // // input 태그나 textarea에서의 이벤트는 무시 + // if (e.target.tagName.toLowerCase() === 'input' || + // e.target.tagName.toLowerCase() === 'textarea') { + // console.log('입력 필드 이벤트 무시'); + // return; + // } + + // const now = Date.now(); + // const timeSinceLastRefresh = now - lastTokenRefresh; + // setLastActivity(now); + + // console.log('활동 감지:', { + // 현재시간: new Date(now).toLocaleString(), + // 마지막갱신: new Date(lastTokenRefresh).toLocaleString(), + // 경과시간: Math.floor(timeSinceLastRefresh / 1000 / 60) + '분' + // }); + + // // 마지막 토큰 갱신으로부터 00분 이상 지났을 때만 갱신 + // if (timeSinceLastRefresh > (process.env.REACT_APP_TOKEN_EXPIRATION-1) * 60 * 1000) { + // console.log('토큰 갱신 시도...'); + // await refreshToken(); + // } else { + // console.log(`토큰 갱신까지 ${Math.ceil((process.env.REACT_APP_TOKEN_EXPIRATION-1) - timeSinceLastRefresh/1000/60)}분 남음`); + // } + // }; + + // console.log('이벤트 리스너 등록됨'); + // window.addEventListener('mousemove', updateActivity); + // window.addEventListener('keydown', updateActivity); + // window.addEventListener('click', updateActivity); + // window.addEventListener('scroll', updateActivity); + // window.addEventListener('touchstart', updateActivity); + + // return () => { + // console.log('이벤트 리스너 제거됨'); + // window.removeEventListener('mousemove', updateActivity); + // window.removeEventListener('keydown', updateActivity); + // window.removeEventListener('click', updateActivity); + // window.removeEventListener('scroll', updateActivity); + // window.removeEventListener('touchstart', updateActivity); + // }; + // }, [lastTokenRefresh]); +//안되면 다시 활성화 ------------- + +const logout = () => { + localStorage.removeItem('token'); + setUser(null); + setIsAuthenticated(false); +}; + + +useEffect(() => { + if (isAuthenticated) { + const checkInactivity = () => { + const inactiveTime = Date.now() - lastActivity; + if (inactiveTime > INACTIVITY_TIMEOUT) { + logout(); + toast.info('장시간 활동이 없어 자동 로그아웃되었습니다.'); + } + }; + + const updateActivity = () => { + setLastActivity(Date.now()); + }; + + // 사용자 활동 감지를 위한 이벤트 리스너 + window.addEventListener('mousemove', updateActivity); + window.addEventListener('keydown', updateActivity); + window.addEventListener('click', updateActivity); + window.addEventListener('scroll', updateActivity); + window.addEventListener('touchstart', updateActivity); + + // 비활성 상태 체크 인터벌 설정 (5초마다) + const inactivityCheck = setInterval(checkInactivity, 5000); + + return () => { + // 이벤트 리스너 및 인터벌 정리 + window.removeEventListener('mousemove', updateActivity); + window.removeEventListener('keydown', updateActivity); + window.removeEventListener('click', updateActivity); + window.removeEventListener('scroll', updateActivity); + window.removeEventListener('touchstart', updateActivity); + clearInterval(inactivityCheck); + }; + } +}, [isAuthenticated, lastActivity, logout]); + + // 비활성 시간 체크 + useEffect(() => { + const checkInactivity = () => { + const inactiveTime = Date.now() - lastActivity; + if (inactiveTime > process.env.REACT_APP_TOKEN_EXPIRATION * 60 * 1000) { // 토큰 만료시간 + logout(); + toast.info('장시간 활동이 없어 로그아웃되었습니다.'); + } + }; + + const interval = setInterval(checkInactivity, 30000); // 30초마다 체크 + return () => clearInterval(interval); + }, [lastActivity]); + //-------------------------------- const login = (userData) => { setUser(userData); setIsAuthenticated(true); }; - const logout = () => { - localStorage.removeItem('token'); - setUser(null); - setIsAuthenticated(false); - }; - + return ( <AuthContext.Provider value={{ isAuthenticated, user, login, logout }}> <ToastContext.Provider value={toast}>