import { createStore } from 'vuex'; import dialogs from './modules/dialogs'; import loadMenu from '../utils/menuLoader.js'; import router from '../router'; import apiClient from '../utils/axios.js'; import { io } from 'socket.io-client'; const store = createStore({ state: { isLoggedIn: localStorage.getItem('isLoggedIn') === 'true', connectionStatus: 'disconnected', // 'connected', 'connecting', 'disconnected', 'error' daemonConnectionStatus: 'disconnected', // 'connected', 'connecting', 'disconnected', 'error' user: JSON.parse(localStorage.getItem('user')) || null, // Reconnect state management backendRetryCount: 0, daemonRetryCount: 0, backendRetryTimer: null, daemonRetryTimer: null, backendConnecting: false, daemonConnecting: false, language: (() => { // Verwende die gleiche Logik wie in main.js const browserLanguage = navigator.language || navigator.languages[0]; const germanSpeakingCountries = ['de', 'at', 'ch', 'li']; if (browserLanguage.startsWith('de')) { return 'de'; } const allLanguages = navigator.languages || [navigator.language]; for (const lang of allLanguages) { if (lang.startsWith('de-')) { const countryCode = lang.split('-')[1]?.toLowerCase(); if (germanSpeakingCountries.includes(countryCode)) { return 'de'; } } if (lang.startsWith('de_')) { const countryCode = lang.split('_')[1]?.toLowerCase(); if (germanSpeakingCountries.includes(countryCode)) { return 'de'; } } } return 'en'; })(), menu: JSON.parse(localStorage.getItem('menu')) || [], socket: null, daemonSocket: null, menuNeedsUpdate: false, }, mutations: { async dologin(state, user) { state.isLoggedIn = true; state.user = user; localStorage.setItem('isLoggedIn', 'true'); localStorage.setItem('user', JSON.stringify(user)); state.menuNeedsUpdate = true; if (user.param.filter(param => ['birthdate', 'gender'].includes(param.name)).length < 2) { router.push({ path: '/settings/personal' }); } }, async dologout(state) { state.isLoggedIn = false; state.user = null; localStorage.removeItem('isLoggedIn'); localStorage.removeItem('user'); localStorage.removeItem('menu'); state.menuNeedsUpdate = false; // Setze die Sprache auf die Browser-Sprache zurück const browserLanguage = navigator.language || navigator.languages[0]; const germanSpeakingCountries = ['de', 'at', 'ch', 'li']; if (browserLanguage.startsWith('de')) { state.language = 'de'; } else { const allLanguages = navigator.languages || [navigator.language]; let isGerman = false; for (const lang of allLanguages) { if (lang.startsWith('de-')) { const countryCode = lang.split('-')[1]?.toLowerCase(); if (germanSpeakingCountries.includes(countryCode)) { isGerman = true; break; } } if (lang.startsWith('de_')) { const countryCode = lang.split('_')[1]?.toLowerCase(); if (germanSpeakingCountries.includes(countryCode)) { isGerman = true; break; } } } state.language = isGerman ? 'de' : 'en'; } }, setLanguage(state, language) { state.language = language; }, setMenu(state, menu) { state.menu = menu; localStorage.setItem('menu', JSON.stringify(menu)); state.menuNeedsUpdate = false; }, setSocket(state, socket) { state.socket = socket; }, setConnectionStatus(state, status) { state.connectionStatus = status; }, setDaemonConnectionStatus(state, status) { state.daemonConnectionStatus = status; }, clearSocket(state) { if (state.socket) { state.socket.disconnect(); } state.socket = null; // Cleanup retry timer if (state.backendRetryTimer) { clearTimeout(state.backendRetryTimer); state.backendRetryTimer = null; } state.backendConnecting = false; }, setDaemonSocket(state, daemonSocket) { state.daemonSocket = daemonSocket; }, clearDaemonSocket(state) { if (state.daemonSocket) { state.daemonSocket.close(); } state.daemonSocket = null; state.daemonConnectionStatus = 'disconnected'; // Cleanup retry timer if (state.daemonRetryTimer) { clearTimeout(state.daemonRetryTimer); state.daemonRetryTimer = null; } state.daemonConnecting = false; }, }, actions: { async login({ commit, dispatch }, user) { await commit('dologin', user); await dispatch('initializeSocket'); await dispatch('initializeDaemonSocket'); const socket = this.getters.socket; if (socket) { const idForSocket = user?.hashedId || user?.id; if (idForSocket) socket.emit('setUserId', idForSocket); } await dispatch('loadMenu'); }, logout({ commit }) { commit('clearSocket'); commit('clearDaemonSocket'); commit('dologout'); router.push('/'); }, initializeSocket({ commit, state, dispatch }) { if (!state.isLoggedIn || !state.user || state.backendConnecting) { return; } state.backendConnecting = true; const connectSocket = () => { // Cleanup existing socket and timer if (state.socket) { state.socket.disconnect(); } if (state.backendRetryTimer) { clearTimeout(state.backendRetryTimer); state.backendRetryTimer = null; } commit('setConnectionStatus', 'connecting'); // Socket.io URL für lokale Entwicklung und Produktion let socketIoUrl = import.meta.env.VITE_SOCKET_IO_URL || import.meta.env.VITE_API_BASE_URL; // Für lokale Entwicklung: direkte Backend-Verbindung if (!socketIoUrl && (import.meta.env.DEV || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) { socketIoUrl = 'http://localhost:3001'; } const socket = io(socketIoUrl, { secure: true, transports: ['websocket', 'polling'] }); socket.on('connect', () => { state.backendRetryCount = 0; // Reset retry counter on successful connection state.backendConnecting = false; commit('setConnectionStatus', 'connected'); const idForSocket = state.user?.hashedId || state.user?.id; if (idForSocket) socket.emit('setUserId', idForSocket); }); socket.on('disconnect', (reason) => { commit('setConnectionStatus', 'disconnected'); dispatch('retryBackendConnection'); }); socket.on('connect_error', (error) => { commit('setConnectionStatus', 'error'); dispatch('retryBackendConnection'); }); commit('setSocket', socket); }; connectSocket(); }, retryBackendConnection({ commit, state }) { if (state.backendRetryTimer || state.backendConnecting) { return; // Already retrying or connecting } const maxRetries = 10; console.log(`Backend-Reconnect-Versuch ${state.backendRetryCount + 1}/${maxRetries}`); if (state.backendRetryCount >= maxRetries) { // Nach maxRetries alle 5 Sekunden weiter versuchen console.log('Backend: Max Retries erreicht, versuche weiter alle 5 Sekunden...'); state.backendRetryTimer = setTimeout(() => { state.backendRetryCount = 0; // Reset für nächsten Zyklus state.backendRetryTimer = null; commit('setConnectionStatus', 'connecting'); // Recursive call to retry setTimeout(() => this.dispatch('retryBackendConnection'), 100); }, 5000); return; } state.backendRetryCount++; const delay = 5000; // Alle 5 Sekunden versuchen console.log(`Backend: Warte ${delay}ms bis zum nächsten Reconnect-Versuch...`); state.backendRetryTimer = setTimeout(() => { state.backendRetryTimer = null; this.dispatch('initializeSocket'); }, delay); }, initializeDaemonSocket({ commit, state, dispatch }) { if (!state.isLoggedIn || !state.user || state.daemonConnecting) { return; } state.daemonConnecting = true; // Daemon URL für lokale Entwicklung und Produktion let daemonUrl = import.meta.env.VITE_DAEMON_SOCKET; // Für lokale Entwicklung: direkte Daemon-Verbindung if (!daemonUrl && (import.meta.env.DEV || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) { daemonUrl = 'ws://localhost:4551'; } // Fallback für Produktion if (!daemonUrl) { daemonUrl = 'wss://www.your-part.de:4551'; } const connectDaemonSocket = () => { // Cleanup existing socket and timer if (state.daemonSocket) { state.daemonSocket.close(); } if (state.daemonRetryTimer) { clearTimeout(state.daemonRetryTimer); state.daemonRetryTimer = null; } // Protokoll-Fallback: zuerst mit Subprotokoll, dann ohne const protocols = ['yourpart-protocol', undefined]; let attemptIndex = 0; const tryConnectWithProtocol = () => { const currentProtocol = protocols[attemptIndex]; try { commit('setDaemonConnectionStatus', 'connecting'); const daemonSocket = currentProtocol ? new WebSocket(daemonUrl, currentProtocol) : new WebSocket(daemonUrl); let opened = false; daemonSocket.onopen = () => { opened = true; state.daemonRetryCount = 0; state.daemonConnecting = false; if (state.daemonRetryTimer) { clearTimeout(state.daemonRetryTimer); state.daemonRetryTimer = null; } commit('setDaemonConnectionStatus', 'connected'); const payload = JSON.stringify({ event: 'setUserId', data: { userId: state.user.id } }); daemonSocket.send(payload); }; daemonSocket.onclose = (event) => { state.daemonConnecting = false; commit('setDaemonConnectionStatus', 'disconnected'); // Bereinige Socket-Referenz wenn Verbindung geschlossen wurde if (state.daemonSocket === daemonSocket) { state.daemonSocket = null; } if (!opened && attemptIndex < protocols.length - 1) { attemptIndex += 1; tryConnectWithProtocol(); return; } // Nur reconnen, wenn Benutzer noch eingeloggt ist if (state.isLoggedIn && state.user) { dispatch('retryDaemonConnection'); } }; daemonSocket.onerror = (error) => { state.daemonConnecting = false; commit('setDaemonConnectionStatus', 'error'); // Bereinige Socket-Referenz bei Fehler if (state.daemonSocket === daemonSocket) { state.daemonSocket = null; } if (!opened && attemptIndex < protocols.length - 1) { attemptIndex += 1; tryConnectWithProtocol(); return; } // Nur reconnen, wenn Benutzer noch eingeloggt ist if (state.isLoggedIn && state.user) { dispatch('retryDaemonConnection'); } }; daemonSocket.addEventListener('message', (event) => { const message = event.data; if (message === "ping") { daemonSocket.send("pong"); } else { try { const data = JSON.parse(message); // Handle daemon messages here } catch (error) { // Error parsing daemon message } } }); commit('setDaemonSocket', daemonSocket); } catch (error) { state.daemonConnecting = false; if (attemptIndex < protocols.length - 1) { attemptIndex += 1; tryConnectWithProtocol(); return; } dispatch('retryDaemonConnection'); } }; tryConnectWithProtocol(); }; connectDaemonSocket(); }, retryDaemonConnection({ commit, state, dispatch }) { // Prüfe ob Benutzer noch eingeloggt ist if (!state.isLoggedIn || !state.user) { console.log('[Daemon] Benutzer nicht eingeloggt, keine Wiederherstellung der Verbindung'); return; } // Prüfe ob bereits ein Timer läuft if (state.daemonRetryTimer) { return; } // Maximale Anzahl von Versuchen: 15 const maxRetries = 15; if (state.daemonRetryCount >= maxRetries) { console.warn(`[Daemon] Maximale Anzahl von Reconnect-Versuchen (${maxRetries}) erreicht. Warte länger...`); // Warte länger nach maximalen Versuchen (30 Sekunden) state.daemonRetryCount = 0; // Reset für nächsten Zyklus const delay = 30000; state.daemonRetryTimer = setTimeout(() => { state.daemonRetryTimer = null; commit('setDaemonConnectionStatus', 'connecting'); dispatch('initializeDaemonSocket'); }, delay); return; } state.daemonConnecting = false; state.daemonRetryCount++; const delay = 5000; console.log(`[Daemon] Reconnect-Versuch ${state.daemonRetryCount}/${maxRetries}, nächster Versuch in ${delay}ms...`); state.daemonRetryTimer = setTimeout(() => { state.daemonRetryTimer = null; // Prüfe noch einmal, ob Benutzer noch eingeloggt ist if (state.isLoggedIn && state.user) { commit('setDaemonConnectionStatus', 'connecting'); dispatch('initializeDaemonSocket'); } }, delay); }, setLanguage({ commit }, language) { commit('setLanguage', language); }, async loadMenu({ commit }) { try { const menu = await loadMenu(); commit('setMenu', menu); } catch (err) { commit('setMenu', []); } }, }, getters: { isLoggedIn: state => state.isLoggedIn, user: state => state.user, language: state => state.language, menu: state => state.menu, socket: state => state.socket, daemonSocket: state => state.daemonSocket, menuNeedsUpdate: state => state.menuNeedsUpdate, connectionStatus: state => state.connectionStatus, daemonConnectionStatus: state => state.daemonConnectionStatus, }, modules: { dialogs, }, }); // Initialisierung beim Laden der Anwendung - nur einmal ausführen if (store.state.isLoggedIn && store.state.user && !store.state.backendConnecting && !store.state.daemonConnecting) { store.dispatch('initializeSocket'); store.dispatch('initializeDaemonSocket'); } export default store;