class SessionTimeoutManager { constructor() { this.config = { timeout: 1800, // default 30 min warning_time: 1500, // default 5 min before timeout check_interval: 60000, // 1 minute logout_url: '/auth/logout', keep_alive_url: '/session/ping-activity', check_url: '/session/check-timeout' }; this.timers = { checkTimer: null, logoutTimer: null, warningTimer: null }; this.modal = null; this.warningShown = false; } async init() { try { await this.fetchConfig(); this.setupEventListeners(); this.startPeriodicChecks(); console.log('Session timeout manager initialized with config:', this.config); } catch (error) { console.warn('Session timeout using default config due to error:', error.message); this.setupEventListeners(); this.startPeriodicChecks(); } } async fetchConfig() { try { const response = await fetch('/session/get-timeout-config', { method: 'GET', headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.success) { this.config = { ...this.config, ...data }; } } catch (error) { console.warn('Failed to load timeout config, using defaults:', error.message); } } setupEventListeners() { // Reset timers on user activity const events = ['mousedown', 'keydown', 'scroll', 'touchstart', 'click', 'input']; events.forEach(event => { document.addEventListener(event, () => this.resetActivity(), { passive: true }); }); // Handle visibility change document.addEventListener('visibilitychange', () => { if (!document.hidden) { this.checkSessionStatus(); } }); } async resetActivity() { try { await fetch(this.config.keep_alive_url, { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }); this.clearWarning(); } catch (error) { console.warn('Activity reset failed:', error); } } startPeriodicChecks() { this.timers.checkTimer = setInterval(() => { this.checkSessionStatus(); }, this.config.check_interval); } async checkSessionStatus() { try { const response = await fetch(this.config.check_url, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); switch (data.status) { case 'expired': this.handleSessionExpired(data); break; case 'warning': this.handleSessionWarning(data); break; case 'active': this.handleSessionActive(data); break; } } catch (error) { console.warn('Session status check failed:', error); } } handleSessionExpired(data) { this.clearWarning(); clearInterval(this.timers.checkTimer); if (data.redirect) { // Show message before redirect alert(data.message || 'Your session has expired. Please log in again.'); window.location.href = data.redirect; } else { window.location.href = this.config.logout_url; } } handleSessionWarning(data) { if (!this.warningShown) { this.showWarning(data.time_remaining); } else { this.updateWarning(data.time_remaining); } } handleSessionActive(data) { this.clearWarning(); } showWarning(timeRemaining) { this.warningShown = true; this.createWarningModal(timeRemaining); } updateWarning(timeRemaining) { if (this.modal) { const countdownElement = this.modal.querySelector('.countdown'); if (countdownElement) { countdownElement.textContent = Math.max(0, timeRemaining); } } } createWarningModal(timeRemaining) { this.hideWarning(); this.modal = document.createElement('div'); this.modal.className = 'session-timeout-modal'; this.modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 99999; display: flex; justify-content: center; align-items: center; font-family: Arial, sans-serif; `; this.modal.innerHTML = `

Session About to Expire

Your session will expire in ${timeRemaining} seconds due to inactivity.

`; document.body.appendChild(this.modal); document.body.style.overflow = 'hidden'; } hideWarning() { if (this.modal) { this.modal.remove(); this.modal = null; } this.warningShown = false; document.body.style.overflow = ''; } clearWarning() { this.hideWarning(); } continueSession() { this.resetActivity(); this.hideWarning(); } logout() { window.location.href = this.config.logout_url; } destroy() { clearInterval(this.timers.checkTimer); this.hideWarning(); } } // Initialize globally const sessionTimeout = new SessionTimeoutManager(); // Start when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => sessionTimeout.init()); } else { sessionTimeout.init(); } // Make available globally window.sessionTimeout = sessionTimeout;