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, 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; }, setDaemonSocket(state, daemonSocket) { state.daemonSocket = daemonSocket; }, clearDaemonSocket(state) { if (state.daemonSocket) { state.daemonSocket.close(); } state.daemonSocket = null; state.daemonConnectionStatus = 'disconnected'; }, }, 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 }) { if (state.isLoggedIn && state.user) { let currentSocket = state.socket; const connectSocket = () => { if (currentSocket) { currentSocket.disconnect(); } 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', () => { retryCount = 0; // Reset retry counter on successful connection commit('setConnectionStatus', 'connected'); const idForSocket = state.user?.hashedId || state.user?.id; if (idForSocket) socket.emit('setUserId', idForSocket); }); socket.on('disconnect', (reason) => { commit('setConnectionStatus', 'disconnected'); retryConnection(connectSocket); }); socket.on('connect_error', (error) => { commit('setConnectionStatus', 'error'); }); commit('setSocket', socket); }; let retryCount = 0; const maxRetries = 10; const retryConnection = (reconnectFn) => { console.log(`Reconnect-Versuch ${retryCount + 1}/${maxRetries}`); if (retryCount >= maxRetries) { // Nach maxRetries alle 5 Sekunden weiter versuchen console.log('Max Retries erreicht, versuche weiter alle 5 Sekunden...'); setTimeout(() => { reconnectFn(); }, 5000); return; } retryCount++; const delay = 5000; // Alle 5 Sekunden versuchen console.log(`Warte ${delay}ms bis zum nächsten Reconnect-Versuch...`); setTimeout(() => { reconnectFn(); }, delay); }; connectSocket(); } }, initializeDaemonSocket({ commit, state }) { if (!state.isLoggedIn || !state.user) { return; } // 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 = () => { // 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; retryCount = 0; // Reset retry counter on successful connection commit('setDaemonConnectionStatus', 'connected'); const payload = JSON.stringify({ event: 'setUserId', data: { userId: state.user.id } }); daemonSocket.send(payload); }; daemonSocket.onclose = (event) => { commit('setDaemonConnectionStatus', 'disconnected'); // Falls Verbindungsaufbau nicht offen war und es noch einen Fallback gibt → nächsten Versuch ohne Subprotokoll if (!opened && attemptIndex < protocols.length - 1) { attemptIndex += 1; tryConnectWithProtocol(); return; } retryConnection(connectDaemonSocket); }; daemonSocket.onerror = (error) => { commit('setDaemonConnectionStatus', 'error'); // Bei Fehler vor Open: Fallback versuchen if (!opened && attemptIndex < protocols.length - 1) { attemptIndex += 1; tryConnectWithProtocol(); return; } retryConnection(connectDaemonSocket); }; 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) { // Beim Konstruktionsfehler ebenfalls Fallback versuchen if (attemptIndex < protocols.length - 1) { attemptIndex += 1; tryConnectWithProtocol(); return; } retryConnection(connectDaemonSocket); } }; tryConnectWithProtocol(); }; let retryCount = 0; const maxRetries = 15; // Increased max retries const retryConnection = (reconnectFn) => { console.log(`Daemon-Reconnect-Versuch ${retryCount + 1}/${maxRetries}`); if (retryCount >= maxRetries) { // Nach maxRetries alle 5 Sekunden weiter versuchen console.log('Daemon: Max Retries erreicht, versuche weiter alle 5 Sekunden...'); setTimeout(() => { reconnectFn(); }, 5000); return; } retryCount++; const delay = 5000; // Alle 5 Sekunden versuchen console.log(`Daemon: Warte ${delay}ms bis zum nächsten Reconnect-Versuch...`); setTimeout(() => { reconnectFn(); }, delay); }; connectDaemonSocket(); }, 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, }, }); if (store.state.isLoggedIn && store.state.user) { store.dispatch('initializeSocket'); store.dispatch('initializeDaemonSocket'); } export default store;