/** * 중앙화된 인증 관리자 * 페이지 간 이동 시 불필요한 API 호출을 방지하고 인증 상태를 효율적으로 관리 */ class AuthManager { constructor() { this.currentUser = null; this.isAuthenticated = false; this.lastAuthCheck = null; this.authCheckInterval = 5 * 60 * 1000; // 5분마다 토큰 유효성 체크 this.listeners = new Set(); // 초기화 this.init(); } /** * 초기화 */ init() { console.log('🔐 AuthManager 초기화'); // localStorage에서 사용자 정보 복원 this.restoreUserFromStorage(); // 토큰 만료 체크 타이머 설정 this.setupTokenExpiryCheck(); // 페이지 가시성 변경 시 토큰 체크 document.addEventListener('visibilitychange', () => { if (!document.hidden && this.shouldCheckAuth()) { this.refreshAuth(); } }); } /** * localStorage에서 사용자 정보 복원 */ restoreUserFromStorage() { const token = localStorage.getItem('access_token'); const userStr = localStorage.getItem('currentUser'); console.log('🔍 localStorage 확인:'); console.log('- 토큰 존재:', !!token); console.log('- 사용자 정보 존재:', !!userStr); if (token && userStr) { try { this.currentUser = JSON.parse(userStr); this.isAuthenticated = true; this.lastAuthCheck = Date.now(); console.log('✅ 저장된 사용자 정보 복원:', this.currentUser.username); } catch (error) { console.error('❌ 사용자 정보 복원 실패:', error); this.clearAuth(); } } else { console.log('❌ 토큰 또는 사용자 정보 없음 - 로그인 필요'); } } /** * 인증이 필요한지 확인 */ shouldCheckAuth() { if (!this.isAuthenticated) return true; if (!this.lastAuthCheck) return true; const timeSinceLastCheck = Date.now() - this.lastAuthCheck; return timeSinceLastCheck > this.authCheckInterval; } /** * 인증 상태 확인 (필요시에만 API 호출) */ async checkAuth() { console.log('🔍 AuthManager.checkAuth() 호출됨'); console.log('- 현재 인증 상태:', this.isAuthenticated); console.log('- 현재 사용자:', this.currentUser?.username || 'null'); const token = localStorage.getItem('access_token'); if (!token) { console.log('❌ 토큰 없음 - 인증 실패'); this.clearAuth(); return null; } // 최근에 체크했으면 캐시된 정보 사용 if (this.isAuthenticated && !this.shouldCheckAuth()) { console.log('✅ 캐시된 인증 정보 사용:', this.currentUser.username); return this.currentUser; } // API 호출이 필요한 경우 console.log('🔄 API 호출 필요 - refreshAuth 실행'); return await this.refreshAuth(); } /** * 강제로 인증 정보 새로고침 (API 호출) */ async refreshAuth() { console.log('🔄 인증 정보 새로고침 (API 호출)'); try { // API가 로드될 때까지 대기 await this.waitForAPI(); const user = await AuthAPI.getCurrentUser(); this.currentUser = user; this.isAuthenticated = true; this.lastAuthCheck = Date.now(); // localStorage 업데이트 localStorage.setItem('currentUser', JSON.stringify(user)); console.log('✅ 인증 정보 새로고침 완료:', user.username); // 리스너들에게 알림 this.notifyListeners('auth-success', user); return user; } catch (error) { console.error('❌ 인증 실패:', error); this.clearAuth(); this.notifyListeners('auth-failed', error); throw error; } } /** * API 로드 대기 */ async waitForAPI() { let attempts = 0; const maxAttempts = 50; while (typeof AuthAPI === 'undefined' && attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); attempts++; } if (typeof AuthAPI === 'undefined') { throw new Error('AuthAPI를 로드할 수 없습니다'); } } /** * 인증 정보 클리어 */ clearAuth() { console.log('🧹 인증 정보 클리어'); this.currentUser = null; this.isAuthenticated = false; this.lastAuthCheck = null; localStorage.removeItem('access_token'); localStorage.removeItem('currentUser'); this.notifyListeners('auth-cleared'); } /** * 로그인 처리 */ async login(username, password) { console.log('🔑 로그인 시도:', username); try { await this.waitForAPI(); const data = await AuthAPI.login(username, password); this.currentUser = data.user; this.isAuthenticated = true; this.lastAuthCheck = Date.now(); // localStorage 저장 localStorage.setItem('access_token', data.access_token); localStorage.setItem('currentUser', JSON.stringify(data.user)); console.log('✅ 로그인 성공:', data.user.username); this.notifyListeners('login-success', data.user); return data; } catch (error) { console.error('❌ 로그인 실패:', error); this.clearAuth(); throw error; } } /** * 로그아웃 처리 */ logout() { console.log('🚪 로그아웃'); this.clearAuth(); this.notifyListeners('logout'); // 로그인 페이지로 이동 window.location.href = '/index.html'; } /** * 토큰 만료 체크 타이머 설정 */ setupTokenExpiryCheck() { // 30분마다 토큰 유효성 체크 setInterval(() => { if (this.isAuthenticated) { console.log('⏰ 정기 토큰 유효성 체크'); this.refreshAuth().catch(() => { console.log('🔄 토큰 만료 - 로그아웃 처리'); this.logout(); }); } }, 30 * 60 * 1000); } /** * 이벤트 리스너 등록 */ addEventListener(callback) { this.listeners.add(callback); } /** * 이벤트 리스너 제거 */ removeEventListener(callback) { this.listeners.delete(callback); } /** * 리스너들에게 알림 */ notifyListeners(event, data = null) { this.listeners.forEach(callback => { try { callback(event, data); } catch (error) { console.error('리스너 콜백 오류:', error); } }); } /** * 현재 사용자 정보 반환 */ getCurrentUser() { return this.currentUser; } /** * 인증 상태 반환 */ isLoggedIn() { return this.isAuthenticated && !!this.currentUser; } } // 전역 인스턴스 생성 window.authManager = new AuthManager(); console.log('🎯 AuthManager 로드 완료');