diff --git a/main.js b/main.js index 0ef9556..ad03bce 100644 --- a/main.js +++ b/main.js @@ -23,26 +23,60 @@ function ensureLogDirectory() { } } +function stripAnsi(str) { + return str.replace(/\x1b\[[0-9;]*m/g, ''); +} + +function fileTimestamp() { + return new Date().toLocaleString('fr-FR', { + timeZone: 'Europe/Paris', + day: '2-digit', month: '2-digit', year: 'numeric', + hour: '2-digit', minute: '2-digit', second: '2-digit' + }); +} + function logToFile(message, data = null) { ensureLogDirectory(); + let line = `[${fileTimestamp()}] ${stripAnsi(message)}`; + if (data) line += ' ' + JSON.stringify(data); + fs.appendFileSync(LOG_FILE, line + '\n', 'utf8'); +} - const timestamp = new Date().toISOString(); - let logEntry = `[${timestamp}] ${message}`; +function logSectionToFile(message) { + ensureLogDirectory(); + fs.appendFileSync(LOG_FILE, `[${fileTimestamp()}] ── ${stripAnsi(message)} ──\n`, 'utf8'); +} - if (data) { - logEntry += '\n' + JSON.stringify(data, null, 2); - } +// ANSI colors +const c = { + reset: '\x1b[0m', + dim: '\x1b[2m', + green: '\x1b[32m', + red: '\x1b[31m', + cyan: '\x1b[36m', + bold: '\x1b[1m', +}; - logEntry += '\n' + '-'.repeat(80) + '\n'; - - fs.appendFileSync(LOG_FILE, logEntry, 'utf8'); +function formatTimestamp() { + return new Date().toLocaleString('fr-FR', { + timeZone: 'Europe/Paris', + hour: '2-digit', minute: '2-digit', second: '2-digit' + }); } function log(message, data = null) { - console.log(message, data || ''); + console.log(`${c.dim}${formatTimestamp()}${c.reset} ${message}`); logToFile(message, data); } +function logBanner(version, serverUrl) { + console.log(''); + console.log(` ${c.bold}${c.cyan}SimpleConnect${c.reset} ${c.dim}v${version}${c.reset}`); + console.log(` ${c.dim}Serveur${c.reset} ${serverUrl}`); + console.log(''); + logSectionToFile(`SimpleConnect v${version} — ${serverUrl}`); +} + // Charger la configuration function loadConfig() { const configPath = path.join(__dirname, 'config.json'); @@ -92,49 +126,77 @@ function initializeSocketIO() { return; } + logBanner(app.getVersion(), config.socketio.serverUrl); + adapter = new SocketIOAdapter(config.socketio.serverUrl); - // Demarrer le health check polling + // Demarrer le health check (ecran de login) startHealthCheck(); } // Health check periodique via GET /health +// Actif uniquement sur l'ecran de login (pas d'agent connecte) function startHealthCheck() { const checkHealth = () => { + // Ne pas faire de health check quand un agent est connecte + // (c'est l'adapter Socket.IO qui pilote le voyant) + if (currentAgent) return; + const serverUrl = config.socketio.serverUrl; + let done = false; const request = net.request(`${serverUrl}/health`); request.on('response', (response) => { - if (response.statusCode === 200) { - if (serverStatus !== 'connected') { - // Ne passer en 'connected' que si on n'a pas d'agent connecte - // (sinon le status est deja 'connected' via le login) - if (!currentAgent) { - serverStatus = 'connected'; - sendServerStatus(); - } - } - } else { - serverStatus = 'error'; + if (done) return; + done = true; + const newStatus = response.statusCode === 200 ? 'connected' : 'error'; + if (serverStatus !== newStatus) { + serverStatus = newStatus; sendServerStatus(); } }); request.on('error', () => { - serverStatus = 'error'; - sendServerStatus(); + if (done) return; + done = true; + if (serverStatus !== 'error') { + serverStatus = 'error'; + sendServerStatus(); + } }); + // Timeout 5s — net.request n'a pas de timeout natif + setTimeout(() => { + if (!done) { + done = true; + request.abort(); + if (serverStatus !== 'error') { + serverStatus = 'error'; + sendServerStatus(); + } + } + }, 5000); + request.end(); }; - // Check immediat puis toutes les 10s + // Check immediat puis toutes les 5s checkHealth(); - healthCheckInterval = setInterval(checkHealth, 10000); + healthCheckInterval = setInterval(checkHealth, 5000); +} + +function stopHealthCheck() { + if (healthCheckInterval) { + clearInterval(healthCheckInterval); + healthCheckInterval = null; + } } function sendServerStatus() { + if (serverStatus !== 'connected') { + log(`${c.red}✗${c.reset} Serveur injoignable`); + } if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('server-status', serverStatus); } @@ -177,7 +239,7 @@ function handleCallHungUp(event) { function setupEventHandlers() { if (!adapter) return; - log('Session Socket.IO demarree', { + log('Session Socket.IO démarrée', { serverUrl: config.socketio.serverUrl, serviceProvider: config.socketio.serviceProvider }); @@ -237,17 +299,18 @@ app.on('window-all-closed', async () => { clearInterval(healthCheckInterval); } - // Deconnexion propre avant de quitter + // Déconnexion propre avant de quitter if (currentAgent && adapter) { try { await adapter.logoff(); - console.log('Agent deconnecte avant fermeture'); } catch (error) { - console.error('Erreur deconnexion:', error); + console.error('Erreur déconnexion:', error); } } if (process.platform !== 'darwin') { + console.log(`${c.dim}${formatTimestamp()}${c.reset} ${c.dim}Application fermée${c.reset}`); + logSectionToFile('Application fermée'); app.quit(); } }); @@ -282,8 +345,6 @@ ipcMain.handle('get-terminal-list', async () => { try { const provider = config.socketio.serviceProvider; const url = `${config.socketio.serverUrl}/terminals?provider=${encodeURIComponent(provider)}`; - console.log('Recuperation des terminaux:', url); - return new Promise((resolve, reject) => { const request = net.request(url); let body = ''; @@ -297,7 +358,7 @@ ipcMain.handle('get-terminal-list', async () => { if (response.statusCode === 200) { try { const terminals = JSON.parse(body); - log('Terminaux recuperes', { count: terminals.length, terminals }); + log(`${c.green}✓${c.reset} Serveur connecté — ${terminals.length} terminaux`); resolve(Array.isArray(terminals) ? terminals : []); } catch (e) { console.error('Erreur parsing terminaux:', e); @@ -348,6 +409,26 @@ ipcMain.handle('login-agent', async (event, credentials) => { // Recreer l'adapter pour une connexion fraiche adapter = new SocketIOAdapter(config.socketio.serverUrl); + // L'adapter pilote le voyant une fois connecte + adapter.onStateChange((state) => { + log('Etat Socket.IO change', { state }); + switch (state) { + case 'reconnecting': + serverStatus = 'connecting'; + sendServerStatus(); + break; + case 'connected': + serverStatus = 'connected'; + sendServerStatus(); + break; + case 'error': + case 'disconnected': + serverStatus = 'error'; + sendServerStatus(); + break; + } + }); + // Configurer les handlers d'evenements AVANT connect setupEventHandlers(); @@ -359,8 +440,8 @@ ipcMain.handle('login-agent', async (event, credentials) => { ); if (result) { - console.log('Connexion reussie:', result); - log('Connexion agent reussie', { + console.log('Connexion réussie:', result); + log('Connexion agent réussie', { accessCode: result.accessCode, firstName: result.firstName, lastName: result.lastName, @@ -390,6 +471,8 @@ ipcMain.handle('login-agent', async (event, credentials) => { ); } + // Agent connecte → l'adapter pilote le voyant, on arrete le health check + stopHealthCheck(); serverStatus = 'connected'; sendServerStatus(); @@ -466,8 +549,8 @@ ipcMain.handle('logout', async () => { accessCode: currentAgent.accessCode }); await adapter.logoff(); - console.log('Agent deconnecte du serveur'); - log('Agent deconnecte avec succes', { + console.log('Agent déconnecté du serveur'); + log('Agent déconnecté avec succès', { accessCode: currentAgent.accessCode, name: currentAgent.name }); @@ -480,6 +563,9 @@ ipcMain.handle('logout', async () => { currentTerminal = null; agentConnectionInfo = null; + // Retour ecran login → relancer le health check + startHealthCheck(); + if (mainWindow) { mainWindow.setTitle(`SimpleConnect v${app.getVersion()}`); } @@ -488,6 +574,8 @@ ipcMain.handle('logout', async () => { }); ipcMain.handle('quit-app', async () => { + console.log(`${c.dim}${formatTimestamp()}${c.reset} ${c.dim}Application fermée${c.reset}`); + logSectionToFile('Application fermée'); app.quit(); });