From b260c6ed0ba9499c3bbbec69772940d8c775f846 Mon Sep 17 00:00:00 2001 From: Pierre Marx Date: Thu, 11 Sep 2025 19:55:15 -0400 Subject: [PATCH] =?UTF-8?q?premi=C3=A8re=20version=20avec=20socketio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- connection-manager.js | 117 +++++++++++++++++++++++++++++ main.js | 89 ++++++++++------------ package.json | 5 +- websocket-adapter.js | 168 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 325 insertions(+), 54 deletions(-) create mode 100644 connection-manager.js create mode 100644 websocket-adapter.js diff --git a/connection-manager.js b/connection-manager.js new file mode 100644 index 0000000..5a7c66f --- /dev/null +++ b/connection-manager.js @@ -0,0 +1,117 @@ +/** + * Gestionnaire de connexion avec fallback SignalR → WebSocket + * Essaie d'abord SignalR, puis bascule sur WebSocket si nécessaire + */ + +const signalR = require('@microsoft/signalr'); +const WebSocketAdapter = require('./websocket-adapter'); + +class ConnectionManager { + constructor(serverUrl, options = {}) { + this.serverUrl = serverUrl; + this.options = options; + this.connection = null; + this.isUsingWebSocketFallback = false; + this.connectionType = 'none'; + } + + /** + * Établit la connexion en essayant SignalR puis WebSocket + */ + async connect() { + console.log('🔌 Tentative de connexion au serveur...'); + + // 1. Essayer SignalR d'abord + try { + console.log('📡 Essai avec SignalR...'); + this.connection = await this.createSignalRConnection(); + await this.connection.start(); + this.connectionType = 'SignalR'; + this.isUsingWebSocketFallback = false; + console.log('✅ Connexion SignalR établie'); + return this.connection; + } catch (signalRError) { + console.warn('⚠️ SignalR indisponible:', signalRError.message); + console.log('🔄 Basculement vers WebSocket...'); + } + + // 2. Fallback vers WebSocket + try { + this.connection = new WebSocketAdapter(this.serverUrl, this.options); + await this.connection.start(); + this.connectionType = 'WebSocket'; + this.isUsingWebSocketFallback = true; + console.log('✅ Connexion WebSocket établie (mode fallback)'); + return this.connection; + } catch (wsError) { + console.error('❌ Impossible de se connecter (SignalR et WebSocket ont échoué)'); + throw wsError; + } + } + + /** + * Crée une connexion SignalR + */ + createSignalRConnection() { + const url = `http://${this.serverUrl}/signalR`; + console.log(`Creating SignalR connection to: ${url}`); + + return new signalR.HubConnectionBuilder() + .withUrl(url) + .withAutomaticReconnect([0, 2000, 5000, 10000]) + .configureLogging(signalR.LogLevel.Information) + .build(); + } + + /** + * Retourne la connexion active + */ + getConnection() { + return this.connection; + } + + /** + * Déconnecte proprement + */ + async disconnect() { + if (this.connection) { + await this.connection.stop(); + this.connection = null; + this.connectionType = 'none'; + } + } + + /** + * Informations sur la connexion + */ + getConnectionInfo() { + return { + type: this.connectionType, + isWebSocketFallback: this.isUsingWebSocketFallback, + isConnected: this.connection && this.connection.state === 'Connected', + serverUrl: this.serverUrl + }; + } + + /** + * Méthode helper pour invoquer des méthodes sur la connexion + */ + async invoke(methodName, ...args) { + if (!this.connection) { + throw new Error('Pas de connexion active'); + } + return this.connection.invoke(methodName, ...args); + } + + /** + * Méthode helper pour écouter des événements + */ + on(eventName, handler) { + if (!this.connection) { + throw new Error('Pas de connexion active'); + } + this.connection.on(eventName, handler); + } +} + +module.exports = ConnectionManager; \ No newline at end of file diff --git a/main.js b/main.js index b962570..35e97f5 100644 --- a/main.js +++ b/main.js @@ -3,6 +3,7 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); const signalR = require('@microsoft/signalr'); +const ConnectionManager = require('./connection-manager'); let mainWindow; let config; @@ -87,58 +88,25 @@ function createWindow() { }); } -// === GESTION SIGNALR === +// === GESTION SIGNALR/WEBSOCKET === function initializeSignalR() { if (!config.signalR || !config.signalR.enabled) { - console.log('SignalR désactivé dans la configuration'); + console.log('SignalR/WebSocket désactivé dans la configuration'); signalRStatus = 'disabled'; sendSignalRStatus(); return; } try { - // Créer la connexion SignalR - signalRConnection = new signalR.HubConnectionBuilder() - .withUrl(config.signalR.serverUrl) - .withAutomaticReconnect([0, 2000, 5000, 10000, 30000]) - .configureLogging(signalR.LogLevel.Information) - .build(); - - // Gérer les changements d'état - signalRConnection.onreconnecting(() => { - console.log('SignalR: Reconnexion en cours...'); - logSignalR('🔄 SignalR en reconnexion...', { - previousStatus: signalRStatus, - timestamp: new Date().toISOString() - }); - signalRStatus = 'connecting'; - sendSignalRStatus(); - }); - - signalRConnection.onreconnected(() => { - console.log('SignalR: Reconnecté'); - logSignalR('🔗 SignalR reconnecté avec succès', { - connectionId: signalRConnection.connectionId, - timestamp: new Date().toISOString() - }); - signalRStatus = 'connected'; - sendSignalRStatus(); - }); - - signalRConnection.onclose(() => { - console.log('SignalR: Connexion fermée'); - logSignalR('🔌 SignalR déconnecté', { - lastConnectionId: signalRConnection.connectionId, - timestamp: new Date().toISOString() - }); - signalRStatus = 'disconnected'; - sendSignalRStatus(); - }); - - // Configurer les méthodes SignalR - setupSignalRMethods(); + // Utiliser le ConnectionManager avec fallback automatique SignalR → WebSocket + const connectionManager = new ConnectionManager(config.ServerIp || config.signalR.serverUrl.replace('http://', '').replace('/signalR', '')); - // Démarrer la connexion + // La connexion sera établie plus tard avec fallback automatique + signalRConnection = connectionManager; + + // Les handlers d'état seront configurés après la connexion + + // Démarrer la connexion (les handlers seront configurés après) startSignalRConnection(); } catch (error) { @@ -148,7 +116,14 @@ function initializeSignalR() { } } -function setupSignalRMethods() { +function setupSignalRHandlers() { + // Configuration des handlers après la connexion + const connection = signalRConnection.getConnection(); + if (!connection) { + console.error('Pas de connexion active pour configurer les handlers'); + return; + } + // === LOGGER UNIVERSEL POUR TOUS LES MESSAGES SIGNALR === // Intercepter TOUS les messages reçus du serveur pour découvrir les événements disponibles @@ -185,7 +160,7 @@ function setupSignalRMethods() { // Écouter tous les événements possibles et logger ce qu'on reçoit possibleEvents.forEach(eventName => { - signalRConnection.on(eventName, (...args) => { + connection.on(eventName, (...args) => { // Logger dans la console avec formatage console.log('═══════════════════════════════════════════════════════════'); console.log(`📨 MESSAGE SIGNALR REÇU: ${eventName}`); @@ -323,18 +298,28 @@ async function startSignalRConnection() { try { signalRStatus = 'connecting'; sendSignalRStatus(); - logSignalR('🔌 Tentative de connexion SignalR...', { - serverUrl: config.signalR.serverUrl, + logSignalR('🔌 Tentative de connexion au serveur...', { + serverUrl: config.ServerIp || config.signalR.serverUrl, status: 'connecting' }); - await signalRConnection.start(); - console.log('SignalR: Connexion établie'); - logSignalR('✅ Connexion SignalR établie avec succès', { - connectionId: signalRConnection.connectionId, + // Le ConnectionManager gère le fallback automatiquement + const connection = await signalRConnection.connect(); + + // Déterminer quel type de connexion a réussi + const connectionInfo = signalRConnection.getConnectionInfo(); + + console.log(`Connexion établie via ${connectionInfo.type}`); + logSignalR(`✅ Connexion établie avec succès (${connectionInfo.type})`, { + connectionType: connectionInfo.type, + isWebSocketFallback: connectionInfo.isWebSocketFallback, status: 'connected', - serverUrl: config.signalR.serverUrl + serverUrl: connectionInfo.serverUrl }); + + // Maintenant configurer les handlers sur la connexion active + setupSignalRHandlers(); + signalRStatus = 'connected'; sendSignalRStatus(); diff --git a/package.json b/package.json index 57b53b2..8378198 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ }, "dependencies": { "@microsoft/signalr": "^9.0.6", - "choices.js": "^11.1.0" + "choices.js": "^11.1.0", + "socket.io-client": "^4.8.1" } -} \ No newline at end of file +} diff --git a/websocket-adapter.js b/websocket-adapter.js new file mode 100644 index 0000000..fdedc1a --- /dev/null +++ b/websocket-adapter.js @@ -0,0 +1,168 @@ +/** + * Adaptateur WebSocket pour fallback quand SignalR n'est pas disponible + * Émule l'API SignalR avec SocketIO + */ + +const io = require('socket.io-client'); + +class WebSocketAdapter { + constructor(serverUrl, options = {}) { + // Nettoyer l'URL et s'assurer qu'elle est valide + this.serverUrl = serverUrl; + if (this.serverUrl.includes('/signalR')) { + this.serverUrl = this.serverUrl.replace('/signalR', ''); + } + // S'assurer qu'on a le protocole + if (!this.serverUrl.startsWith('http://') && !this.serverUrl.startsWith('https://')) { + this.serverUrl = `http://${this.serverUrl}`; + } + this.socket = null; + this.connected = false; + this.handlers = new Map(); + this.options = options; + this.pendingInvocations = new Map(); + this.invocationId = 0; + } + + /** + * Émule connection.start() de SignalR + */ + async start() { + return new Promise((resolve, reject) => { + console.log(`🔄 Connexion WebSocket à ${this.serverUrl}...`); + + // Se connecter avec SocketIO + this.socket = io(this.serverUrl, { + transports: ['websocket'], + reconnection: true, + reconnectionAttempts: 5, + reconnectionDelay: 2000, + ...this.options + }); + + this.socket.on('connect', () => { + console.log('✅ WebSocket connecté (mode fallback)'); + this.connected = true; + + // Émuler l'événement 'connected' de SignalR + if (this.handlers.has('connected')) { + this.handlers.get('connected')({ connectionId: this.socket.id }); + } + + resolve(); + }); + + this.socket.on('connect_error', (error) => { + console.error('❌ Erreur connexion WebSocket:', error.message); + reject(error); + }); + + this.socket.on('disconnect', (reason) => { + console.log('🔌 WebSocket déconnecté:', reason); + this.connected = false; + }); + + // Timeout de connexion + setTimeout(() => { + if (!this.connected) { + reject(new Error('Timeout de connexion WebSocket')); + } + }, 10000); + }); + } + + /** + * Émule connection.stop() de SignalR + */ + async stop() { + if (this.socket) { + this.socket.disconnect(); + this.connected = false; + console.log('🛑 WebSocket fermé'); + } + } + + /** + * Émule connection.invoke() de SignalR + */ + async invoke(methodName, ...args) { + return new Promise((resolve, reject) => { + if (!this.connected) { + reject(new Error('WebSocket non connecté')); + return; + } + + const invocationId = ++this.invocationId; + console.log(`📤 WebSocket invoke: ${methodName}`, args); + + // Stocker la promesse pour la résolution + this.pendingInvocations.set(invocationId, { resolve, reject }); + + // Émuler le format SignalR mais utiliser l'API SocketIO + // SocketIO utilise emit avec un callback pour les réponses + this.socket.emit(methodName, ...args, (response) => { + console.log(`📥 WebSocket response for ${methodName}:`, response); + + const pending = this.pendingInvocations.get(invocationId); + if (pending) { + pending.resolve(response); + this.pendingInvocations.delete(invocationId); + } + }); + + // Timeout pour éviter les promesses pendantes + setTimeout(() => { + const pending = this.pendingInvocations.get(invocationId); + if (pending) { + pending.reject(new Error(`Timeout invoking ${methodName}`)); + this.pendingInvocations.delete(invocationId); + } + }, 30000); + }); + } + + /** + * Émule connection.on() de SignalR + */ + on(eventName, handler) { + console.log(`👂 WebSocket listener ajouté: ${eventName}`); + this.handlers.set(eventName, handler); + + // Mapper les événements SocketIO vers les handlers + if (this.socket) { + this.socket.on(eventName, (...args) => { + console.log(`📨 WebSocket event reçu: ${eventName}`, args); + handler(eventName, args); + }); + } + } + + /** + * Émule connection.off() de SignalR + */ + off(eventName) { + this.handlers.delete(eventName); + if (this.socket) { + this.socket.off(eventName); + } + } + + /** + * État de la connexion (émule SignalR HubConnectionState) + */ + get state() { + if (this.connected) { + return 'Connected'; + } + return 'Disconnected'; + } + + /** + * Vérifier si la connexion utilise WebSocket (toujours vrai pour cet adaptateur) + */ + get isWebSocketFallback() { + return true; + } +} + +module.exports = WebSocketAdapter; \ No newline at end of file