From 22bb66b68088e6363edd1efeab3ac85673ccbd98 Mon Sep 17 00:00:00 2001 From: Benoit Date: Tue, 30 Jun 2026 14:11:29 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20corriger=20la=20d=C3=A9tection=20de=20st?= =?UTF-8?q?atut=20serveur=20et=20l'URL/QR=20code=20de=20connexion=20client?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Statut serveur : - SERVER_URL utilisait "localhost", que le Node embarqué par Electron peut résoudre en IPv6 (::1) en priorité ; le serveur n'écoutant qu'en IPv4 (host: 0.0.0.0), le ping de statut échouait silencieusement alors que le serveur tournait. Bascule sur 127.0.0.1 (main.js + preload.js). - L'erreur réelle de pingServer() n'était jamais remontée au renderer (health.error perdu) ; elle est maintenant incluse dans la réponse IPC. URL/QR code clients : - L'URL affichée utilisait un remplacement de chaîne ("localhost" -> IP) qui ne matchait plus rien depuis le passage à 127.0.0.1 ; remplacé par un parsing d'URL qui ne réutilise que le protocole/port. - Le QR code dépendait d'une lib chargée depuis un CDN externe, inadapté à une app self-hosted censée fonctionner sans accès Internet sur le WiFi d'un événement. Généré désormais côté Main Process avec la lib qrcode (déjà en dépendance, jamais utilisée) et transmis au renderer en data URL ; suppression du fichier placeholder et de la dépendance CDN. - getNetworkIP() lisait /admin/config, qui renvoie la valeur YAML brute "AUTO" (jamais résolue), donc retombait toujours sur "localhost". Remplacé par la détection réseau du Main Process (même logique que pour les certificats mkcert). - Ajout d'un placeholder visuel (icône + message) tant qu'aucun QR code n'est généré ou que le serveur est arrêté, en CSS pur. --- electron/main.js | 20 ++++++++++++++- electron/preload.js | 9 ++++++- electron/ui/app.js | 53 +++++++++++++++++++-------------------- electron/ui/index.html | 11 +++++--- electron/ui/qrcode.min.js | 2 -- electron/ui/styles.css | 40 +++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 34 deletions(-) delete mode 100644 electron/ui/qrcode.min.js diff --git a/electron/main.js b/electron/main.js index 4885f8b..918a261 100644 --- a/electron/main.js +++ b/electron/main.js @@ -8,6 +8,7 @@ const path = require('path'); const { spawn } = require('child_process'); const http = require('http'); const https = require('https'); +const QRCode = require('qrcode'); const setupHelper = require('./setup-helper'); // État de l'application @@ -22,7 +23,10 @@ const SERVER_PORT = process.env.PORT || 3000; // lancement) ; ENABLE_HTTPS=false permet de revenir explicitement en HTTP const ENABLE_HTTPS = process.env.ENABLE_HTTPS !== 'false'; const SERVER_PROTOCOL = ENABLE_HTTPS ? 'https' : 'http'; -const SERVER_URL = `${SERVER_PROTOCOL}://localhost:${SERVER_PORT}`; +// 127.0.0.1 plutôt que localhost : le serveur n'écoute qu'en IPv4 (host: 0.0.0.0 +// dans config.yaml), or le Node embarqué par Electron peut résoudre "localhost" +// en IPv6 (::1) en priorité, ce qui ferait échouer silencieusement le ping +const SERVER_URL = `${SERVER_PROTOCOL}://127.0.0.1:${SERVER_PORT}`; const isDev = process.argv.includes('--dev'); /** @@ -339,6 +343,7 @@ app.whenReady().then(async () => { return { running: health.success, health: health.data, + error: health.error, url: SERVER_URL }; }); @@ -347,6 +352,19 @@ app.whenReady().then(async () => { return await pingServer(); }); + ipcMain.handle('qrcode:generate', async (event, text) => { + try { + const dataUrl = await QRCode.toDataURL(text, { width: 256, margin: 2 }); + return { success: true, dataUrl }; + } catch (error) { + return { success: false, error: error.message }; + } + }); + + ipcMain.handle('network:ip', async () => { + return setupHelper.getNetworkIP(); + }); + // Créer fenêtre createWindow(); createTray(); diff --git a/electron/preload.js b/electron/preload.js index dc6f5a9..da83344 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -6,9 +6,10 @@ const { contextBridge, ipcRenderer } = require('electron'); // Même logique que dans main.js : doit rester synchronisé avec SERVER_URL +// (127.0.0.1 : le serveur n'écoute qu'en IPv4, voir le commentaire dans main.js) const SERVER_PORT = process.env.PORT || 3000; const ENABLE_HTTPS = process.env.ENABLE_HTTPS !== 'false'; -const SERVER_URL = `${ENABLE_HTTPS ? 'https' : 'http'}://localhost:${SERVER_PORT}`; +const SERVER_URL = `${ENABLE_HTTPS ? 'https' : 'http'}://127.0.0.1:${SERVER_PORT}`; // Exposer l'API au renderer de manière sécurisée contextBridge.exposeInMainWorld('electronAPI', { @@ -30,6 +31,12 @@ contextBridge.exposeInMainWorld('electronAPI', { } }, + // QR Code (généré côté Main Process, pas de dépendance CDN) + generateQRCode: (text) => ipcRenderer.invoke('qrcode:generate', text), + + // IP réseau locale (même détection que pour les certificats mkcert) + getNetworkIP: () => ipcRenderer.invoke('network:ip'), + // Helpers platform: process.platform, version: process.env.npm_package_version || '0.3.0' diff --git a/electron/ui/app.js b/electron/ui/app.js index e1b71c2..c7048ff 100644 --- a/electron/ui/app.js +++ b/electron/ui/app.js @@ -194,6 +194,10 @@ function updateServerStatus(running) { // Déconnecter WebSocket audio levels disconnectAudioLevelsWS(); + + // QR code obsolète tant que le serveur est arrêté : revenir au placeholder + document.getElementById('qr-code').removeAttribute('src'); + document.getElementById('client-url').textContent = '--'; } } @@ -362,31 +366,29 @@ async function generateQRCode() { // Détecter l'IP réseau (depuis hostname ou config) const networkIP = await getNetworkIP(); - const clientUrl = `https://${networkIP}:5173`; // Mode dev Vite + // En prod (Electron), le client buildé est servi par le serveur Express + // lui-même (même port que l'API), pas par Vite (port 5173, dev only) + // API_BASE pointe sur 127.0.0.1 (loopback, pour le ping interne) : + // on ne réutilise que protocole + port, l'IP doit être celle du réseau local + const serverOrigin = new URL(API_BASE); + const clientUrl = `${serverOrigin.protocol}//${networkIP}:${serverOrigin.port}`; document.getElementById('client-url').textContent = clientUrl; - // Générer QR Code - const canvas = document.getElementById('qr-code'); - if (canvas && window.QRCode) { - QRCode.toCanvas(canvas, clientUrl, { - width: 256, - margin: 2, - color: { - dark: '#000000', - light: '#ffffff' - } - }, (error) => { - if (error) { - console.error('Erreur génération QR Code:', error); - } else { - console.log('✅ QR Code généré'); - } - }); + // Générer QR Code (rendu côté Main Process, pas de dépendance réseau/CDN) + const img = document.getElementById('qr-code'); + if (img) { + const result = await window.electronAPI.generateQRCode(clientUrl); + if (result.success) { + img.src = result.dataUrl; + console.log('✅ QR Code généré'); + } else { + console.error('Erreur génération QR Code:', result.error); + } } } catch (error) { console.error('Erreur récupération URL:', error); - document.getElementById('client-url').textContent = 'https://localhost:5173'; + document.getElementById('client-url').textContent = API_BASE; } // Bouton copier URL (setup une seule fois) @@ -402,15 +404,12 @@ async function generateQRCode() { } async function getNetworkIP() { - // Méthode 1 : depuis l'API serveur (qui détecte déjà l'IP) + // Détection via le Main Process (même logique que pour les certs mkcert) : + // /admin/config renvoie la valeur YAML brute ("AUTO"), jamais l'IP résolue, + // donc inutilisable ici. try { - const config = await apiCall('/admin/config'); - if (config && config.server && config.server.livekit && config.server.livekit.url) { - const url = config.server.livekit.url; - // Extraire l'IP depuis ws://IP:7880 - const match = url.match(/ws:\/\/([^:]+):/); - if (match) return match[1]; - } + const ip = await window.electronAPI.getNetworkIP(); + if (ip) return ip; } catch (error) { console.error('Erreur détection IP:', error); } diff --git a/electron/ui/index.html b/electron/ui/index.html index c343855..ee85cea 100644 --- a/electron/ui/index.html +++ b/electron/ui/index.html @@ -78,7 +78,13 @@

📱 Connexion rapide clients

- +
+ QR Code connexion +
+ 📷 + En attente du démarrage du serveur +
+

URL clients :

--

@@ -180,8 +186,7 @@
- - + diff --git a/electron/ui/qrcode.min.js b/electron/ui/qrcode.min.js deleted file mode 100644 index 8c4c1ec..0000000 --- a/electron/ui/qrcode.min.js +++ /dev/null @@ -1,2 +0,0 @@ -// Placeholder - QR Code sera généré via CDN -// En production, utiliser une lib locale ou CDN diff --git a/electron/ui/styles.css b/electron/ui/styles.css index 86d9242..1763615 100644 --- a/electron/ui/styles.css +++ b/electron/ui/styles.css @@ -230,11 +230,51 @@ body { align-items: center; } +.qr-wrapper { + position: relative; + width: 256px; + height: 256px; + flex-shrink: 0; +} + #qr-code { + display: none; + width: 256px; + height: 256px; border: 4px solid white; border-radius: 8px; } +/* L'image n'a un attribut src qu'une fois le QR code généré */ +#qr-code[src] { + display: block; +} + +#qr-code[src] ~ .qr-placeholder { + display: none; +} + +.qr-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; + height: 100%; + border: 2px dashed var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + text-align: center; + padding: 1rem; + box-sizing: border-box; +} + +.qr-placeholder-icon { + font-size: 2.5rem; + opacity: 0.5; +} + .qr-info { flex: 1; }