1749 lines
60 KiB
JavaScript
1749 lines
60 KiB
JavaScript
// Chrome Extension 환경에서 안정적으로 동작하도록 수정
|
|
// 백엔드 설정은 background.js에서 중앙 관리됨
|
|
|
|
// 백엔드 설정 가져오기 함수
|
|
async function getBackendConfig() {
|
|
try {
|
|
console.log('[popup.js] 백엔드 설정 요청 시작');
|
|
|
|
const response = await chrome.runtime.sendMessage({
|
|
action: 'getBackendConfig'
|
|
});
|
|
|
|
console.log('[popup.js] 백엔드 설정 응답:', response);
|
|
|
|
if (response && response.success && response.config) {
|
|
console.log('[popup.js] 백엔드 설정 로드 성공:', response.config);
|
|
return response.config;
|
|
} else {
|
|
throw new Error('백엔드 설정 응답이 올바르지 않습니다');
|
|
}
|
|
} catch (error) {
|
|
console.error('[popup.js] 백엔드 설정 로드 실패:', error);
|
|
// 폴백 설정 없이 에러 전파 - background.js에서만 설정 관리
|
|
throw new Error('백엔드 설정을 가져올 수 없습니다. 확장 프로그램을 다시 로드해주세요.');
|
|
}
|
|
}
|
|
|
|
// 디버그 모드 플래그 (개발 시에만 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 = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>로그인 로그</title>
|
|
<style>
|
|
body { font-family: monospace; margin: 20px; }
|
|
.log-entry { margin: 10px 0; padding: 10px; border-left: 3px solid #ccc; }
|
|
.ERROR { border-left-color: #e74c3c; background: #fdf2f2; }
|
|
.WARN { border-left-color: #f39c12; background: #fef9e7; }
|
|
.INFO { border-left-color: #3498db; background: #ebf3fd; }
|
|
.DEBUG { border-left-color: #95a5a6; background: #f8f9fa; }
|
|
.timestamp { color: #666; font-size: 12px; }
|
|
.level { font-weight: bold; }
|
|
.message { margin: 5px 0; }
|
|
.data { color: #666; font-size: 12px; white-space: pre-wrap; }
|
|
.controls { margin-bottom: 20px; }
|
|
button { padding: 8px 16px; margin-right: 10px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>로그인 로그</h1>
|
|
<div class="controls">
|
|
<button onclick="location.reload()">새로고침</button>
|
|
<button onclick="if(confirm('모든 로그를 삭제하시겠습니까?')) clearLogs()">로그 삭제</button>
|
|
</div>
|
|
<div id="logs">
|
|
${logs.map(log => `
|
|
<div class="log-entry ${log.level}">
|
|
<div class="timestamp">${new Date(log.timestamp).toLocaleString()}</div>
|
|
<div class="level">[${log.level}]</div>
|
|
<div class="message">${log.message}</div>
|
|
${log.data ? `<div class="data">${log.data}</div>` : ''}
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
<script>
|
|
function clearLogs() {
|
|
// 부모 창의 logger를 통해 로그 삭제
|
|
if (window.opener && window.opener.logger) {
|
|
window.opener.logger.clearLogs().then(() => {
|
|
location.reload();
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
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 { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
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 { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
// 서버에서 토큰 유효성 검증
|
|
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) {
|
|
// 저장된 사용자 정보가 있으면 먼저 표시 (빠른 UI 응답)
|
|
const savedUserInfo = await chrome.storage.local.get([
|
|
'user_email', 'user_membership_level', 'user_api_limit',
|
|
'user_current_api_calls', 'user_expire_date'
|
|
]);
|
|
|
|
if (savedUserInfo.user_email) {
|
|
updateDebugInfo('⚡ 저장된 정보로 빠른 표시 중...');
|
|
await logger.log(LOG_LEVELS.INFO, '저장된 사용자 정보로 빠른 UI 표시');
|
|
|
|
// UI 즉시 업데이트
|
|
document.getElementById("login-section").style.display = "none";
|
|
document.getElementById("user-info-section").style.display = "block";
|
|
document.getElementById("user-email").textContent = savedUserInfo.user_email;
|
|
document.getElementById("user-level").textContent = savedUserInfo.user_membership_level || "기본";
|
|
|
|
// 사용량 표시
|
|
const currentCalls = savedUserInfo.user_current_api_calls || 0;
|
|
let usageText = currentCalls.toString();
|
|
if (savedUserInfo.user_api_limit) {
|
|
usageText = `${currentCalls}/${savedUserInfo.user_api_limit}`;
|
|
}
|
|
document.getElementById("user-usage").textContent = usageText;
|
|
|
|
// 만료일 표시
|
|
document.getElementById("user-expire").textContent = savedUserInfo.user_expire_date || "없음";
|
|
|
|
// 타이머 동기화
|
|
await handleTimerInitOrSync(false);
|
|
}
|
|
|
|
// 백그라운드에서 토큰 유효성 검증
|
|
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);
|
|
|
|
// 기존 토큰으로 자동 로그인이므로 타이머 동기화만 수행
|
|
await handleTimerInitOrSync(false);
|
|
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, '저장된 계정으로 자동 로그인 시도');
|
|
|
|
// 저장된 정보로 자동 로그인 (UI 필드는 채우지 않음)
|
|
try {
|
|
updateDebugInfo('🔐 자동 로그인 진행 중...');
|
|
const authResult = await supabaseAuth(savedEmail, savedPassword);
|
|
|
|
if (authResult.access_token) {
|
|
const token = authResult.access_token;
|
|
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
await chrome.storage.local.set({
|
|
access_token: token,
|
|
SUPABASE_URL,
|
|
SUPABASE_ANON_KEY
|
|
});
|
|
|
|
updateDebugInfo('✅ 자동 로그인 성공! 사용자 정보 로드 중...');
|
|
await loadUserInfo(token);
|
|
await handleTimerInitOrSync(true);
|
|
|
|
return true;
|
|
}
|
|
} catch (autoLoginError) {
|
|
updateDebugInfo(`❌ 자동 로그인 실패: ${autoLoginError.message}`);
|
|
await logger.log(LOG_LEVELS.ERROR, '자동 로그인 실패', { error: autoLoginError.message });
|
|
|
|
// 자동 로그인 실패 시 저장된 정보를 UI에 표시만 하고 사용자가 직접 로그인하도록 안내
|
|
document.getElementById("email").value = savedEmail;
|
|
document.getElementById("password").value = savedPassword;
|
|
document.getElementById("save-login").checked = true;
|
|
document.getElementById("auto-login").checked = 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", "isTimerInitialized"]);
|
|
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;
|
|
}
|
|
|
|
// 백엔드 설정 가져오기
|
|
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
// 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);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 설정 버튼
|
|
const settingsBtn = document.getElementById("settings-btn");
|
|
if (settingsBtn) {
|
|
await logger.log(LOG_LEVELS.DEBUG, '설정 버튼 이벤트 등록');
|
|
|
|
settingsBtn.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;
|
|
}
|
|
|
|
// 백엔드 설정 가져오기
|
|
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
// chrome.storage에 설정값 저장 (새 창에서 사용할 수 있도록)
|
|
await chrome.storage.local.set({
|
|
'settings_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
|
|
});
|
|
|
|
// 새 창에서 설정 페이지 열기
|
|
chrome.windows.create({
|
|
url: chrome.runtime.getURL('settings.html'),
|
|
type: 'popup',
|
|
width: 900,
|
|
height: 900
|
|
});
|
|
|
|
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;
|
|
}
|
|
|
|
// 백엔드 설정 가져오기
|
|
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
// 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);
|
|
}
|
|
});
|
|
|
|
// 찜관리 버튼 클릭 이벤트
|
|
document.getElementById('zzim-btn').addEventListener('click', async () => {
|
|
console.log('찜관리 버튼 클릭됨');
|
|
|
|
try {
|
|
// 현재 로그인된 토큰 확인
|
|
const { access_token, user_id } = await chrome.storage.local.get(["access_token", "user_id"]);
|
|
if (!access_token) {
|
|
alert('로그인이 필요합니다.');
|
|
return;
|
|
}
|
|
|
|
// 토큰 유효성 재확인
|
|
const isValid = await validateToken(access_token);
|
|
if (!isValid) {
|
|
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
|
await chrome.storage.local.remove("access_token");
|
|
location.reload();
|
|
return;
|
|
}
|
|
|
|
// 백엔드 설정 가져오기
|
|
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
// chrome.storage에 설정값 저장 (새 창에서 사용할 수 있도록)
|
|
await chrome.storage.local.set({
|
|
'zzim_config': {
|
|
SUPABASE_URL: SUPABASE_URL,
|
|
SUPABASE_ANON_KEY: SUPABASE_ANON_KEY,
|
|
DEBUG_MODE: DEBUG_MODE,
|
|
ACCESS_TOKEN: access_token,
|
|
USER_ID: user_id,
|
|
timestamp: Date.now()
|
|
}
|
|
});
|
|
|
|
await logger.log(LOG_LEVELS.INFO, '찜관리 창 설정 저장 완료', {
|
|
hasUrl: !!SUPABASE_URL,
|
|
hasKey: !!SUPABASE_ANON_KEY,
|
|
hasToken: !!access_token,
|
|
hasUserId: !!user_id,
|
|
debugMode: DEBUG_MODE
|
|
});
|
|
|
|
// 새 창에서 찜관리 페이지 열기
|
|
chrome.windows.create({
|
|
url: chrome.runtime.getURL('zzim.html'),
|
|
type: 'popup',
|
|
width: 1200,
|
|
height: 800
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('찜관리 창 열기 실패:', error);
|
|
alert('찜관리 창을 열 수 없습니다: ' + error.message);
|
|
}
|
|
});
|
|
|
|
// 모달 초기화
|
|
initializeModal('banned-words-modal');
|
|
initializeModal('kipris-modal');
|
|
initializeModal('sayings-modal');
|
|
|
|
// 🚀 자동 로그인 시도 (가장 중요한 부분)
|
|
updateDebugInfo('🚀 자동 로그인 시도 중...');
|
|
const autoLoginSuccess = await attemptAutoLogin();
|
|
|
|
if (!autoLoginSuccess) {
|
|
updateDebugInfo('✅ 초기화 완료 - 수동 로그인이 필요합니다');
|
|
}
|
|
|
|
// API 키 로딩
|
|
await loadApiKeysFromFile();
|
|
|
|
// 설정 변경 감지 시작
|
|
setupStorageListener();
|
|
watchSettingsChanges();
|
|
console.log('설정 변경 감지 시스템 활성화됨');
|
|
|
|
await logger.log(LOG_LEVELS.INFO, '초기화 완료');
|
|
|
|
// 타이머 초기화 또는 동기화는 로그인 시점에서 처리됨 (중복 호출 방지)
|
|
|
|
} catch (error) {
|
|
updateDebugInfo(`❌ 초기화 실패: ${error.message}`);
|
|
await logger.log(LOG_LEVELS.ERROR, '초기화 중 치명적 오류', { error: error.message });
|
|
console.error('초기화 오류:', error);
|
|
}
|
|
});
|
|
|
|
// 로컬 파일에서 API 키 로드
|
|
async function loadApiKeysFromFile() {
|
|
try {
|
|
await logger.log(LOG_LEVELS.INFO, 'API 키 로딩 시작');
|
|
|
|
// 로컬 파일에서 API 키 읽기
|
|
const response = await fetch(chrome.runtime.getURL('api-keys.json'));
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API 키 파일 로드 실패: ${response.status}`);
|
|
}
|
|
|
|
const apiKeys = await response.json();
|
|
await logger.log(LOG_LEVELS.DEBUG, 'API 키 파일 읽기 완료', { keys: Object.keys(apiKeys) });
|
|
|
|
// Chrome storage에 API 키 저장 (background.js에서 사용할 수 있도록)
|
|
// 각 서비스별로 정확한 구조로 저장
|
|
const storageData = {};
|
|
|
|
if (apiKeys.papago && apiKeys.papago.clientId && apiKeys.papago.clientSecret) {
|
|
storageData.papago_api_key = {
|
|
clientId: apiKeys.papago.clientId,
|
|
clientSecret: apiKeys.papago.clientSecret
|
|
};
|
|
await logger.log(LOG_LEVELS.DEBUG, 'Papago API 키 준비 완료');
|
|
} else {
|
|
await logger.log(LOG_LEVELS.WARN, 'Papago API 키가 누락되었습니다');
|
|
}
|
|
|
|
if (apiKeys.deepl && apiKeys.deepl.authKey) {
|
|
storageData.deepl_api_key = {
|
|
authKey: apiKeys.deepl.authKey
|
|
};
|
|
await logger.log(LOG_LEVELS.DEBUG, 'DeepL API 키 준비 완료');
|
|
} else {
|
|
await logger.log(LOG_LEVELS.WARN, 'DeepL API 키가 누락되었습니다');
|
|
}
|
|
|
|
if (apiKeys.openai && apiKeys.openai.apiKey) {
|
|
storageData.openai_api_key = {
|
|
apiKey: apiKeys.openai.apiKey
|
|
};
|
|
await logger.log(LOG_LEVELS.DEBUG, 'OpenAI API 키 준비 완료');
|
|
} else {
|
|
await logger.log(LOG_LEVELS.WARN, 'OpenAI API 키가 누락되었습니다');
|
|
}
|
|
|
|
if (apiKeys.gemini && apiKeys.gemini.apiKey) {
|
|
storageData.gemini_api_key = {
|
|
apiKey: apiKeys.gemini.apiKey
|
|
};
|
|
await logger.log(LOG_LEVELS.DEBUG, 'Gemini API 키 준비 완료');
|
|
} else {
|
|
await logger.log(LOG_LEVELS.WARN, 'Gemini API 키가 누락되었습니다');
|
|
}
|
|
|
|
// Chrome storage에 저장
|
|
await chrome.storage.local.set(storageData);
|
|
|
|
// 저장 확인
|
|
const saved = await chrome.storage.local.get(Object.keys(storageData));
|
|
await logger.log(LOG_LEVELS.INFO, 'API 키 Chrome storage 저장 완료', {
|
|
savedKeys: Object.keys(saved),
|
|
totalKeys: Object.keys(storageData).length
|
|
});
|
|
|
|
console.log('✅ API 키 로드 및 저장 완료:', Object.keys(storageData));
|
|
|
|
} catch (error) {
|
|
console.error('❌ API 키 파일 로드 오류:', error);
|
|
await logger.log(LOG_LEVELS.ERROR, 'API 키 파일 로드 실패', {
|
|
error: error.message,
|
|
stack: error.stack
|
|
});
|
|
|
|
// 사용자에게 알림
|
|
if (DEBUG_MODE) {
|
|
updateDebugInfo(`❌ API 키 로드 실패: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 로그인 함수
|
|
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;
|
|
// 백엔드 설정 가져오기
|
|
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
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);
|
|
|
|
// 새로운 로그인이므로 타이머 초기화
|
|
await handleTimerInitOrSync(true);
|
|
} 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 {
|
|
// Background Script에 사용자 정보 요청
|
|
const response = await chrome.runtime.sendMessage({
|
|
action: 'getUserInfo',
|
|
token: token
|
|
});
|
|
|
|
if (!response || !response.success) {
|
|
throw new Error(response?.error || '사용자 정보를 불러올 수 없습니다.');
|
|
}
|
|
|
|
const { user } = response;
|
|
|
|
await logger.log(LOG_LEVELS.DEBUG, '사용자 정보 응답', { user });
|
|
|
|
// UI 업데이트
|
|
updateDebugInfo('✅ 사용자 정보 로드 완료');
|
|
|
|
// 사용자 ID를 storage에 저장
|
|
if (user.id) {
|
|
let apiLimit = null;
|
|
if (user.membership_levels) {
|
|
apiLimit = user.membership_levels.api_call_limit;
|
|
}
|
|
|
|
// 필요한 정보 저장
|
|
const { SUPABASE_URL, SUPABASE_ANON_KEY } = await getBackendConfig();
|
|
|
|
await chrome.storage.local.set({
|
|
user_id: user.id,
|
|
user_email: user.email,
|
|
user_membership_level: user.membership_level,
|
|
user_api_limit: apiLimit,
|
|
user_current_api_calls: user.current_api_calls || 0,
|
|
SUPABASE_URL,
|
|
SUPABASE_ANON_KEY
|
|
});
|
|
}
|
|
|
|
document.getElementById("login-section").style.display = "none";
|
|
document.getElementById("user-info-section").style.display = "block";
|
|
document.getElementById("user-email").textContent = user.email;
|
|
|
|
// 상세 정보 표시
|
|
let levelText = "기본";
|
|
let apiLimit = null;
|
|
|
|
if (user.membership_levels) {
|
|
levelText = user.membership_levels.level || "기본";
|
|
apiLimit = user.membership_levels.api_call_limit;
|
|
} else if (user.membership_level) {
|
|
levelText = user.membership_level;
|
|
}
|
|
|
|
document.getElementById("user-level").textContent = levelText;
|
|
|
|
const { user_current_api_calls } = await chrome.storage.local.get(['user_current_api_calls']);
|
|
const currentCalls = user_current_api_calls || user.current_api_calls || 0;
|
|
|
|
let usageText = currentCalls.toString();
|
|
if (apiLimit !== null && apiLimit !== undefined) {
|
|
usageText = `${currentCalls}/${apiLimit}`;
|
|
}
|
|
|
|
document.getElementById("user-usage").textContent = usageText;
|
|
|
|
document.getElementById("user-expire").textContent = user.payment_period_end
|
|
? new Date(user.payment_period_end).toLocaleDateString()
|
|
: "없음";
|
|
|
|
} catch (error) {
|
|
updateDebugInfo(`❌ 사용자 정보 로드 실패: ${error.message}`);
|
|
await logger.log(LOG_LEVELS.ERROR, '사용자 정보 로드 실패', { error: error.message });
|
|
document.getElementById("status").textContent = "⚠️ 사용자 정보 로드 실패: " + error.message;
|
|
console.error('사용자 정보 로드 오류:', error);
|
|
}
|
|
}
|
|
|
|
// 카운트다운 타이머 관련 변수
|
|
let countdownInterval = null;
|
|
let settingsWatcher = null; // 설정 변경 감지용
|
|
let isCountdownInitializing = false; // 중복 초기화 방지
|
|
|
|
// 타이머 초기화 상태를 Chrome Storage에서 관리하는 함수들
|
|
async function getTimerInitializedStatus() {
|
|
try {
|
|
const result = await chrome.storage.local.get('isTimerInitialized');
|
|
return result.isTimerInitialized || false;
|
|
} catch (error) {
|
|
console.error('타이머 초기화 상태 확인 실패:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function setTimerInitializedStatus(status) {
|
|
try {
|
|
await chrome.storage.local.set({ isTimerInitialized: status });
|
|
console.log('타이머 초기화 상태 저장:', status);
|
|
} catch (error) {
|
|
console.error('타이머 초기화 상태 저장 실패:', error);
|
|
}
|
|
}
|
|
|
|
// 타이머 초기화 또는 동기화 함수 (loadUserInfo에서 분리)
|
|
async function handleTimerInitOrSync(isFirstLogin = false) {
|
|
try {
|
|
const isTimerInitialized = await getTimerInitializedStatus();
|
|
|
|
if (!isTimerInitialized || isFirstLogin) {
|
|
console.log('타이머 초기화 수행:', { isFirstLogin, isTimerInitialized });
|
|
await setTimerInitializedStatus(true);
|
|
|
|
// 잠시 대기 후 초기화
|
|
setTimeout(() => {
|
|
if (!isCountdownInitializing) {
|
|
initBreakCountdown();
|
|
}
|
|
}, 500);
|
|
|
|
// Background 타이머 시작 요청
|
|
try {
|
|
await logger.log(LOG_LEVELS.INFO, 'Background 타이머 시작 요청');
|
|
const timerStartResponse = await chrome.runtime.sendMessage({
|
|
action: 'startTimerAfterLogin'
|
|
});
|
|
|
|
if (timerStartResponse && timerStartResponse.success) {
|
|
await logger.log(LOG_LEVELS.INFO, 'Background 타이머 시작 성공');
|
|
} else {
|
|
await logger.log(LOG_LEVELS.WARN, 'Background 타이머 시작 실패', {
|
|
response: timerStartResponse
|
|
});
|
|
}
|
|
} catch (timerError) {
|
|
await logger.log(LOG_LEVELS.ERROR, 'Background 타이머 시작 요청 중 오류', {
|
|
error: timerError.message
|
|
});
|
|
}
|
|
|
|
} else {
|
|
console.log('이미 초기화됨 - 타이머 동기화만 수행');
|
|
// 동기화만 수행 (리셋하지 않음)
|
|
if (!isCountdownInitializing) {
|
|
// Background 타이머 상태만 확인하고 UI 업데이트
|
|
try {
|
|
const response = await chrome.runtime.sendMessage({
|
|
action: 'getTimerStatus'
|
|
});
|
|
|
|
if (response && response.success && response.isRunning) {
|
|
const remainingMs = response.remainingTime;
|
|
console.log('Background 타이머 동기화 (UI만 업데이트):', {
|
|
남은시간_분: Math.floor(remainingMs / (1000 * 60)),
|
|
남은시간_초: Math.floor((remainingMs % (1000 * 60)) / 1000)
|
|
});
|
|
|
|
// 기존 카운트다운이 있으면 정리하고 새로 시작
|
|
if (countdownInterval) {
|
|
clearInterval(countdownInterval);
|
|
}
|
|
|
|
const nextBreakTime = new Date(Date.now() + remainingMs);
|
|
startCountdown(nextBreakTime);
|
|
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl) {
|
|
countdownEl.style.color = '#1e7e1e';
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('타이머 동기화 실패:', error);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('타이머 초기화/동기화 처리 실패:', error);
|
|
}
|
|
}
|
|
|
|
// 카운트다운 타이머 초기화 함수
|
|
async function initBreakCountdown() {
|
|
// 중복 실행 방지
|
|
if (isCountdownInitializing) {
|
|
console.log('카운트다운 초기화가 이미 진행 중입니다.');
|
|
return;
|
|
}
|
|
|
|
isCountdownInitializing = true;
|
|
|
|
try {
|
|
// 설정에서 시간 알림 설정 가져오기
|
|
const settings = await chrome.storage.local.get(['time_alarm_settings']);
|
|
let timeAlarmSettings = settings.time_alarm_settings;
|
|
|
|
// 시간 알림 설정이 없으면 기본값으로 활성화
|
|
if (!timeAlarmSettings) {
|
|
timeAlarmSettings = {
|
|
enabled: true, // 기본값을 true로 변경
|
|
workTime: 60,
|
|
restTime: 5
|
|
};
|
|
|
|
// 기본 설정 저장
|
|
await chrome.storage.local.set({ time_alarm_settings: timeAlarmSettings });
|
|
console.log('시간 알림 설정이 없어서 기본값으로 활성화:', timeAlarmSettings);
|
|
}
|
|
|
|
// 디버깅을 위한 로그 추가
|
|
console.log('카운트다운 타이머 설정:', timeAlarmSettings);
|
|
|
|
// 타이머 섹션 항상 표시 (로그인 시에는)
|
|
const timerSection = document.getElementById('break-timer-section');
|
|
if (timerSection) {
|
|
timerSection.style.display = 'block';
|
|
}
|
|
|
|
// 시간 알림이 비활성화된 경우에만 메시지 표시
|
|
if (!timeAlarmSettings.enabled) {
|
|
console.log('시간 알림이 비활성화됨 - 설정에서 활성화 필요');
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl) {
|
|
countdownEl.textContent = '⚙️ 설정에서 시간 알림을 활성화해주세요';
|
|
countdownEl.style.color = '#f39c12';
|
|
}
|
|
|
|
// 기존 타이머 정리
|
|
if (countdownInterval) {
|
|
clearInterval(countdownInterval);
|
|
countdownInterval = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Background에서 현재 타이머 상태 가져오기 (리셋하지 않고 동기화만)
|
|
try {
|
|
const response = await chrome.runtime.sendMessage({
|
|
action: 'getTimerStatus'
|
|
});
|
|
|
|
if (response && response.success && response.isRunning) {
|
|
// Background 타이머가 실행 중인 경우 - 남은 시간으로 동기화
|
|
const remainingMs = response.remainingTime;
|
|
|
|
console.log('Background 타이머와 동기화 (리셋 없음):', {
|
|
남은시간_분: Math.floor(remainingMs / (1000 * 60)),
|
|
남은시간_초: Math.floor((remainingMs % (1000 * 60)) / 1000)
|
|
});
|
|
|
|
// 기존 카운트다운이 있으면 정리하고 새로 시작
|
|
if (countdownInterval) {
|
|
clearInterval(countdownInterval);
|
|
}
|
|
|
|
const nextBreakTime = new Date(Date.now() + remainingMs);
|
|
startCountdown(nextBreakTime);
|
|
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl) {
|
|
countdownEl.style.color = '#1e7e1e'; // 정상 색상
|
|
}
|
|
|
|
} else {
|
|
// Background 타이머가 실행 중이지 않은 경우
|
|
console.log('Background 타이머가 실행 중이 아님');
|
|
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl) {
|
|
countdownEl.textContent = '타이머가 시작되지 않았습니다';
|
|
countdownEl.style.color = '#e74c3c';
|
|
}
|
|
|
|
// 기존 카운트다운 정리
|
|
if (countdownInterval) {
|
|
clearInterval(countdownInterval);
|
|
countdownInterval = null;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Background 타이머 상태 확인 실패:', error);
|
|
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl) {
|
|
countdownEl.textContent = '타이머 상태 확인 중...';
|
|
countdownEl.style.color = '#3498db';
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('카운트다운 타이머 초기화 오류:', error);
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl) {
|
|
countdownEl.textContent = '설정 오류';
|
|
countdownEl.style.color = '#e74c3c';
|
|
}
|
|
} finally {
|
|
isCountdownInitializing = false;
|
|
}
|
|
}
|
|
|
|
// 설정 변경 감지 및 타이머 리셋 함수
|
|
function watchSettingsChanges() {
|
|
// 기존 감시자가 있으면 정리
|
|
if (settingsWatcher) {
|
|
clearInterval(settingsWatcher);
|
|
}
|
|
|
|
let lastSettings = null;
|
|
|
|
// 5초마다 설정 변경 확인
|
|
settingsWatcher = setInterval(async () => {
|
|
try {
|
|
const settings = await chrome.storage.local.get(['time_alarm_settings']);
|
|
const currentSettings = settings.time_alarm_settings;
|
|
|
|
// 설정이 변경되었는지 확인
|
|
if (lastSettings && JSON.stringify(lastSettings) !== JSON.stringify(currentSettings)) {
|
|
console.log('시간 알림 설정 변경 감지:', {
|
|
이전: lastSettings,
|
|
현재: currentSettings
|
|
});
|
|
|
|
// 타이머 리셋 및 재시작
|
|
console.log('타이머 리셋 및 재시작');
|
|
await initBreakCountdown();
|
|
|
|
// 사용자에게 알림
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl && currentSettings && currentSettings.enabled) {
|
|
// 잠시 리셋 메시지 표시
|
|
const originalText = countdownEl.textContent;
|
|
countdownEl.textContent = '⚙️ 설정 변경됨 - 타이머 리셋';
|
|
countdownEl.style.color = '#2196F3';
|
|
|
|
setTimeout(() => {
|
|
countdownEl.style.color = '';
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
lastSettings = currentSettings ? JSON.parse(JSON.stringify(currentSettings)) : null;
|
|
|
|
} catch (error) {
|
|
console.error('설정 변경 감지 오류:', error);
|
|
}
|
|
}, 5000); // 5초마다 확인
|
|
}
|
|
|
|
// Chrome storage 변경 이벤트 리스너 (더 즉각적인 반응)
|
|
function setupStorageListener() {
|
|
if (chrome.storage && chrome.storage.onChanged) {
|
|
chrome.storage.onChanged.addListener((changes, namespace) => {
|
|
if (namespace === 'local' && changes.time_alarm_settings) {
|
|
console.log('Chrome storage 변경 감지 - time_alarm_settings:', {
|
|
이전값: changes.time_alarm_settings.oldValue,
|
|
새값: changes.time_alarm_settings.newValue
|
|
});
|
|
|
|
// 즉시 타이머 리셋
|
|
setTimeout(() => {
|
|
initBreakCountdown();
|
|
|
|
// 사용자에게 즉시 피드백
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (countdownEl) {
|
|
const newSettings = changes.time_alarm_settings.newValue;
|
|
if (newSettings && newSettings.enabled) {
|
|
countdownEl.textContent = '⚙️ 설정 적용됨';
|
|
countdownEl.style.color = '#4CAF50';
|
|
|
|
setTimeout(() => {
|
|
countdownEl.style.color = '';
|
|
}, 1500);
|
|
}
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
// API 호출량 변경 감지 및 UI 업데이트
|
|
if (namespace === 'local' && changes.user_current_api_calls) {
|
|
console.log('Chrome storage 변경 감지 - user_current_api_calls:', {
|
|
이전값: changes.user_current_api_calls.oldValue,
|
|
새값: changes.user_current_api_calls.newValue
|
|
});
|
|
|
|
// 즉시 사용자 호출량 UI 업데이트
|
|
updateUserUsageDisplay();
|
|
}
|
|
});
|
|
|
|
console.log('Chrome storage 변경 리스너 설정 완료');
|
|
}
|
|
}
|
|
|
|
// 사용자 호출량 표시 업데이트 함수
|
|
async function updateUserUsageDisplay() {
|
|
try {
|
|
const { user_current_api_calls, user_api_limit } = await chrome.storage.local.get([
|
|
'user_current_api_calls',
|
|
'user_api_limit'
|
|
]);
|
|
|
|
const userUsageEl = document.getElementById('user-usage');
|
|
if (userUsageEl) {
|
|
const currentCalls = user_current_api_calls || 0;
|
|
let usageText = currentCalls.toString();
|
|
|
|
if (user_api_limit !== null && user_api_limit !== undefined) {
|
|
usageText = `${currentCalls}/${user_api_limit}`;
|
|
}
|
|
|
|
userUsageEl.textContent = usageText;
|
|
|
|
// 시각적 피드백 제공
|
|
userUsageEl.style.color = '#4CAF50';
|
|
userUsageEl.style.fontWeight = 'bold';
|
|
|
|
setTimeout(() => {
|
|
userUsageEl.style.color = '';
|
|
userUsageEl.style.fontWeight = '';
|
|
}, 2000);
|
|
|
|
console.log('사용자 호출량 UI 업데이트 완료:', {
|
|
currentCalls,
|
|
apiLimit: user_api_limit,
|
|
displayText: usageText
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('사용자 호출량 UI 업데이트 실패:', error);
|
|
}
|
|
}
|
|
|
|
// 카운트다운 시작 함수
|
|
function startCountdown(targetTime) {
|
|
// 기존 타이머가 있으면 정리
|
|
if (countdownInterval) {
|
|
clearInterval(countdownInterval);
|
|
}
|
|
|
|
const countdownEl = document.getElementById('break-countdown');
|
|
if (!countdownEl) return;
|
|
|
|
countdownInterval = setInterval(() => {
|
|
const now = new Date();
|
|
const timeLeft = targetTime.getTime() - now.getTime();
|
|
|
|
if (timeLeft <= 0) {
|
|
// 시간이 다 되었을 때
|
|
countdownEl.textContent = '휴식 시간입니다! 🎉';
|
|
clearInterval(countdownInterval);
|
|
|
|
// 새로운 카운트다운 시작 (휴식 시간 후 다시 작업 시간)
|
|
setTimeout(() => {
|
|
initBreakCountdown();
|
|
}, 1000);
|
|
|
|
return;
|
|
}
|
|
|
|
// 남은 시간을 시:분:초 형태로 표시
|
|
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
|
|
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
|
|
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
|
|
|
|
let timeString = '';
|
|
if (hours > 0) {
|
|
timeString = `${hours}시간 ${minutes}분 ${seconds}초`;
|
|
} else if (minutes > 0) {
|
|
timeString = `${minutes}분 ${seconds}초`;
|
|
} else {
|
|
timeString = `${seconds}초`;
|
|
}
|
|
|
|
countdownEl.textContent = timeString;
|
|
}, 1000);
|
|
}
|
|
|
|
// 페이지 언로드 시 타이머 정리
|
|
window.addEventListener('beforeunload', () => {
|
|
// 카운트다운 타이머 정리
|
|
if (countdownInterval) {
|
|
clearInterval(countdownInterval);
|
|
countdownInterval = null;
|
|
}
|
|
|
|
// 설정 감시 타이머 정리
|
|
if (settingsWatcher) {
|
|
clearInterval(settingsWatcher);
|
|
settingsWatcher = null;
|
|
}
|
|
|
|
console.log('모든 타이머 정리 완료');
|
|
});
|
|
|
|
// 팝업 포커스 시 사용자 호출량 업데이트
|
|
window.addEventListener('focus', () => {
|
|
console.log('팝업 포커스 - 사용자 호출량 업데이트');
|
|
updateUserUsageDisplay();
|
|
});
|
|
|
|
// 팝업 가시성 변경 시 사용자 호출량 업데이트
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (!document.hidden) {
|
|
console.log('팝업 가시성 변경 - 사용자 호출량 업데이트');
|
|
updateUserUsageDisplay();
|
|
}
|
|
});
|
|
|
|
// 매뉴얼 버튼 이벤트 리스너
|
|
document.getElementById('manual-btn').addEventListener('click', function() {
|
|
chrome.tabs.create({ url: chrome.runtime.getURL('manual.html') });
|
|
}); |