feat: migration Socket.IO natif — login, terminaux REST, health check (closes #3)

Remplace toute la couche SignalR par une connexion Socket.IO directe
au serveur Python (port 8004). Auth au handshake, reconnexion native
illimitée, terminaux via REST GET /terminals.

- socketio-adapter.js : connect/logoff/disconnect, events login_ok/login_error
- main.js : initializeSocketIO, health check net.request, terminaux REST
- renderer.js : IPC signalr-status → server-status
- config.json : clé socketio (plus signalR)
- Version 2.0.0
This commit is contained in:
Pierre Marx
2026-03-18 17:31:30 -04:00
parent 630f1fa8c3
commit 77a310976b
5 changed files with 414 additions and 447 deletions

151
socketio-adapter.js Normal file
View File

@@ -0,0 +1,151 @@
/**
* Adaptateur Socket.IO natif pour le serveur Python SimpleServer
*
* Connexion directe au port 8004, auth au handshake,
* reconnexion native illimitee.
*/
const io = require('socket.io-client');
class SocketIOAdapter {
constructor(serverUrl) {
this.serverUrl = serverUrl;
this.socket = null;
this._state = 'disconnected'; // disconnected, connecting, connected, error
this._eventHandlers = new Map();
}
/**
* Connexion avec auth au handshake.
* Le serveur authentifie dans le handler connect et emet 'login_ok' avec le resultat.
* @returns {Promise<object>} connResult (accessCode, firstName, lastName, connList)
*/
connect(accessCode, password, terminal) {
return new Promise((resolve, reject) => {
this._state = 'connecting';
this.socket = io(this.serverUrl, {
auth: { access_code: accessCode, password, terminal },
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: Infinity,
reconnectionDelay: 2000,
reconnectionDelayMax: 10000,
});
let settled = false;
// Le serveur emet 'login_ok' avec les donnees de session
this.socket.once('login_ok', (data) => {
if (settled) return;
settled = true;
this._state = 'connected';
resolve(data);
});
// Le serveur emet 'login_error' si auth echouee (avant return false)
this.socket.once('login_error', (data) => {
if (settled) return;
settled = true;
this._state = 'error';
this.socket.disconnect();
reject(new Error(data.message || 'Authentification refusee'));
});
// Erreur de connexion (serveur injoignable ou return false du handler connect)
this.socket.on('connect_error', (err) => {
if (!settled) {
settled = true;
this._state = 'error';
this.socket.disconnect();
reject(new Error(err.message || 'Connexion refusee'));
}
// Apres login reussi : reconnexion auto geree par socket.io
});
// Timeout de connexion initiale (15s)
setTimeout(() => {
if (!settled) {
settled = true;
this._state = 'error';
this.socket.disconnect();
reject(new Error('Timeout de connexion au serveur'));
}
}, 15000);
// Restaurer les handlers enregistres avant connect
this._eventHandlers.forEach((handler, event) => {
this.socket.on(event, handler);
});
});
}
/**
* Deconnexion volontaire avec logoff IPBX.
* Emet 'logout' et attend 'logout_ok' du serveur.
*/
logoff() {
return new Promise((resolve) => {
if (!this.socket || !this.socket.connected) {
this._state = 'disconnected';
resolve();
return;
}
this.socket.once('logout_ok', () => {
this._state = 'disconnected';
resolve();
});
this.socket.emit('logout');
// Timeout si le serveur ne repond pas
setTimeout(() => {
if (this.socket) {
this.socket.disconnect();
}
this._state = 'disconnected';
resolve();
}, 5000);
});
}
/**
* Deconnexion brute (sans logoff IPBX).
*/
disconnect() {
if (this.socket) {
this.socket.disconnect();
}
this._state = 'disconnected';
}
/**
* Ecouter un evenement serveur.
*/
on(event, handler) {
this._eventHandlers.set(event, handler);
if (this.socket) {
this.socket.on(event, handler);
}
}
/**
* Retirer un handler d'evenement.
*/
off(event) {
this._eventHandlers.delete(event);
if (this.socket) {
this.socket.off(event);
}
}
/**
* Etat de la connexion.
*/
get state() {
return this._state;
}
}
module.exports = SocketIOAdapter;