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; }