- Clear socket reference on connection close and error - Ensure reconnection attempts only occur if the user is logged in - Improved logging for reconnection attempts and retry count - Added maximum retry limit with extended wait time after reaching it
458 lines
15 KiB
JavaScript
458 lines
15 KiB
JavaScript
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;
|