// 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);
}
}