// Chrome Extension 환경에서 안정적으로 동작하도록 수정 const SUPABASE_URL = "http://146.56.101.199:8000"; const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"; // 디버그 모드 플래그 (개발 시에만 true로 설정) const DEBUG_MODE = true; // false로 설정하면 디버그 정보 숨김 // const DEBUG_MODE = false; // false로 설정하면 디버그 정보 숨김 // 로그 레벨 정의 const LOG_LEVELS = { ERROR: 'ERROR', WARN: 'WARN', INFO: 'INFO', DEBUG: 'DEBUG' }; // 로그 저장 및 관리 시스템 class Logger { constructor() { this.maxLogs = 100; // 최대 저장할 로그 수 } // 로그 저장 async saveLog(level, message, data = null) { const timestamp = new Date().toISOString(); const logEntry = { timestamp, level, message, data: data ? JSON.stringify(data) : null }; try { const { loginLogs = [] } = await chrome.storage.local.get('loginLogs'); loginLogs.unshift(logEntry); // 최신 로그를 앞에 추가 // 최대 로그 수 제한 if (loginLogs.length > this.maxLogs) { loginLogs.splice(this.maxLogs); } await chrome.storage.local.set({ loginLogs }); } catch (error) { console.error('로그 저장 실패:', error); } } // 로그 조회 async getLogs() { try { const { loginLogs = [] } = await chrome.storage.local.get('loginLogs'); return loginLogs; } catch (error) { console.error('로그 조회 실패:', error); return []; } } // 로그 삭제 async clearLogs() { try { await chrome.storage.local.remove('loginLogs'); return true; } catch (error) { console.error('로그 삭제 실패:', error); return false; } } // 통합 로그 함수 async log(level, message, data = null) { // 콘솔에 출력 const consoleMessage = `[${level}] ${message}`; switch (level) { case LOG_LEVELS.ERROR: console.error(consoleMessage, data); break; case LOG_LEVELS.WARN: console.warn(consoleMessage, data); break; case LOG_LEVELS.INFO: console.info(consoleMessage, data); break; case LOG_LEVELS.DEBUG: if (DEBUG_MODE) console.log(consoleMessage, data); break; } // 스토리지에 저장 await this.saveLog(level, message, data); // 디버그 정보 업데이트 if (DEBUG_MODE) { updateDebugInfo(`[${level}] ${message}`); } } } // 전역 로거 인스턴스 const logger = new Logger(); // 디버그 정보 업데이트 함수 (수정됨) function updateDebugInfo(message) { const debugEl = document.getElementById("debug-info"); if (debugEl) { if (DEBUG_MODE) { // 기존 로그 버튼 찾기 const existingButton = debugEl.querySelector('button'); // 메시지만 업데이트 (버튼은 유지) if (existingButton) { // 버튼이 있으면 텍스트 노드만 업데이트 const textNode = debugEl.firstChild; if (textNode && textNode.nodeType === Node.TEXT_NODE) { textNode.textContent = message; } else { // 텍스트 노드가 없으면 새로 생성 debugEl.insertBefore(document.createTextNode(message), debugEl.firstChild); } } else { // 버튼이 없으면 전체 내용 설정 debugEl.textContent = message; } debugEl.style.display = "block"; console.log('디버그 정보 업데이트:', message); } else { debugEl.style.display = "none"; } } else { console.warn('debug-info 요소를 찾을 수 없습니다'); } } // 로그 표시 함수 async function showLogs() { const logs = await logger.getLogs(); const logWindow = window.open('', '_blank', 'width=800,height=600'); const logHtml = ` 로그인 로그

로그인 로그

${logs.map(log => `
${new Date(log.timestamp).toLocaleString()}
[${log.level}]
${log.message}
${log.data ? `
${log.data}
` : ''}
`).join('')}
`; logWindow.document.write(logHtml); logWindow.document.close(); } // 이메일 형식 검증 함수 function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // Supabase 클라이언트 초기화 (직접 fetch 사용) async function supabaseAuth(email, password) { await logger.log(LOG_LEVELS.INFO, '로그인 API 호출 시작', { email }); const url = `${SUPABASE_URL}/auth/v1/token?grant_type=password`; await logger.log(LOG_LEVELS.DEBUG, '로그인 API URL', { url }); try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': SUPABASE_ANON_KEY }, body: JSON.stringify({ email: email, password: password }) }); await logger.log(LOG_LEVELS.DEBUG, '로그인 API 응답 상태', { status: response.status, statusText: response.statusText, ok: response.ok, headers: Object.fromEntries(response.headers.entries()) }); if (!response.ok) { const error = await response.json(); await logger.log(LOG_LEVELS.ERROR, '로그인 API 응답 오류', { status: response.status, error: error }); throw new Error(error.error_description || error.message || '로그인 실패'); } const result = await response.json(); await logger.log(LOG_LEVELS.INFO, '로그인 API 응답 성공', { hasAccessToken: !!result.access_token, tokenLength: result.access_token ? result.access_token.length : 0 }); return result; } catch (error) { await logger.log(LOG_LEVELS.ERROR, '로그인 API 호출 중 네트워크 오류', { error: error.message, name: error.name, url: url }); // 네트워크 에러 메시지 개선 if (error.name === 'TypeError' && (error.message.includes('fetch') || error.message.includes('Failed to fetch'))) { throw new Error(`서버에 연결할 수 없습니다.\n- 인터넷 연결을 확인해주세요\n- 서버가 실행 중인지 확인해주세요\n- 방화벽 설정을 확인해주세요\n\n기술적 세부사항: ${error.message}`); } throw error; } } // 모달 드래그 기능 function makeModalDraggable(modal) { const modalContent = modal.querySelector('.modal-content'); const modalHeader = modal.querySelector('.modal-header'); let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; modalHeader.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); function dragStart(e) { if (e.target.classList.contains('close')) return; initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === modalHeader) { isDragging = true; } } function drag(e) { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; setTranslate(currentX, currentY, modalContent); } } function dragEnd(e) { initialX = currentX; initialY = currentY; isDragging = false; } function setTranslate(xPos, yPos, el) { el.style.transform = `translate(${xPos}px, ${yPos}px)`; } } // 모달 리사이즈 기능 function makeModalResizable(modal) { const modalContent = modal.querySelector('.modal-content'); const resizer = document.createElement('div'); resizer.className = 'resizer'; modalContent.appendChild(resizer); let isResizing = false; let originalWidth; let originalHeight; let originalX; let originalY; resizer.addEventListener('mousedown', initResize); document.addEventListener('mousemove', resize); document.addEventListener('mouseup', stopResize); function initResize(e) { isResizing = true; originalWidth = modalContent.offsetWidth; originalHeight = modalContent.offsetHeight; originalX = e.clientX; originalY = e.clientY; e.preventDefault(); } function resize(e) { if (!isResizing) return; const width = originalWidth + (e.clientX - originalX); const height = originalHeight + (e.clientY - originalY); if (width > 400) { modalContent.style.width = width + 'px'; } if (height > 300) { modalContent.style.height = height + 'px'; } } function stopResize() { isResizing = false; } } // 모달 초기화 함수 function initializeModal(modalId) { const modal = document.getElementById(modalId); if (!modal) return; makeModalDraggable(modal); makeModalResizable(modal); // 모달 외부 클릭 시 닫기 modal.addEventListener('click', (e) => { if (e.target === modal) { modal.style.display = 'none'; } }); // 닫기 버튼 const closeBtn = modal.querySelector('.close'); if (closeBtn) { closeBtn.addEventListener('click', () => { modal.style.display = 'none'; }); } } // JWT 토큰 유효성 검증 함수 추가 async function validateToken(token) { if (!token) { await logger.log(LOG_LEVELS.DEBUG, '토큰이 없음'); return false; } try { // JWT 토큰 만료 시간 확인 (클라이언트 사이드) const payload = JSON.parse(atob(token.split('.')[1])); const currentTime = Math.floor(Date.now() / 1000); if (payload.exp && payload.exp < currentTime) { await logger.log(LOG_LEVELS.WARN, '토큰이 만료됨', { exp: payload.exp, currentTime, expired: true }); return false; } // 서버에서 토큰 유효성 검증 const authUrl = `${SUPABASE_URL}/auth/v1/user`; const authRes = await fetch(authUrl, { headers: { Authorization: `Bearer ${token}`, apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' } }); if (authRes.ok) { await logger.log(LOG_LEVELS.INFO, '토큰 유효성 검증 성공'); return true; } else { await logger.log(LOG_LEVELS.WARN, '토큰 유효성 검증 실패', { status: authRes.status, statusText: authRes.statusText }); return false; } } catch (error) { await logger.log(LOG_LEVELS.ERROR, '토큰 유효성 검증 중 오류', { error: error.message }); return false; } } // 자동 로그인 시도 함수 추가 async function attemptAutoLogin() { updateDebugInfo('🔄 자동 로그인 확인 중...'); try { // 1. 기존 토큰 확인 const { access_token } = await chrome.storage.local.get("access_token"); if (access_token) { updateDebugInfo('🔍 기존 토큰 유효성 검증 중...'); await logger.log(LOG_LEVELS.INFO, '기존 토큰 발견, 유효성 검증 시작'); const isValidToken = await validateToken(access_token); if (isValidToken) { // 토큰이 유효하면 바로 사용자 정보 로드 updateDebugInfo('✅ 유효한 토큰으로 자동 로그인 중...'); await logger.log(LOG_LEVELS.INFO, '유효한 토큰으로 자동 로그인 성공'); await loadUserInfo(access_token); return true; } else { // 토큰이 유효하지 않으면 제거 updateDebugInfo('🗑️ 만료된 토큰 제거 중...'); await logger.log(LOG_LEVELS.WARN, '만료된 토큰 제거'); await chrome.storage.local.remove("access_token"); } } // 2. 자동 로그인 설정 확인 const { autoLogin, savedEmail, savedPassword } = await chrome.storage.local.get([ "autoLogin", "savedEmail", "savedPassword" ]); if (autoLogin && savedEmail && savedPassword) { updateDebugInfo('🔄 저장된 계정으로 자동 로그인 시도 중...'); await logger.log(LOG_LEVELS.INFO, '저장된 계정으로 자동 로그인 시도'); // 저장된 정보로 자동 로그인 document.getElementById("email").value = savedEmail; document.getElementById("password").value = savedPassword; document.getElementById("save-login").checked = true; document.getElementById("auto-login").checked = true; // 약간의 지연 후 로그인 시도 setTimeout(doLogin, 500); return true; } // 3. 자동 로그인이 불가능한 경우 updateDebugInfo('✅ 초기화 완료 - 로그인이 필요합니다'); await logger.log(LOG_LEVELS.DEBUG, '자동 로그인 불가 - 수동 로그인이 필요합니다'); return false; } catch (error) { updateDebugInfo(`❌ 자동 로그인 확인 실패: ${error.message}`); await logger.log(LOG_LEVELS.ERROR, '자동 로그인 확인 중 오류', { error: error.message }); return false; } } // DOM 로드 완료 후 실행 document.addEventListener('DOMContentLoaded', async function() { // 즉시 디버그 정보 표시 updateDebugInfo('DOM 로드 완료, 초기화 시작...'); await logger.log(LOG_LEVELS.INFO, 'DOM 로드 완료, 초기화 시작'); // 디버그 정보 표시/숨김 처리 const debugEl = document.getElementById("debug-info"); if (debugEl && DEBUG_MODE) { debugEl.style.display = "block"; // 로그 보기 버튼 생성 및 추가 if (!debugEl.querySelector('button')) { const logButton = document.createElement('button'); logButton.textContent = '📋 로그 보기'; logButton.onclick = showLogs; // 줄바꿈 추가 후 버튼 추가 debugEl.appendChild(document.createElement('br')); debugEl.appendChild(logButton); console.log('로그 보기 버튼 추가됨'); } } updateDebugInfo('요소 존재 확인 중...'); try { // 요소 존재 확인 const requiredElements = ['email', 'password', 'login-btn', 'password-toggle']; const missingElements = []; for (const elementId of requiredElements) { if (!document.getElementById(elementId)) { missingElements.push(elementId); } } if (missingElements.length > 0) { updateDebugInfo(`❌ 필수 요소 누락: ${missingElements.join(', ')}`); await logger.log(LOG_LEVELS.ERROR, '필수 요소 누락', { missingElements }); return; } updateDebugInfo('✅ 모든 필수 요소 확인됨'); await logger.log(LOG_LEVELS.DEBUG, '모든 필수 요소 확인됨'); // 저장된 로그인 정보 로드 (UI 표시용) try { const saved = await chrome.storage.local.get(["savedEmail", "savedPassword", "autoLogin"]); await logger.log(LOG_LEVELS.DEBUG, '저장된 설정 로드 완료', { hasSavedEmail: !!saved.savedEmail, autoLogin: saved.autoLogin }); if (saved.savedEmail) document.getElementById("email").value = saved.savedEmail; if (saved.savedPassword) document.getElementById("password").value = saved.savedPassword; document.getElementById("save-login").checked = !!saved.savedEmail; document.getElementById("auto-login").checked = !!saved.autoLogin; } catch (error) { updateDebugInfo(`❌ 설정 로드 실패: ${error.message}`); await logger.log(LOG_LEVELS.ERROR, '저장된 설정 로드 실패', { error: error.message }); } // 🔐 비밀번호 보기 (눌렀을 때만 보이기) const passwordToggle = document.getElementById("password-toggle"); const passwordInput = document.getElementById("password"); if (passwordToggle && passwordInput) { await logger.log(LOG_LEVELS.DEBUG, '비밀번호 토글 이벤트 등록'); // 마우스 이벤트 passwordToggle.addEventListener("mousedown", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.DEBUG, '비밀번호 보기 - mousedown'); passwordInput.type = "text"; }); passwordToggle.addEventListener("mouseup", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.DEBUG, '비밀번호 숨기기 - mouseup'); passwordInput.type = "password"; }); passwordToggle.addEventListener("mouseleave", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.DEBUG, '비밀번호 숨기기 - mouseleave'); passwordInput.type = "password"; }); // 터치 이벤트 passwordToggle.addEventListener("touchstart", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.DEBUG, '비밀번호 보기 - touchstart'); passwordInput.type = "text"; }); passwordToggle.addEventListener("touchend", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.DEBUG, '비밀번호 숨기기 - touchend'); passwordInput.type = "password"; }); } // 이메일 입력 필드 이벤트 const emailInput = document.getElementById("email"); if (emailInput) { await logger.log(LOG_LEVELS.DEBUG, '이메일 필드 이벤트 등록'); emailInput.addEventListener("keypress", async (e) => { if (e.key === 'Enter') { e.preventDefault(); const password = document.getElementById("password").value.trim(); if (password) { // 비밀번호가 있으면 로그인 시도 await logger.log(LOG_LEVELS.INFO, '이메일 필드에서 Enter - 로그인 시도'); doLogin(); } else { // 비밀번호가 없으면 비밀번호 필드로 포커스 이동 await logger.log(LOG_LEVELS.DEBUG, '이메일 필드에서 Enter - 비밀번호 필드로 이동'); document.getElementById("password").focus(); } } }); } // 비밀번호 입력 필드 이벤트 if (passwordInput) { await logger.log(LOG_LEVELS.DEBUG, '비밀번호 필드 이벤트 등록'); passwordInput.addEventListener("keypress", async (e) => { if (e.key === 'Enter') { e.preventDefault(); await logger.log(LOG_LEVELS.INFO, '비밀번호 필드에서 Enter - 로그인 시도'); doLogin(); } }); } // 로그인 버튼 const loginBtn = document.getElementById("login-btn"); if (loginBtn) { await logger.log(LOG_LEVELS.DEBUG, '로그인 버튼 이벤트 등록'); loginBtn.addEventListener("click", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.INFO, '로그인 버튼 클릭됨'); doLogin(); }); } // 로그아웃 버튼 const logoutBtn = document.getElementById("logout-btn"); if (logoutBtn) { await logger.log(LOG_LEVELS.DEBUG, '로그아웃 버튼 이벤트 등록'); logoutBtn.addEventListener("click", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.INFO, '로그아웃 버튼 클릭됨'); // 토큰과 자동로그인 설정 제거 await chrome.storage.local.remove(["access_token", "autoLogin"]); await logger.log(LOG_LEVELS.INFO, '로그아웃 완료 - 토큰 및 자동로그인 설정 제거'); location.reload(); }); } // 금지어 관리 버튼 const bannedWordsBtn = document.getElementById("banned-words-btn"); if (bannedWordsBtn) { await logger.log(LOG_LEVELS.DEBUG, '금지어 관리 버튼 이벤트 등록'); bannedWordsBtn.addEventListener("click", async (e) => { e.preventDefault(); await logger.log(LOG_LEVELS.INFO, '금지어 관리 버튼 클릭됨'); try { // 현재 로그인된 토큰 확인 const { access_token } = await chrome.storage.local.get("access_token"); if (!access_token) { alert('로그인이 필요합니다.'); return; } // 토큰 유효성 재확인 const isValid = await validateToken(access_token); if (!isValid) { alert('세션이 만료되었습니다. 다시 로그인해주세요.'); await chrome.storage.local.remove("access_token"); location.reload(); return; } // chrome.storage에 설정값 저장 (새 창에서 사용할 수 있도록) await chrome.storage.local.set({ 'bannedWords_config': { SUPABASE_URL: SUPABASE_URL, SUPABASE_ANON_KEY: SUPABASE_ANON_KEY, DEBUG_MODE: DEBUG_MODE, ACCESS_TOKEN: access_token, timestamp: Date.now() } }); await logger.log(LOG_LEVELS.INFO, '금지어 관리 창 설정 저장 완료', { hasUrl: !!SUPABASE_URL, hasKey: !!SUPABASE_ANON_KEY, hasToken: !!access_token, debugMode: DEBUG_MODE }); // 크롬 익스텐션 리소스 URL로 새 창 열기 const url = chrome.runtime.getURL('bannedWords.html'); const newWindow = window.open(url, '_blank', 'width=1000,height=700'); if (!newWindow) { throw new Error('새 창을 열 수 없습니다. 팝업 차단을 확인해주세요.'); } await logger.log(LOG_LEVELS.INFO, '금지어 관리 창 열기 완료'); } catch (error) { await logger.log(LOG_LEVELS.ERROR, '금지어 관리 창 열기 실패', { error: error.message }); alert('금지어 관리 창을 열 수 없습니다: ' + error.message); } }); } // 어록보기 버튼 클릭 이벤트 document.getElementById('sayings-btn').addEventListener('click', async () => { console.log('어록보기 버튼 클릭됨'); try { // 현재 로그인된 토큰 확인 const { access_token } = await chrome.storage.local.get("access_token"); if (!access_token) { alert('로그인이 필요합니다.'); return; } // 토큰 유효성 재확인 const isValid = await validateToken(access_token); if (!isValid) { alert('세션이 만료되었습니다. 다시 로그인해주세요.'); await chrome.storage.local.remove("access_token"); location.reload(); return; } // chrome.storage에 설정 저장 (금지어 관리와 동일한 설정 사용) const settingsToSave = { SUPABASE_URL: SUPABASE_URL, SUPABASE_ANON_KEY: SUPABASE_ANON_KEY, DEBUG_MODE: DEBUG_MODE, ACCESS_TOKEN: access_token, timestamp: Date.now() }; console.log('어록보기용 설정을 chrome.storage에 저장 중...', { url: !!settingsToSave.SUPABASE_URL, key: !!settingsToSave.SUPABASE_ANON_KEY, token: !!settingsToSave.ACCESS_TOKEN, debug: settingsToSave.DEBUG_MODE }); await chrome.storage.local.set({ 'sayings_config': settingsToSave }); console.log('어록보기 설정 저장 완료, 새 창 열기'); // 새 창에서 어록보기 페이지 열기 chrome.windows.create({ url: chrome.runtime.getURL('sayings.html'), type: 'popup', width: 1200, height: 800 }); } catch (error) { console.error('어록보기 창 열기 실패:', error); alert('어록보기 창을 열 수 없습니다: ' + error.message); } }); // 모달 초기화 initializeModal('banned-words-modal'); initializeModal('kipris-modal'); // 🚀 자동 로그인 시도 (가장 중요한 부분) updateDebugInfo('🚀 자동 로그인 시도 중...'); const autoLoginSuccess = await attemptAutoLogin(); if (!autoLoginSuccess) { updateDebugInfo('✅ 초기화 완료 - 수동 로그인이 필요합니다'); } await logger.log(LOG_LEVELS.INFO, '초기화 완료', { autoLoginSuccess }); } catch (error) { updateDebugInfo(`❌ 초기화 실패: ${error.message}`); await logger.log(LOG_LEVELS.ERROR, '초기화 중 치명적 오류', { error: error.message }); console.error('초기화 오류:', error); } }); // 로그인 함수 async function doLogin() { updateDebugInfo('🔄 로그인 프로세스 시작...'); await logger.log(LOG_LEVELS.INFO, '로그인 프로세스 시작'); try { const email = document.getElementById("email").value.trim(); const password = document.getElementById("password").value.trim(); await logger.log(LOG_LEVELS.DEBUG, '로그인 입력값 검증 시작', { email }); // 오류 메시지 초기화 document.getElementById("email-error").textContent = ""; document.getElementById("password-error").textContent = ""; document.getElementById("status").textContent = ""; document.getElementById("membership-level").textContent = ""; // 유효성 검사 let hasError = false; // 이메일 형식 검증 강화 if (!email) { document.getElementById("email-error").textContent = "이메일을 입력하세요."; hasError = true; await logger.log(LOG_LEVELS.WARN, '이메일 입력 누락'); } else if (!isValidEmail(email)) { document.getElementById("email-error").textContent = "올바른 이메일 형식을 입력하세요. (예: user@example.com)"; hasError = true; await logger.log(LOG_LEVELS.WARN, '잘못된 이메일 형식', { email }); } // 비밀번호 검증 if (!password) { document.getElementById("password-error").textContent = "비밀번호를 입력하세요."; hasError = true; await logger.log(LOG_LEVELS.WARN, '비밀번호 입력 누락'); } else if (password.length < 6) { document.getElementById("password-error").textContent = "비밀번호는 6자 이상이어야 합니다."; hasError = true; await logger.log(LOG_LEVELS.WARN, '비밀번호 길이 부족', { length: password.length }); } if (hasError) { updateDebugInfo('❌ 입력 유효성 검사 실패'); await logger.log(LOG_LEVELS.WARN, '입력 유효성 검사 실패'); return; } // UI 상태 변경 - 로딩 표시 document.getElementById("loading").style.display = "block"; document.getElementById("login-btn").disabled = true; document.getElementById("login-btn").textContent = "로그인 중..."; document.getElementById("status").textContent = "🔄 로그인 중입니다..."; updateDebugInfo('🔄 로그인 API 호출 중...'); await logger.log(LOG_LEVELS.INFO, '로그인 요청 전송 중...'); try { // 직접 fetch를 사용한 로그인 const authResult = await supabaseAuth(email, password); if (authResult.access_token) { updateDebugInfo('✅ 로그인 성공! 사용자 정보 로드 중...'); await logger.log(LOG_LEVELS.INFO, '로그인 성공, 토큰 저장'); const token = authResult.access_token; await chrome.storage.local.set({ access_token: token, SUPABASE_URL, SUPABASE_ANON_KEY }); // 로그인 정보 저장 처리 if (document.getElementById("save-login").checked) { await chrome.storage.local.set({ savedEmail: email, savedPassword: password }); await logger.log(LOG_LEVELS.INFO, '로그인 정보 저장됨'); } else { await chrome.storage.local.remove(["savedEmail", "savedPassword"]); } // 자동 로그인 설정 처리 if (document.getElementById("auto-login").checked) { await chrome.storage.local.set({ autoLogin: true }); await logger.log(LOG_LEVELS.INFO, '자동 로그인 설정됨'); } else { await chrome.storage.local.remove("autoLogin"); } document.getElementById("status").textContent = "✅ 로그인 성공!"; await logger.log(LOG_LEVELS.INFO, '사용자 정보 로드 시작'); await loadUserInfo(token); } else { throw new Error('토큰을 받지 못했습니다'); } } catch (authError) { updateDebugInfo(`❌ 로그인 실패: ${authError.message}`); await logger.log(LOG_LEVELS.ERROR, '로그인 실패', { error: authError.message }); document.getElementById("status").textContent = "❌ 로그인 실패: " + authError.message; // 입력 필드에 포커스 if (authError.message.includes('email') || authError.message.includes('이메일')) { document.getElementById("email").focus(); } else { document.getElementById("password").focus(); } } } catch (err) { updateDebugInfo(`❌ 로그인 오류: ${err.message}`); await logger.log(LOG_LEVELS.ERROR, '로그인 중 예외', { error: err.message }); document.getElementById("status").textContent = "❌ 알 수 없는 오류: " + err.message; console.error('로그인 오류:', err); } finally { // UI 상태 복원 document.getElementById("loading").style.display = "none"; document.getElementById("login-btn").disabled = false; document.getElementById("login-btn").textContent = "로그인"; await logger.log(LOG_LEVELS.DEBUG, '로그인 프로세스 종료'); } } // 사용자 정보 로드 함수 async function loadUserInfo(token) { updateDebugInfo('🔄 사용자 정보 로드 중...'); await logger.log(LOG_LEVELS.INFO, '사용자 정보 요청 중...'); try { // 먼저 Auth API로 사용자 기본 정보 가져오기 const authUrl = `${SUPABASE_URL}/auth/v1/user`; await logger.log(LOG_LEVELS.DEBUG, '사용자 Auth 정보 API 호출', { authUrl }); const authRes = await fetch(authUrl, { headers: { Authorization: `Bearer ${token}`, apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' } }); if (!authRes.ok) { // Auth API 응답 오류 처리 let authErrorDetails; try { authErrorDetails = await authRes.json(); } catch { authErrorDetails = await authRes.text(); } await logger.log(LOG_LEVELS.ERROR, 'Auth API 응답 오류', { status: authRes.status, statusText: authRes.statusText, authErrorDetails }); throw new Error(`Auth API 오류 - HTTP ${authRes.status}: ${authRes.statusText}`); } const authUser = await authRes.json(); await logger.log(LOG_LEVELS.DEBUG, 'Auth API 응답 데이터', { authUser }); // 사용자 상세 정보와 membership_levels 조인해서 가져오기 let userDetails = null; try { // membership_levels 테이블과 조인하여 api_call_limit도 함께 가져오기 const detailsUrl = `${SUPABASE_URL}/rest/v1/users?select=*,membership_levels(level,api_call_limit)&email=eq.${encodeURIComponent(authUser.email)}&limit=1`; await logger.log(LOG_LEVELS.DEBUG, '사용자 상세 정보 API 호출 (조인)', { detailsUrl }); const detailsRes = await fetch(detailsUrl, { headers: { Authorization: `Bearer ${token}`, apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' } }); if (detailsRes.ok) { const detailsData = await detailsRes.json(); await logger.log(LOG_LEVELS.DEBUG, '사용자 상세 정보 응답 (조인)', { detailsData }); userDetails = detailsData[0]; } else { // 조인이 실패하면 기본 users 테이블만 조회 await logger.log(LOG_LEVELS.WARN, '조인 쿼리 실패, 기본 사용자 정보만 조회', { status: detailsRes.status }); const fallbackUrl = `${SUPABASE_URL}/rest/v1/users?select=*&email=eq.${encodeURIComponent(authUser.email)}&limit=1`; const fallbackRes = await fetch(fallbackUrl, { headers: { Authorization: `Bearer ${token}`, apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' } }); if (fallbackRes.ok) { const fallbackData = await fallbackRes.json(); await logger.log(LOG_LEVELS.DEBUG, '기본 사용자 정보 응답', { fallbackData }); userDetails = fallbackData[0]; } } } catch (detailsError) { await logger.log(LOG_LEVELS.WARN, '사용자 상세 정보 조회 중 오류', { error: detailsError.message }); } // UI 업데이트 updateDebugInfo('✅ 사용자 정보 로드 완료'); await logger.log(LOG_LEVELS.INFO, '사용자 정보 로드 성공', { email: authUser.email, hasDetails: !!userDetails, membershipLevel: userDetails?.membership_level, membershipLevels: userDetails?.membership_levels }); document.getElementById("login-section").style.display = "none"; document.getElementById("user-info-section").style.display = "block"; document.getElementById("user-email").textContent = authUser.email; // 상세 정보 표시 if (userDetails) { // 회원등급 표시 (membership_levels 조인 데이터 또는 membership_level 필드) let levelText = "기본"; let apiLimit = null; if (userDetails.membership_levels) { // 조인된 데이터가 있는 경우 levelText = userDetails.membership_levels.level || "기본"; apiLimit = userDetails.membership_levels.api_call_limit; } else if (userDetails.membership_level) { // 직접 필드가 있는 경우 levelText = userDetails.membership_level; } document.getElementById("user-level").textContent = levelText; // 오늘 호출량 표시 (daily_usage_count/api_call_limit 형태) const dailyUsage = userDetails.daily_usage_count ?? 0; let usageText = dailyUsage.toString(); if (apiLimit !== null && apiLimit !== undefined) { usageText = `${dailyUsage}/${apiLimit}`; } document.getElementById("user-usage").textContent = usageText; // 만료일 표시 document.getElementById("user-expire").textContent = userDetails.payment_period_end ? new Date(userDetails.payment_period_end).toLocaleDateString() : "없음"; await logger.log(LOG_LEVELS.INFO, '사용자 정보 UI 업데이트 완료', { level: levelText, usage: usageText, apiLimit: apiLimit }); } else { // 상세 정보가 없는 경우 기본값 document.getElementById("user-level").textContent = "기본"; document.getElementById("user-usage").textContent = "0"; document.getElementById("user-expire").textContent = "없음"; await logger.log(LOG_LEVELS.WARN, '사용자 상세 정보 없음, 기본값 사용'); } } catch (error) { updateDebugInfo(`❌ 사용자 정보 로드 실패: ${error.message}`); await logger.log(LOG_LEVELS.ERROR, '사용자 정보 로드 실패', { error: error.message }); document.getElementById("status").textContent = "⚠️ 사용자 정보 로드 실패: " + error.message; console.error('사용자 정보 로드 오류:', error); } }