NonInventPurchasingSystem/CPRNIMS.WebApps/wwwroot/JsFunctions/Account/sessionTimeoutV5.js

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();
}
});
}
});