/** * 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, socketFactory = null) { this.serverUrl = serverUrl; this._socketFactory = socketFactory || io; this.socket = null; this._state = 'disconnected'; // disconnected, connecting, connected, reconnecting, error this._eventHandlers = new Map(); this._onStateChange = null; // callback pour notifier main.js des changements d'etat } /** * Enregistrer un callback de changement d'etat. * @param {function(string)} callback - recoit le nouvel etat */ onStateChange(callback) { this._onStateChange = callback; } _setState(state) { this._state = state; if (this._onStateChange) { this._onStateChange(state); } } /** * Connexion avec auth au handshake. * Le serveur authentifie dans le handler connect et emet 'login_ok' avec le resultat. * @returns {Promise} connResult (accessCode, firstName, lastName, connList) */ connect(accessCode, password, terminal) { return new Promise((resolve, reject) => { this._setState('connecting'); this.socket = this._socketFactory(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._setState('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._setState('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._setState('error'); this.socket.disconnect(); reject(new Error(err.message || 'Connexion refusee')); } // Apres login reussi : reconnexion auto, on passe en 'reconnecting' }); // Timeout de connexion initiale (15s) setTimeout(() => { if (!settled) { settled = true; this._setState('error'); this.socket.disconnect(); reject(new Error('Timeout de connexion au serveur')); } }, 15000); // === Handlers de reconnexion (actifs apres le premier login) === // Deconnexion temporaire → passer en 'reconnecting' this.socket.on('disconnect', (reason) => { if (this._state === 'connected') { // Deconnexion temporaire, socket.io va retenter this._setState('reconnecting'); } }); // Reconnexion reussie → le serveur recoit un nouveau connect avec les memes auth // et emet 'login_ok' (reprise de session) this.socket.on('login_ok', (data) => { if (settled && this._state === 'reconnecting') { this._setState('connected'); } }); // 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._setState('disconnected'); resolve(); return; } const timeout = setTimeout(() => { if (this.socket) { this.socket.disconnect(); } this._setState('disconnected'); resolve(); }, 5000); this.socket.once('logout_ok', () => { clearTimeout(timeout); this._setState('disconnected'); resolve(); }); this.socket.emit('logout'); }); } /** * Deconnexion brute (sans logoff IPBX). */ disconnect() { if (this.socket) { this.socket.disconnect(); } this._setState('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;