manage-your-printer/static/js/session-manager.js
2025-06-04 10:03:22 +02:00

490 lines
19 KiB
JavaScript

/**
* Erweitertes Session-Management für MYP Platform
*
* Features:
* - Automatische Session-Überwachung
* - Heartbeat-System für Session-Verlängerung
* - Benutzer-Warnungen bei bevorstehender Abmeldung
* - Graceful Logout bei Session-Ablauf
* - Modal-Dialoge für Session-Verlängerung
*
* @author Mercedes-Benz MYP Platform
* @version 2.0
*/
class SessionManager {
constructor() {
this.isAuthenticated = false;
this.maxInactiveMinutes = 30; // Standard: 30 Minuten
this.heartbeatInterval = 5 * 60 * 1000; // 5 Minuten
this.warningTime = 5 * 60 * 1000; // 5 Minuten vor Ablauf warnen
this.checkInterval = 30 * 1000; // Alle 30 Sekunden prüfen
this.heartbeatTimer = null;
this.statusCheckTimer = null;
this.warningShown = false;
this.sessionWarningModal = null;
this.init();
}
async init() {
try {
// Prüfe initial ob Benutzer angemeldet ist
await this.checkAuthenticationStatus();
if (this.isAuthenticated) {
this.startSessionMonitoring();
this.createWarningModal();
console.log('🔐 Session Manager gestartet');
console.log(`📊 Max Inaktivität: ${this.maxInactiveMinutes} Minuten`);
console.log(`💓 Heartbeat Intervall: ${this.heartbeatInterval / 1000 / 60} Minuten`);
} else {
console.log('👤 Benutzer nicht angemeldet - Session Manager inaktiv');
}
} catch (error) {
console.error('❌ Session Manager Initialisierung fehlgeschlagen:', error);
}
}
async checkAuthenticationStatus() {
try {
const response = await fetch('/api/session/status', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
this.isAuthenticated = true;
this.maxInactiveMinutes = data.session.max_inactive_minutes;
console.log('✅ Session Status:', {
user: data.user.email,
timeLeft: Math.floor(data.session.time_left_seconds / 60) + ' Minuten',
lastActivity: new Date(data.session.last_activity).toLocaleString('de-DE')
});
return data;
}
} else if (response.status === 401) {
this.isAuthenticated = false;
this.handleSessionExpired('Authentication check failed');
}
} catch (error) {
console.error('❌ Fehler beim Prüfen des Session-Status:', error);
this.isAuthenticated = false;
}
return null;
}
startSessionMonitoring() {
// Heartbeat alle 5 Minuten senden
this.heartbeatTimer = setInterval(() => {
this.sendHeartbeat();
}, this.heartbeatInterval);
// Session-Status alle 30 Sekunden prüfen
this.statusCheckTimer = setInterval(() => {
this.checkSessionStatus();
}, this.checkInterval);
// Initial Heartbeat senden
setTimeout(() => this.sendHeartbeat(), 1000);
}
async sendHeartbeat() {
try {
// CSRF-Token aus dem Meta-Tag holen
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
const headers = {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
};
// CSRF-Token hinzufügen wenn verfügbar - Flask-WTF erwartet X-CSRFToken oder den Token im Body
if (csrfToken) {
headers['X-CSRFToken'] = csrfToken;
}
const response = await fetch('/api/session/heartbeat', {
method: 'POST',
headers: headers,
body: JSON.stringify({
timestamp: new Date().toISOString(),
page: window.location.pathname,
csrf_token: csrfToken // Zusätzlich im Body senden
})
});
if (response.ok) {
const data = await response.json();
if (data.success) {
console.log('💓 Heartbeat gesendet - Session aktiv:',
Math.floor(data.time_left_seconds / 60) + ' Minuten verbleibend');
} else {
console.warn('⚠️ Heartbeat fehlgeschlagen:', data);
}
} else if (response.status === 401) {
this.handleSessionExpired('Heartbeat failed - unauthorized');
} else if (response.status === 400) {
console.warn('⚠️ CSRF-Token Problem beim Heartbeat - versuche Seite neu zu laden');
// Bei CSRF-Problemen die Seite neu laden
setTimeout(() => location.reload(), 5000);
}
} catch (error) {
console.error('❌ Heartbeat-Fehler:', error);
}
}
async checkSessionStatus() {
try {
const sessionData = await this.checkAuthenticationStatus();
if (sessionData && sessionData.session) {
const timeLeftSeconds = sessionData.session.time_left_seconds;
const timeLeftMinutes = Math.floor(timeLeftSeconds / 60);
// Warnung anzeigen wenn weniger als 5 Minuten verbleiben
if (timeLeftSeconds <= this.warningTime / 1000 && timeLeftSeconds > 0) {
if (!this.warningShown) {
this.showSessionWarning(timeLeftMinutes);
this.warningShown = true;
}
} else if (timeLeftSeconds <= 0) {
// Session abgelaufen
this.handleSessionExpired('Session time expired');
} else {
// Session OK - Warnung zurücksetzen
this.warningShown = false;
this.hideSessionWarning();
}
// Session-Status in der UI aktualisieren
this.updateSessionStatusDisplay(sessionData);
}
} catch (error) {
console.error('❌ Session-Status-Check fehlgeschlagen:', error);
}
}
showSessionWarning(minutesLeft) {
// Bestehende Warnung entfernen
this.hideSessionWarning();
// Toast-Notification anzeigen
this.showToast(
'Session läuft ab',
`Ihre Session läuft in ${minutesLeft} Minuten ab. Möchten Sie verlängern?`,
'warning',
10000, // 10 Sekunden anzeigen
[
{
text: 'Verlängern',
action: () => this.extendSession()
},
{
text: 'Abmelden',
action: () => this.logout()
}
]
);
// Modal anzeigen für wichtige Warnung
if (this.sessionWarningModal) {
this.sessionWarningModal.show();
this.updateWarningModal(minutesLeft);
}
console.log(`⚠️ Session-Warnung: ${minutesLeft} Minuten verbleibend`);
}
hideSessionWarning() {
if (this.sessionWarningModal) {
this.sessionWarningModal.hide();
}
}
createWarningModal() {
// Modal HTML erstellen
const modalHTML = `
<div id="sessionWarningModal" class="fixed inset-0 z-50 hidden overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L3.314 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
Session läuft ab
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500" id="warningMessage">
Ihre Session läuft in <span id="timeRemaining" class="font-bold text-red-600">5</span> Minuten ab.
</p>
<p class="text-sm text-gray-500 mt-2">
Möchten Sie Ihre Session verlängern oder sich abmelden?
</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button type="button" id="extendSessionBtn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
Session verlängern
</button>
<button type="button" id="logoutBtn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Abmelden
</button>
</div>
</div>
</div>
</div>`;
// Modal in DOM einfügen
document.body.insertAdjacentHTML('beforeend', modalHTML);
// Event-Listener hinzufügen
document.getElementById('extendSessionBtn').addEventListener('click', () => {
this.extendSession();
this.hideSessionWarning();
});
document.getElementById('logoutBtn').addEventListener('click', () => {
this.logout();
});
// Modal-Objekt erstellen
this.sessionWarningModal = {
element: document.getElementById('sessionWarningModal'),
show: () => {
document.getElementById('sessionWarningModal').classList.remove('hidden');
},
hide: () => {
document.getElementById('sessionWarningModal').classList.add('hidden');
}
};
}
updateWarningModal(minutesLeft) {
const timeElement = document.getElementById('timeRemaining');
if (timeElement) {
timeElement.textContent = minutesLeft;
}
}
async extendSession(extendMinutes = 30) {
try {
const response = await fetch('/api/session/extend', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
extend_minutes: extendMinutes
})
});
if (response.ok) {
const data = await response.json();
if (data.success) {
this.warningShown = false;
this.showToast(
'Session verlängert',
`Ihre Session wurde um ${data.extended_minutes} Minuten verlängert`,
'success',
5000
);
console.log('✅ Session verlängert:', data);
} else {
this.showToast('Fehler', 'Session konnte nicht verlängert werden', 'error');
}
} else if (response.status === 401) {
this.handleSessionExpired('Extend session failed - unauthorized');
}
} catch (error) {
console.error('❌ Session-Verlängerung fehlgeschlagen:', error);
this.showToast('Fehler', 'Session-Verlängerung fehlgeschlagen', 'error');
}
}
async logout() {
try {
this.stopSessionMonitoring();
// Logout-Request senden
const response = await fetch('/auth/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
// Zur Login-Seite weiterleiten
if (response.ok) {
window.location.href = '/auth/login';
} else {
// Fallback: Direkter Redirect
window.location.href = '/auth/login';
}
} catch (error) {
console.error('❌ Logout-Fehler:', error);
// Fallback: Direkter Redirect
window.location.href = '/auth/login';
}
}
handleSessionExpired(reason) {
console.log('🕒 Session abgelaufen:', reason);
this.stopSessionMonitoring();
this.isAuthenticated = false;
// Benutzer benachrichtigen
this.showToast(
'Session abgelaufen',
'Sie wurden automatisch abgemeldet. Bitte melden Sie sich erneut an.',
'warning',
8000
);
// Nach kurzer Verzögerung zur Login-Seite weiterleiten
setTimeout(() => {
window.location.href = '/auth/login?reason=session_expired';
}, 2000);
}
stopSessionMonitoring() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
if (this.statusCheckTimer) {
clearInterval(this.statusCheckTimer);
this.statusCheckTimer = null;
}
console.log('🛑 Session-Monitoring gestoppt');
}
updateSessionStatusDisplay(sessionData) {
// Session-Status in der Navigation/Header anzeigen (falls vorhanden)
const statusElement = document.getElementById('sessionStatus');
if (statusElement) {
const timeLeftMinutes = Math.floor(sessionData.session.time_left_seconds / 60);
statusElement.textContent = `Session: ${timeLeftMinutes}min`;
// Farbe basierend auf verbleibender Zeit
if (timeLeftMinutes <= 5) {
statusElement.className = 'text-red-600 font-medium';
} else if (timeLeftMinutes <= 10) {
statusElement.className = 'text-yellow-600 font-medium';
} else {
statusElement.className = 'text-green-600 font-medium';
}
}
}
showToast(title, message, type = 'info', duration = 5000, actions = []) {
// Verwende das bestehende Toast-System falls verfügbar
if (window.showToast) {
window.showToast(message, type, duration);
return;
}
// Fallback: Simple Browser-Notification
if (type === 'error' || type === 'warning') {
alert(`${title}: ${message}`);
} else {
console.log(`${title}: ${message}`);
}
}
// === ÖFFENTLICHE API ===
/**
* Prüft ob Benutzer angemeldet ist
*/
isLoggedIn() {
return this.isAuthenticated;
}
/**
* Startet Session-Monitoring manuell
*/
start() {
if (!this.heartbeatTimer && this.isAuthenticated) {
this.startSessionMonitoring();
}
}
/**
* Stoppt Session-Monitoring manuell
*/
stop() {
this.stopSessionMonitoring();
}
/**
* Verlängert Session manuell
*/
async extend(minutes = 30) {
return await this.extendSession(minutes);
}
/**
* Meldet Benutzer manuell ab
*/
async logoutUser() {
return await this.logout();
}
}
// Session Manager automatisch starten wenn DOM geladen ist
document.addEventListener('DOMContentLoaded', () => {
// Nur starten wenn wir nicht auf der Login-Seite sind
if (!window.location.pathname.includes('/auth/login')) {
window.sessionManager = new SessionManager();
// Globale Event-Listener für Session-Management
window.addEventListener('beforeunload', () => {
if (window.sessionManager) {
window.sessionManager.stop();
}
});
// Reaktion auf Sichtbarkeitsänderungen (Tab-Wechsel)
document.addEventListener('visibilitychange', () => {
if (window.sessionManager && window.sessionManager.isLoggedIn()) {
if (document.hidden) {
console.log('🙈 Tab versteckt - Session-Monitoring reduziert');
} else {
console.log('👁️ Tab sichtbar - Session-Check');
// Sofortiger Session-Check wenn Tab wieder sichtbar wird
setTimeout(() => window.sessionManager.checkSessionStatus(), 1000);
}
}
});
}
});
// Session Manager für andere Scripts verfügbar machen
window.SessionManager = SessionManager;