314 lines
9.1 KiB
JavaScript
314 lines
9.1 KiB
JavaScript
// Configuration
|
|
const CONFIG = {
|
|
timeoutMinutes: 240, // 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();
|
|
}
|
|
});
|
|
}
|
|
}); |