/** * 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 if (csrfToken) { headers['X-CSRF-Token'] = csrfToken; } const response = await fetch('/api/session/heartbeat', { method: 'POST', headers: headers, body: JSON.stringify({ timestamp: new Date().toISOString(), page: window.location.pathname }) }); 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 = ` `; // 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;