class SessionTimeoutManager { constructor() { this.config = { timeout: 1800, // 30 minutes warning_time: 300, // 5 minutes 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; this.csrfToken = this.getCsrfToken(); } getCsrfToken() { // Try to get CSRF token from meta tag const metaTag = document.querySelector('meta[name="csrf-token"]'); if (metaTag) { return metaTag.getAttribute('content'); } // Try to get CSRF token from form input (fallback) const csrfInput = document.querySelector('input[name="' + csrf_token + '"]'); if (csrfInput) { return csrfInput.value; } console.warn('CSRF token not found'); return ''; } async init() { this.setupEventListeners(); this.startPeriodicChecks(); console.log('Session timeout manager initialized with config:', this.config); } // REMOVE the fetchConfig() method entirely 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', 'X-CSRF-TOKEN': this.csrfToken }, 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', 'X-CSRF-TOKEN': this.csrfToken }, credentials: 'same-origin' }); if (response.status === 404) { // If check endpoint doesn't exist, skip checking console.log('Session check endpoint not available, skipping'); return; } 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); } } // ... rest of the methods remain the same handleSessionExpired(data) { this.clearWarning(); clearInterval(this.timers.checkTimer); if (data.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 = `
Your session will expire in ${timeRemaining} seconds due to inactivity.