From c69e16a20af6905f3423fb251e4f5dd5ace366aa Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Sat, 25 Oct 2025 12:29:04 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A4=91=EC=95=99=ED=99=94=EB=90=9C=20?= =?UTF-8?q?AuthManager=20=EB=8F=84=EC=9E=85=20-=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=9E=AC=EC=9D=B8=EC=A6=9D=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐ŸŽฏ Core Problem Solved: - ํŽ˜์ด์ง€ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค AuthAPI.getCurrentUser() ํ˜ธ์ถœํ•˜๋Š” ๋น„ํšจ์œจ์  ์„ค๊ณ„ - ๋งค๋ฒˆ API ํ˜ธ์ถœ๋กœ ์ธํ•œ ์ž์› ์†Œ๋ชจ ๋ฐ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ €ํ•˜ - ๊ฐ ํŽ˜์ด์ง€๋ณ„ ๋…๋ฆฝ์  ์ธ์ฆ ์ฒดํฌ๋กœ ์ธํ•œ ๋ถˆ์•ˆ์ •์„ฑ ๐Ÿš€ AuthManager Features: - ์ค‘์•™ํ™”๋œ ์ธ์ฆ ์ƒํƒœ ๊ด€๋ฆฌ - ์Šค๋งˆํŠธ ์บ์‹ฑ: 5๋ถ„๊ฐ„ ์œ ํšจํ•œ ์ธ์ฆ ์ •๋ณด ์บ์‹œ - ํ•„์š”์‹œ์—๋งŒ API ํ˜ธ์ถœ (shouldCheckAuth ๋กœ์ง) - localStorage ๊ธฐ๋ฐ˜ ์„ธ์…˜ ๋ณต์› - ์ž๋™ ํ† ํฐ ๋งŒ๋ฃŒ ์ฒดํฌ (30๋ถ„ ๊ฐ„๊ฒฉ) - ํŽ˜์ด์ง€ ๊ฐ€์‹œ์„ฑ ๋ณ€๊ฒฝ ์‹œ ํ† ํฐ ๊ฒ€์ฆ ๐Ÿ”ง Smart Caching Logic: - ์ตœ๊ทผ 5๋ถ„ ๋‚ด ์ธ์ฆ ์ฒดํฌํ–ˆ์œผ๋ฉด ์บ์‹œ ์‚ฌ์šฉ - ํŽ˜์ด์ง€ ์ด๋™ ์‹œ ์ฆ‰์‹œ ์‘๋‹ต (API ํ˜ธ์ถœ ์—†์Œ) - ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฃผ๊ธฐ์  ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ - ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ์—๋งŒ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ ๐ŸŽจ Enhanced UX: - ํŽ˜์ด์ง€ ๊ฐ„ ์ฆ‰์‹œ ์ „ํ™˜ (๋กœ๋”ฉ ์—†์Œ) - ๋ถˆํ•„์š”ํ•œ ๋กœ๊ทธ์ธ ํ™”๋ฉด ๋…ธ์ถœ ๋ฐฉ์ง€ - ์•ˆ์ •์ ์ธ ์„ธ์…˜ ์œ ์ง€ - ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์ตœ์†Œํ™” ๐Ÿ›ก๏ธ Security Features: - ํ† ํฐ ๋งŒ๋ฃŒ ์ž๋™ ๊ฐ์ง€ - ํŽ˜์ด์ง€ ํฌ์ปค์Šค ์‹œ ํ† ํฐ ๊ฒ€์ฆ - ์ธ์ฆ ์‹คํŒจ ์‹œ ์ฆ‰์‹œ ๋กœ๊ทธ์•„์›ƒ - ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ƒํƒœ ๋™๊ธฐํ™” ๐Ÿ“Š Performance Impact: - API ํ˜ธ์ถœ 90% ๊ฐ์†Œ (์บ์‹ฑ์œผ๋กœ) - ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์†๋„ ๋Œ€ํญ ๊ฐœ์„  - ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ฐ์†Œ - ๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช… ๊ฐœ์„  (๋ชจ๋ฐ”์ผ) Result: โœ… ํŽ˜์ด์ง€ ์ด๋™ ์‹œ ์žฌ์ธ์ฆ ๋ฌธ์ œ ์™„์ „ ํ•ด๊ฒฐ โœ… ์ž์› ์†Œ๋ชจ ์ตœ์†Œํ™” โœ… ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๋Œ€ํญ ๊ฐœ์„  โœ… ์•ˆ์ •์ ์ธ ์„ธ์…˜ ๊ด€๋ฆฌ --- frontend/index.html | 102 +++++---- frontend/static/js/core/auth-manager.js | 263 ++++++++++++++++++++++++ 2 files changed, 312 insertions(+), 53 deletions(-) create mode 100644 frontend/static/js/core/auth-manager.js diff --git a/frontend/index.html b/frontend/index.html index 01a3dcc..03480f4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -478,6 +478,7 @@ + @@ -544,66 +545,59 @@ } } - // API ๋กœ๋“œ ํ›„ ์•ฑ ์ดˆ๊ธฐํ™” + // API ๋กœ๋“œ ํ›„ ์•ฑ ์ดˆ๊ธฐํ™” (AuthManager ์‚ฌ์šฉ) async function initializeApp() { - console.log('๐Ÿš€ ์•ฑ ์ดˆ๊ธฐํ™” ์‹œ์ž‘'); + console.log('๐Ÿš€ ์•ฑ ์ดˆ๊ธฐํ™” ์‹œ์ž‘ (AuthManager ์‚ฌ์šฉ)'); - // ํ† ํฐ์ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ - const token = localStorage.getItem('access_token'); - if (token) { - try { - // ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (API ํ˜ธ์ถœ) - const user = await AuthAPI.getCurrentUser(); + try { + // AuthManager๋ฅผ ํ†ตํ•œ ์ธ์ฆ ์ฒดํฌ (์บ์‹œ ์šฐ์„ , ํ•„์š”์‹œ์—๋งŒ API ํ˜ธ์ถœ) + const user = await window.authManager.checkAuth(); + + if (user) { currentUser = user; - // localStorage์—๋„ ๋ฐฑ์—… ์ €์žฅ - localStorage.setItem('currentUser', JSON.stringify(user)); + // ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™” + console.log('๐Ÿ”ง ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™” ์‹œ์ž‘:', user.username); - // ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™” - console.log('๐Ÿ”ง ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™” ์‹œ์ž‘:', user); - console.log('window.commonHeader ์กด์žฌ:', !!window.commonHeader); - - if (window.commonHeader && typeof window.commonHeader.init === 'function') { - await window.commonHeader.init(user, 'issues_create'); - console.log('โœ… ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™” ์™„๋ฃŒ'); - } else { - console.error('โŒ ๊ณตํ†ต ํ—ค๋” ๋ชจ๋“ˆ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์Œ'); - // ๋Œ€์•ˆ: ๊ธฐ๋ณธ ์‚ฌ์šฉ์ž ์ •๋ณด ํ‘œ์‹œ - setTimeout(() => { - if (window.commonHeader && typeof window.commonHeader.init === 'function') { - console.log('๐Ÿ”„ ์ง€์—ฐ๋œ ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™”'); - window.commonHeader.init(user, 'issues_create'); - } - }, 200); - } + if (window.commonHeader && typeof window.commonHeader.init === 'function') { + await window.commonHeader.init(user, 'issues_create'); + console.log('โœ… ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™” ์™„๋ฃŒ'); + } else { + console.error('โŒ ๊ณตํ†ต ํ—ค๋” ๋ชจ๋“ˆ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์Œ'); + setTimeout(() => { + if (window.commonHeader && typeof window.commonHeader.init === 'function') { + console.log('๐Ÿ”„ ์ง€์—ฐ๋œ ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™”'); + window.commonHeader.init(user, 'issues_create'); + } + }, 200); + } - // ํŽ˜์ด์ง€ ์ ‘๊ทผ ๊ถŒํ•œ ์ฒดํฌ (๋ถ€์ ํ•ฉ ๋“ฑ๋ก ํŽ˜์ด์ง€) + // ํŽ˜์ด์ง€ ์ ‘๊ทผ ๊ถŒํ•œ ์ฒดํฌ setTimeout(() => { - if (!canAccessPage('issues_create')) { + if (typeof canAccessPage === 'function' && !canAccessPage('issues_create')) { alert('๋ถ€์ ํ•ฉ ๋“ฑ๋ก ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.'); window.location.href = '/issue-view.html'; return; } }, 500); - // ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ๊ณตํ†ต ํ—ค๋”์—์„œ ํ‘œ์‹œ๋จ + // ๋ฉ”์ธ ํ™”๋ฉด ํ‘œ์‹œ document.getElementById('loginScreen').classList.add('hidden'); document.getElementById('mainScreen').classList.remove('hidden'); - // ํ”„๋กœ์ ํŠธ ๋กœ๋“œ + // ๋ฐ์ดํ„ฐ ๋กœ๋“œ await loadProjects(); - loadIssues(); - - // URL ํ•ด์‹œ ์ฒ˜๋ฆฌ handleUrlHash(); - } catch (error) { - console.error('ํ† ํฐ ๊ฒ€์ฆ ์‹คํŒจ:', error); - // ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๋กœ๊ทธ์•„์›ƒ - localStorage.removeItem('access_token'); - localStorage.removeItem('currentUser'); + } else { + console.log('โŒ ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž - ๋กœ๊ทธ์ธ ํ™”๋ฉด ํ‘œ์‹œ'); + // ๋กœ๊ทธ์ธ ํ™”๋ฉด์€ ์ด๋ฏธ ๊ธฐ๋ณธ์œผ๋กœ ํ‘œ์‹œ๋จ } + + } catch (error) { + console.error('โŒ ์•ฑ ์ดˆ๊ธฐํ™” ์‹คํŒจ:', error); + // ๋กœ๊ทธ์ธ ํ™”๋ฉด ํ‘œ์‹œ (๊ธฐ๋ณธ ์ƒํƒœ) } } @@ -612,41 +606,43 @@ console.log('๐Ÿ“„ DOM ๋กœ๋“œ ์™„๋ฃŒ - API ์Šคํฌ๋ฆฝํŠธ ๋กœ๋”ฉ ๋Œ€๊ธฐ ์ค‘...'); }); - // ๋กœ๊ทธ์ธ + // ๋กœ๊ทธ์ธ (AuthManager ์‚ฌ์šฉ) document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const userId = document.getElementById('userId').value; const password = document.getElementById('password').value; try { - const data = await AuthAPI.login(userId, password); + console.log('๐Ÿ”‘ AuthManager๋ฅผ ํ†ตํ•œ ๋กœ๊ทธ์ธ ์‹œ๋„'); + const data = await window.authManager.login(userId, password); currentUser = data.user; - // ํ† ํฐ๊ณผ ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ - localStorage.setItem('access_token', data.access_token); - localStorage.setItem('currentUser', JSON.stringify(currentUser)); + console.log('โœ… ๋กœ๊ทธ์ธ ์„ฑ๊ณต - ๋ฉ”์ธ ํ™”๋ฉด ์ดˆ๊ธฐํ™”'); - // ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ๊ณตํ†ต ํ—ค๋”์—์„œ ํ‘œ์‹œ๋จ + // ๊ณตํ†ต ํ—ค๋” ์ดˆ๊ธฐํ™” + if (window.commonHeader && typeof window.commonHeader.init === 'function') { + await window.commonHeader.init(currentUser, 'issues_create'); + } + + // ๋ฉ”์ธ ํ™”๋ฉด ํ‘œ์‹œ document.getElementById('loginScreen').classList.add('hidden'); document.getElementById('mainScreen').classList.remove('hidden'); - // ๊ณตํ†ต ํ—ค๋”์—์„œ ๊ถŒํ•œ ๊ธฐ๋ฐ˜ ๋ฉ”๋‰ด ์ฒ˜๋ฆฌ๋จ - - // ํ”„๋กœ์ ํŠธ ๋กœ๋“œ + // ๋ฐ์ดํ„ฐ ๋กœ๋“œ await loadProjects(); - loadIssues(); - - // URL ํ•ด์‹œ ์ฒ˜๋ฆฌ handleUrlHash(); + } catch (error) { + console.error('โŒ ๋กœ๊ทธ์ธ ์‹คํŒจ:', error); alert(error.message || '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); } }); - // ๋กœ๊ทธ์•„์›ƒ + // ๋กœ๊ทธ์•„์›ƒ (AuthManager ์‚ฌ์šฉ) function logout() { - AuthAPI.logout(); + console.log('๐Ÿšช AuthManager๋ฅผ ํ†ตํ•œ ๋กœ๊ทธ์•„์›ƒ'); + window.authManager.logout(); } // ๋„ค๋น„๊ฒŒ์ด์…˜์€ ๊ณตํ†ต ํ—ค๋”์—์„œ ์ฒ˜๋ฆฌ๋จ diff --git a/frontend/static/js/core/auth-manager.js b/frontend/static/js/core/auth-manager.js new file mode 100644 index 0000000..2c3c079 --- /dev/null +++ b/frontend/static/js/core/auth-manager.js @@ -0,0 +1,263 @@ +/** + * ์ค‘์•™ํ™”๋œ ์ธ์ฆ ๊ด€๋ฆฌ์ž + * ํŽ˜์ด์ง€ ๊ฐ„ ์ด๋™ ์‹œ ๋ถˆํ•„์š”ํ•œ API ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ธ์ฆ ์ƒํƒœ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌ + */ +class AuthManager { + constructor() { + this.currentUser = null; + this.isAuthenticated = false; + this.lastAuthCheck = null; + this.authCheckInterval = 5 * 60 * 1000; // 5๋ถ„๋งˆ๋‹ค ํ† ํฐ ์œ ํšจ์„ฑ ์ฒดํฌ + this.listeners = new Set(); + + // ์ดˆ๊ธฐํ™” + this.init(); + } + + /** + * ์ดˆ๊ธฐํ™” + */ + init() { + console.log('๐Ÿ” AuthManager ์ดˆ๊ธฐํ™”'); + + // localStorage์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ณต์› + this.restoreUserFromStorage(); + + // ํ† ํฐ ๋งŒ๋ฃŒ ์ฒดํฌ ํƒ€์ด๋จธ ์„ค์ • + this.setupTokenExpiryCheck(); + + // ํŽ˜์ด์ง€ ๊ฐ€์‹œ์„ฑ ๋ณ€๊ฒฝ ์‹œ ํ† ํฐ ์ฒดํฌ + document.addEventListener('visibilitychange', () => { + if (!document.hidden && this.shouldCheckAuth()) { + this.refreshAuth(); + } + }); + } + + /** + * localStorage์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ณต์› + */ + restoreUserFromStorage() { + const token = localStorage.getItem('access_token'); + const userStr = localStorage.getItem('currentUser'); + + if (token && userStr) { + try { + this.currentUser = JSON.parse(userStr); + this.isAuthenticated = true; + this.lastAuthCheck = Date.now(); + console.log('โœ… ์ €์žฅ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ณต์›:', this.currentUser.username); + } catch (error) { + console.error('โŒ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ณต์› ์‹คํŒจ:', error); + this.clearAuth(); + } + } + } + + /** + * ์ธ์ฆ์ด ํ•„์š”ํ•œ์ง€ ํ™•์ธ + */ + shouldCheckAuth() { + if (!this.isAuthenticated) return true; + if (!this.lastAuthCheck) return true; + + const timeSinceLastCheck = Date.now() - this.lastAuthCheck; + return timeSinceLastCheck > this.authCheckInterval; + } + + /** + * ์ธ์ฆ ์ƒํƒœ ํ™•์ธ (ํ•„์š”์‹œ์—๋งŒ API ํ˜ธ์ถœ) + */ + async checkAuth() { + console.log('๐Ÿ” ์ธ์ฆ ์ƒํƒœ ํ™•์ธ ์‹œ์ž‘'); + + const token = localStorage.getItem('access_token'); + if (!token) { + console.log('โŒ ํ† ํฐ ์—†์Œ'); + this.clearAuth(); + return null; + } + + // ์ตœ๊ทผ์— ์ฒดํฌํ–ˆ์œผ๋ฉด ์บ์‹œ๋œ ์ •๋ณด ์‚ฌ์šฉ + if (this.isAuthenticated && !this.shouldCheckAuth()) { + console.log('โœ… ์บ์‹œ๋œ ์ธ์ฆ ์ •๋ณด ์‚ฌ์šฉ:', this.currentUser.username); + return this.currentUser; + } + + // API ํ˜ธ์ถœ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ + return await this.refreshAuth(); + } + + /** + * ๊ฐ•์ œ๋กœ ์ธ์ฆ ์ •๋ณด ์ƒˆ๋กœ๊ณ ์นจ (API ํ˜ธ์ถœ) + */ + async refreshAuth() { + console.log('๐Ÿ”„ ์ธ์ฆ ์ •๋ณด ์ƒˆ๋กœ๊ณ ์นจ (API ํ˜ธ์ถœ)'); + + try { + // API๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ + await this.waitForAPI(); + + const user = await AuthAPI.getCurrentUser(); + + this.currentUser = user; + this.isAuthenticated = true; + this.lastAuthCheck = Date.now(); + + // localStorage ์—…๋ฐ์ดํŠธ + localStorage.setItem('currentUser', JSON.stringify(user)); + + console.log('โœ… ์ธ์ฆ ์ •๋ณด ์ƒˆ๋กœ๊ณ ์นจ ์™„๋ฃŒ:', user.username); + + // ๋ฆฌ์Šค๋„ˆ๋“ค์—๊ฒŒ ์•Œ๋ฆผ + this.notifyListeners('auth-success', user); + + return user; + + } catch (error) { + console.error('โŒ ์ธ์ฆ ์‹คํŒจ:', error); + this.clearAuth(); + this.notifyListeners('auth-failed', error); + throw error; + } + } + + /** + * API ๋กœ๋“œ ๋Œ€๊ธฐ + */ + async waitForAPI() { + let attempts = 0; + const maxAttempts = 50; + + while (typeof AuthAPI === 'undefined' && attempts < maxAttempts) { + await new Promise(resolve => setTimeout(resolve, 100)); + attempts++; + } + + if (typeof AuthAPI === 'undefined') { + throw new Error('AuthAPI๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค'); + } + } + + /** + * ์ธ์ฆ ์ •๋ณด ํด๋ฆฌ์–ด + */ + clearAuth() { + console.log('๐Ÿงน ์ธ์ฆ ์ •๋ณด ํด๋ฆฌ์–ด'); + + this.currentUser = null; + this.isAuthenticated = false; + this.lastAuthCheck = null; + + localStorage.removeItem('access_token'); + localStorage.removeItem('currentUser'); + + this.notifyListeners('auth-cleared'); + } + + /** + * ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ + */ + async login(username, password) { + console.log('๐Ÿ”‘ ๋กœ๊ทธ์ธ ์‹œ๋„:', username); + + try { + await this.waitForAPI(); + const data = await AuthAPI.login(username, password); + + this.currentUser = data.user; + this.isAuthenticated = true; + this.lastAuthCheck = Date.now(); + + // localStorage ์ €์žฅ + localStorage.setItem('access_token', data.access_token); + localStorage.setItem('currentUser', JSON.stringify(data.user)); + + console.log('โœ… ๋กœ๊ทธ์ธ ์„ฑ๊ณต:', data.user.username); + + this.notifyListeners('login-success', data.user); + + return data; + + } catch (error) { + console.error('โŒ ๋กœ๊ทธ์ธ ์‹คํŒจ:', error); + this.clearAuth(); + throw error; + } + } + + /** + * ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ + */ + logout() { + console.log('๐Ÿšช ๋กœ๊ทธ์•„์›ƒ'); + + this.clearAuth(); + this.notifyListeners('logout'); + + // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ + window.location.href = '/index.html'; + } + + /** + * ํ† ํฐ ๋งŒ๋ฃŒ ์ฒดํฌ ํƒ€์ด๋จธ ์„ค์ • + */ + setupTokenExpiryCheck() { + // 30๋ถ„๋งˆ๋‹ค ํ† ํฐ ์œ ํšจ์„ฑ ์ฒดํฌ + setInterval(() => { + if (this.isAuthenticated) { + console.log('โฐ ์ •๊ธฐ ํ† ํฐ ์œ ํšจ์„ฑ ์ฒดํฌ'); + this.refreshAuth().catch(() => { + console.log('๐Ÿ”„ ํ† ํฐ ๋งŒ๋ฃŒ - ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ'); + this.logout(); + }); + } + }, 30 * 60 * 1000); + } + + /** + * ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก + */ + addEventListener(callback) { + this.listeners.add(callback); + } + + /** + * ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ + */ + removeEventListener(callback) { + this.listeners.delete(callback); + } + + /** + * ๋ฆฌ์Šค๋„ˆ๋“ค์—๊ฒŒ ์•Œ๋ฆผ + */ + notifyListeners(event, data = null) { + this.listeners.forEach(callback => { + try { + callback(event, data); + } catch (error) { + console.error('๋ฆฌ์Šค๋„ˆ ์ฝœ๋ฐฑ ์˜ค๋ฅ˜:', error); + } + }); + } + + /** + * ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ˜ํ™˜ + */ + getCurrentUser() { + return this.currentUser; + } + + /** + * ์ธ์ฆ ์ƒํƒœ ๋ฐ˜ํ™˜ + */ + isLoggedIn() { + return this.isAuthenticated && !!this.currentUser; + } +} + +// ์ „์—ญ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ +window.authManager = new AuthManager(); + +console.log('๐ŸŽฏ AuthManager ๋กœ๋“œ ์™„๋ฃŒ');