// api-config.js - nginx 프록시 대응 API 설정 import { config } from './config.js'; import { redirectToLogin } from './navigation.js'; function getApiBaseUrl() { const hostname = window.location.hostname; const protocol = window.location.protocol; const port = window.location.port; console.log('🌐 감지된 환경:', { hostname, protocol, port }); // 🔗 nginx 프록시를 통한 접근 (권장) // nginx가 /api/ 요청을 백엔드로 프록시하므로 포트 없이 접근 if (hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.') || hostname === 'localhost' || hostname === '127.0.0.1' || hostname.includes('.local') || hostname.includes('hyungi')) { // 현재 웹서버의 도메인/IP를 그대로 사용하되 API 포트(config.api.port)로 직접 연결 const baseUrl = `${protocol}//${hostname}:${config.api.port}${config.api.path}`; console.log('✅ nginx 프록시 사용:', baseUrl); return baseUrl; } // 🚨 백업: 직접 접근 (nginx 프록시 실패시에만) console.warn('⚠️ 직접 API 접근 (백업 모드)'); return `${protocol}//${hostname}:${config.api.port}${config.api.path}`; } // API 설정 const API_URL = getApiBaseUrl(); // 전역 변수로 설정 window.API = API_URL; window.API_BASE_URL = API_URL; function ensureAuthenticated() { const token = localStorage.getItem('token'); if (!token || token === 'undefined' || token === 'null') { console.log('🚨 인증되지 않은 사용자. 로그인 페이지로 이동합니다.'); clearAuthData(); // 만약을 위해 한번 더 정리 redirectToLogin(); return false; // 이후 코드 실행 방지 } // 토큰 만료 확인 if (isTokenExpired(token)) { console.log('🚨 토큰이 만료되었습니다. 로그인 페이지로 이동합니다.'); clearAuthData(); alert('세션이 만료되었습니다. 다시 로그인해주세요.'); redirectToLogin(); return false; } return token; } // 토큰 만료 확인 함수 function isTokenExpired(token) { try { const payload = JSON.parse(atob(token.split('.')[1])); const currentTime = Math.floor(Date.now() / 1000); return payload.exp < currentTime; } catch (error) { console.error('토큰 파싱 오류:', error); return true; // 파싱 실패 시 만료된 것으로 간주 } } // 인증 데이터 정리 함수 function clearAuthData() { localStorage.removeItem('token'); localStorage.removeItem('user'); localStorage.removeItem('userInfo'); localStorage.removeItem('currentUser'); } function getAuthHeaders() { const token = localStorage.getItem('token'); return { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }; } // 🔧 개선된 API 호출 함수 (에러 처리 강화) async function apiCall(url, method = 'GET', data = null) { // 상대 경로를 절대 경로로 변환 const fullUrl = url.startsWith('http') ? url : `${API}${url}`; const options = { method: method, headers: { 'Content-Type': 'application/json', ...getAuthHeaders() } }; // POST/PUT 요청시 데이터 추가 if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) { options.body = JSON.stringify(data); } try { console.log(`📡 API 호출: ${fullUrl} (${method})`); const response = await fetch(fullUrl, options); // 인증 만료 처리 if (response.status === 401) { console.error('🚨 인증 실패: 토큰이 만료되었거나 유효하지 않습니다.'); clearAuthData(); alert('세션이 만료되었습니다. 다시 로그인해주세요.'); redirectToLogin(); throw new Error('인증에 실패했습니다.'); } // 응답 실패 처리 if (!response.ok) { let errorMessage = `HTTP ${response.status}`; try { const errorData = await response.json(); errorMessage = errorData.error || errorData.message || errorMessage; } catch (e) { // JSON 파싱 실패시 기본 메시지 사용 } throw new Error(errorMessage); } const result = await response.json(); console.log(`✅ API 성공: ${fullUrl}`); return result; } catch (error) { console.error(`❌ API 오류 (${fullUrl}):`, error); // 네트워크 오류 vs 서버 오류 구분 if (error.name === 'TypeError' && error.message.includes('fetch')) { throw new Error('네트워크 연결 오류입니다. 인터넷 연결을 확인해주세요.'); } throw error; } } // 디버깅 정보 console.log('🔗 API Base URL:', API); console.log('🌐 Current Location:', { hostname: window.location.hostname, protocol: window.location.protocol, port: window.location.port, href: window.location.href }); // 🧪 API 연결 테스트 함수 (개발용) async function testApiConnection() { try { console.log('🧪 API 연결 테스트 시작...'); const response = await fetch(`${API}/health`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (response.ok) { console.log('✅ API 연결 성공!'); return true; } else { console.log('❌ API 연결 실패:', response.status); return false; } } catch (error) { console.log('❌ API 연결 오류:', error.message); return false; } } // 전역 함수로 설정 window.ensureAuthenticated = ensureAuthenticated; window.getAuthHeaders = getAuthHeaders; window.apiCall = apiCall; window.testApiConnection = testApiConnection; window.isTokenExpired = isTokenExpired; window.clearAuthData = clearAuthData; // 개발 모드에서 자동 테스트 if (window.location.hostname === 'localhost' || window.location.hostname.startsWith('192.168.')) { setTimeout(() => { testApiConnection(); }, 1000); } // 주기적으로 토큰 만료 확인 (5분마다) setInterval(() => { const token = localStorage.getItem('token'); if (token && isTokenExpired(token)) { console.log('🚨 주기적 확인: 토큰이 만료되었습니다.'); clearAuthData(); alert('세션이 만료되었습니다. 다시 로그인해주세요.'); redirectToLogin(); } }, config.app.tokenRefreshInterval); // 5분마다 확인