// Configuration const CONFIG = { timeoutMinutes: 120, // Total session timeout checkIntervalSeconds: 60, // How often to check (every minute) warningMinutes: 5, // Warn THIS many minutes before timeout logoutUrl: '/Home/Index', storageKey: 'lastActivity' }; // Global variables for countdown (needed outside IIFE for modal functions) let countdownInterval = null; let remainingSeconds = 0; let totalSeconds = 0; /** * Performs logout by clearing session and redirecting */ function performLogout() { try { // Clear session storage sessionStorage.clear(); // Optional: Clear local storage if needed localStorage.clear(); // Redirect to logout page window.location.href = CONFIG.logoutUrl; } catch (e) { console.error('Logout failed:', e); // Force redirect even if storage clear fails window.location.href = CONFIG.logoutUrl; } } /** * Shows the session timeout warning modal * @param {number} remainingMinutes - Minutes until session expires */ function showTimeoutWarning(remainingMinutes) { const overlay = document.getElementById('sessionModalOverlay'); const countdownElement = document.getElementById('countdownTime'); const progressBar = document.getElementById('sessionProgressBar'); if (!overlay) { console.error('Session modal overlay not found in DOM'); return; } // Calculate total seconds totalSeconds = remainingMinutes * 60; remainingSeconds = totalSeconds; // Show modal overlay.classList.add('active'); // Initialize progress bar if (progressBar) { progressBar.style.width = '100%'; } // Clear any existing interval if (countdownInterval) { clearInterval(countdownInterval); } // Start countdown updateCountdown(); countdownInterval = setInterval(updateCountdown, 1000); console.log(`Session warning shown: ${remainingMinutes} minutes remaining`); } /** * Updates the countdown timer and progress bar */ function updateCountdown() { const countdownElement = document.getElementById('countdownTime'); const progressBar = document.getElementById('sessionProgressBar'); if (remainingSeconds <= 0) { clearInterval(countdownInterval); handleLogout(); return; } // Calculate minutes and seconds const minutes = Math.floor(remainingSeconds / 60); const seconds = remainingSeconds % 60; // Format time display const timeString = `${minutes}:${seconds.toString().padStart(2, '0')}`; if (countdownElement) { countdownElement.textContent = timeString; // Update countdown color based on time remaining countdownElement.classList.remove('warning', 'critical'); if (remainingSeconds <= 60) { countdownElement.classList.add('critical'); } else if (remainingSeconds <= 180) { countdownElement.classList.add('warning'); } } // Update progress bar if (progressBar) { const progress = (remainingSeconds / totalSeconds) * 100; progressBar.style.width = progress + '%'; } remainingSeconds--; } /** * Handles the "Stay Logged In" action */ function handleStayLoggedIn() { const overlay = document.getElementById('sessionModalOverlay'); // Clear countdown if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } // Hide modal if (overlay) { overlay.classList.remove('active'); } // Reset activity timestamp if (typeof updateLastActivity === 'function') { updateLastActivity(); } console.log('User chose to stay logged in - session extended'); } /** * Handles the logout action */ function handleLogout() { // Clear countdown if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } console.log('Session timeout - logging out...'); // Perform logout performLogout(); } // Session Timeout Manager (IIFE) (function () { 'use strict'; const sessionTimeout = CONFIG.timeoutMinutes * 60 * 1000; const warningThreshold = CONFIG.warningMinutes * 60 * 1000; let warningShown = false; let checkInterval = null; /** * Updates the last activity timestamp */ window.updateLastActivity = function () { try { const timestamp = Date.now(); sessionStorage.setItem(CONFIG.storageKey, timestamp.toString()); console.log('Activity updated:', new Date(timestamp).toLocaleTimeString()); warningShown = false; // Reset warning flag on activity } catch (e) { console.error('Failed to update last activity:', e); } }; /** * Gets the last activity timestamp * @returns {number|null} Timestamp or null if not found */ function getLastActivity() { try { const lastActivity = sessionStorage.getItem(CONFIG.storageKey); return lastActivity ? parseInt(lastActivity, 10) : null; } catch (e) { console.error('Failed to get last activity:', e); return null; } } /** * Checks if session has timed out */ function checkSessionTimeout() { const lastActivity = getLastActivity(); const now = Date.now(); // If no activity recorded, initialize it if (!lastActivity) { window.updateLastActivity(); return; } const inactiveTime = now - lastActivity; const inactiveMinutes = Math.floor(inactiveTime / 60000); console.log(`Session check - Inactive for: ${inactiveMinutes} minutes`); // Check if session has expired if (inactiveTime >= sessionTimeout) { console.log('Session expired - logging out'); performLogout(); return; } // Calculate remaining time until timeout const remainingTime = sessionTimeout - inactiveTime; const remainingMinutes = Math.ceil(remainingTime / 60000); // Show warning if within warning threshold and not already shown if (!warningShown && remainingTime <= warningThreshold) { warningShown = true; console.log(`Showing warning - ${remainingMinutes} minutes remaining`); showTimeoutWarning(remainingMinutes); } } /** * Initializes the session timeout manager */ function initialize() { console.log('Session timeout initialized:', { timeout: CONFIG.timeoutMinutes + ' minutes', warning: CONFIG.warningMinutes + ' minutes before timeout', checkInterval: CONFIG.checkIntervalSeconds + ' seconds' }); // Validate configuration if (CONFIG.warningMinutes >= CONFIG.timeoutMinutes) { console.warn('Warning: warningMinutes should be less than timeoutMinutes'); } // Set initial activity timestamp if not present if (!getLastActivity()) { window.updateLastActivity(); } // Listen for user activity events const activityEvents = ['mousedown', 'keypress', 'scroll', 'touchstart', 'click']; // Use throttling to avoid excessive storage writes let throttleTimer = null; const throttledUpdate = function () { if (!throttleTimer) { throttleTimer = setTimeout(function () { window.updateLastActivity(); throttleTimer = null; }, 1000); // Update at most once per second } }; activityEvents.forEach(function (event) { document.addEventListener(event, throttledUpdate, { passive: true }); }); // Start checking for timeout checkInterval = setInterval(checkSessionTimeout, CONFIG.checkIntervalSeconds * 1000); // Also check on page visibility change (when user returns to tab) document.addEventListener('visibilitychange', function () { if (!document.hidden) { console.log('Tab became visible - checking session'); checkSessionTimeout(); } }); // Check immediately on load checkSessionTimeout(); } /** * Cleanup function (if needed for SPA navigation) */ function cleanup() { if (checkInterval) { clearInterval(checkInterval); checkInterval = null; } console.log('Session timeout manager cleaned up'); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize); } else { initialize(); } // Expose cleanup for SPA frameworks if needed window.sessionTimeoutCleanup = cleanup; })(); // Setup modal close handler when DOM is ready document.addEventListener('DOMContentLoaded', function () { const overlay = document.getElementById('sessionModalOverlay'); if (overlay) { overlay.addEventListener('click', function (e) { if (e.target === this) { handleStayLoggedIn(); } }); } });