247 lines
7.7 KiB
JavaScript
247 lines
7.7 KiB
JavaScript
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 = `
|
|
<div style="background: white; padding: 30px; border-radius: 10px; text-align: center; max-width: 400px; box-shadow: 0 5px 25px rgba(0,0,0,0.3);">
|
|
<h3 style="color: #d35400; margin-bottom: 20px; font-size: 1.5em;">Session About to Expire</h3>
|
|
<p style="font-size: 1.1em; margin-bottom: 20px;">
|
|
Your session will expire in <span class="countdown" style="font-weight: bold; color: #e74c3c;">${timeRemaining}</span> seconds due to inactivity.
|
|
</p>
|
|
<div style="margin-top: 25px;">
|
|
<button onclick="sessionTimeout.continueSession()"
|
|
style="background: #27ae60; color: white; border: none; padding: 12px 24px;
|
|
border-radius: 5px; cursor: pointer; margin-right: 15px; font-size: 1em;">
|
|
Continue Session
|
|
</button>
|
|
<button onclick="sessionTimeout.logout()"
|
|
style="background: #e74c3c; color: white; border: none; padding: 12px 24px;
|
|
border-radius: 5px; cursor: pointer; font-size: 1em;">
|
|
Logout Now
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
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; |