diff --git a/config.json b/config.json
index 943971d..a0ac8aa 100644
--- a/config.json
+++ b/config.json
@@ -1,8 +1,6 @@
{
- "signalR": {
- "enabled": true,
- "serverUrl": "http://10.90.20.201:8002/signalR",
- "serviceProvider": "RDVPREM",
- "terminalsSimulation": ["3001", "3002", "3003", "3004", "3005"]
+ "socketio": {
+ "serverUrl": "http://10.90.20.201:8004",
+ "serviceProvider": "RDVPREM"
}
-}
\ No newline at end of file
+}
diff --git a/main.js b/main.js
index 798a6b9..0ef9556 100644
--- a/main.js
+++ b/main.js
@@ -1,51 +1,46 @@
-const { app, BrowserWindow, ipcMain, session } = require('electron');
+const { app, BrowserWindow, ipcMain, session, net } = require('electron');
const path = require('path');
const fs = require('fs');
const os = require('os');
-const signalR = require('@microsoft/signalr');
-const ConnectionManager = require('./connection-manager');
+const SocketIOAdapter = require('./socketio-adapter');
let mainWindow;
let config;
let currentAgent = null;
-let currentTerminal = null; // Terminal téléphonique de l'agent connecté
-let signalRConnection = null;
-let signalRStatus = 'disconnected'; // disconnected, connecting, connected, error
-let agentConnectionInfo = null; // Informations complètes retournées par SignalR
+let currentTerminal = null;
+let adapter = null;
+let serverStatus = 'disconnected'; // disconnected, connecting, connected, error
+let agentConnectionInfo = null;
+let healthCheckInterval = null;
-// Configuration du système de logs SignalR
-const SIGNALR_LOG_DIR = path.join(os.homedir(), '.simpleconnect-ng');
-const SIGNALR_LOG_FILE = path.join(SIGNALR_LOG_DIR, 'signalr.log');
+// Configuration du systeme de logs
+const LOG_DIR = path.join(os.homedir(), '.simpleconnect-ng');
+const LOG_FILE = path.join(LOG_DIR, 'socketio.log');
-// Créer le répertoire de logs s'il n'existe pas
function ensureLogDirectory() {
- if (!fs.existsSync(SIGNALR_LOG_DIR)) {
- fs.mkdirSync(SIGNALR_LOG_DIR, { recursive: true });
- console.log(`📁 Répertoire de logs créé: ${SIGNALR_LOG_DIR}`);
+ if (!fs.existsSync(LOG_DIR)) {
+ fs.mkdirSync(LOG_DIR, { recursive: true });
}
}
-// Fonction pour écrire dans le fichier de log SignalR
-function logToSignalRFile(message, data = null) {
+function logToFile(message, data = null) {
ensureLogDirectory();
-
+
const timestamp = new Date().toISOString();
let logEntry = `[${timestamp}] ${message}`;
-
+
if (data) {
logEntry += '\n' + JSON.stringify(data, null, 2);
}
-
- logEntry += '\n' + '─'.repeat(80) + '\n';
-
- // Ajouter au fichier (append)
- fs.appendFileSync(SIGNALR_LOG_FILE, logEntry, 'utf8');
+
+ logEntry += '\n' + '-'.repeat(80) + '\n';
+
+ fs.appendFileSync(LOG_FILE, logEntry, 'utf8');
}
-// Logger dans la console ET dans le fichier
-function logSignalR(message, data = null) {
+function log(message, data = null) {
console.log(message, data || '');
- logToSignalRFile(message, data);
+ logToFile(message, data);
}
// Charger la configuration
@@ -55,7 +50,7 @@ function loadConfig() {
config = JSON.parse(configData);
}
-// Créer la fenêtre principale
+// Creer la fenetre principale
function createWindow() {
mainWindow = new BrowserWindow({
width: 1400,
@@ -68,211 +63,92 @@ function createWindow() {
},
icon: path.join(__dirname, 'icon.png'),
title: `SimpleConnect v${app.getVersion()}`,
- autoHideMenuBar: true // Cache la barre de menu par défaut
+ autoHideMenuBar: true
});
- // Supprimer complètement le menu (pour Linux/Windows)
mainWindow.setMenuBarVisibility(false);
-
- // Charger l'interface HTML
mainWindow.loadFile('index.html');
- // Forcer le titre après le chargement de la page (le
HTML l'écrase sinon)
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.setTitle(`SimpleConnect v${app.getVersion()}`);
});
- // Ouvrir les DevTools uniquement en mode développement
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools();
}
- // Gérer la fermeture de la fenêtre
mainWindow.on('closed', () => {
mainWindow = null;
});
}
-// === GESTION SIGNALR/WEBSOCKET ===
-function initializeSignalR() {
- if (!config.signalR || !config.signalR.enabled) {
- console.log('SignalR/WebSocket désactivé dans la configuration');
- signalRStatus = 'disabled';
- sendSignalRStatus();
+// === GESTION SOCKET.IO ===
+
+function initializeSocketIO() {
+ if (!config.socketio || !config.socketio.serverUrl) {
+ console.log('Socket.IO non configure');
+ serverStatus = 'disabled';
+ sendServerStatus();
return;
}
- try {
- // Utiliser le ConnectionManager avec fallback automatique SignalR → WebSocket
- const connectionManager = new ConnectionManager(config.ServerIp || config.signalR.serverUrl.replace('http://', '').replace('/signalR', ''));
-
- // La connexion sera établie plus tard avec fallback automatique
- signalRConnection = connectionManager;
+ adapter = new SocketIOAdapter(config.socketio.serverUrl);
- // Les handlers d'état seront configurés après la connexion
-
- // Démarrer la connexion (les handlers seront configurés après)
- startSignalRConnection();
-
- } catch (error) {
- console.error('Erreur initialisation SignalR:', error);
- signalRStatus = 'error';
- sendSignalRStatus();
- }
+ // Demarrer le health check polling
+ startHealthCheck();
}
-function setupSignalRHandlers() {
- // Configuration des handlers après la connexion
- const connection = signalRConnection.getConnection();
- if (!connection) {
- console.error('Pas de connexion active pour configurer les handlers');
- return;
- }
+// Health check periodique via GET /health
+function startHealthCheck() {
+ const checkHealth = () => {
+ const serverUrl = config.socketio.serverUrl;
- // === LOGGER UNIVERSEL POUR TOUS LES MESSAGES SIGNALR ===
- // Intercepter TOUS les messages reçus du serveur pour découvrir les événements disponibles
-
- // Initialiser le fichier de log avec une session
- logSignalR('════════════════════════════════════════════════════════════');
- logSignalR('🚀 NOUVELLE SESSION SIGNALR DÉMARRÉE');
- logSignalR(`Application: SimpleConnect v${app.getVersion()}`);
- logSignalR(`Serveur SignalR: ${config.signalR.serverUrl}`);
- logSignalR(`Service Provider: ${config.signalR.serviceProvider}`);
- logSignalR('════════════════════════════════════════════════════════════');
-
- // Liste des événements connus pour les logger différemment
- const knownEvents = ['IpbxEvent'];
-
- // Créer un proxy pour intercepter tous les appels .on()
- const originalOn = signalRConnection.on.bind(signalRConnection);
-
- // Logger tous les événements possibles en essayant d'écouter les plus communs
- const possibleEvents = [
- 'IpbxEvent', // Événements téléphoniques (confirmé)
- 'AgentStatusChanged', // Changement de statut agent
- 'QueueUpdate', // Mise à jour des files
- 'CallReceived', // Appel entrant
- 'CallEnded', // Fin d'appel
- 'MessageReceived', // Messages
- 'Notification', // Notifications générales
- 'StatusUpdate', // Mises à jour de statut
- 'SystemMessage', // Messages système
- 'BroadcastMessage', // Messages broadcast
- 'AgentUpdate', // Mises à jour agent
- 'QueueStatistics', // Statistiques de file
- 'PresenceUpdate' // Mise à jour de présence
- ];
-
- // Écouter tous les événements possibles et logger ce qu'on reçoit
- possibleEvents.forEach(eventName => {
- connection.on(eventName, (...args) => {
- // Logger dans la console avec formatage
- console.log('═══════════════════════════════════════════════════════════');
- console.log(`📨 MESSAGE SIGNALR REÇU: ${eventName}`);
- console.log('Timestamp:', new Date().toISOString());
- console.log('Nombre d\'arguments:', args.length);
-
- // Logger chaque argument en détail
- args.forEach((arg, index) => {
- console.log(`Argument ${index}:`, JSON.stringify(arg, null, 2));
- });
-
- console.log('═══════════════════════════════════════════════════════════');
-
- // Logger dans le fichier avec structure
- const logData = {
- event: eventName,
- timestamp: new Date().toISOString(),
- argumentCount: args.length,
- arguments: args.map((arg, index) => ({
- index: index,
- type: typeof arg,
- value: arg
- })),
- agent: currentAgent ? {
- id: currentAgent.id,
- name: currentAgent.name,
- terminal: currentTerminal
- } : null
- };
-
- logSignalR(`📨 MESSAGE SIGNALR REÇU: ${eventName}`, logData);
-
- // Si c'est IpbxEvent, traiter comme avant
- if (eventName === 'IpbxEvent') {
- handleIpbxEventOriginal(args);
+ 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';
+ sendServerStatus();
}
});
- });
-
- // Fonction originale pour traiter les IpbxEvent
- function handleIpbxEventOriginal(args) {
- if (!args || !agentConnectionInfo) return;
-
- const [name, eventArgs] = args;
- const event = eventArgs?.[0] || args[0];
-
- console.log('🔍 Traitement IpbxEvent:', {
- eventCode: event.eventCode,
- terminal: event.terminal,
- queueName: event.queueName,
- fullEvent: event // Logger l'objet complet pour voir tous les champs
+
+ request.on('error', () => {
+ serverStatus = 'error';
+ sendServerStatus();
});
-
- // Vérifier que l'événement est pour notre terminal
- if (event.terminal !== currentTerminal) {
- console.log('⚠️ Événement ignoré - Terminal différent:', event.terminal, '!==', currentTerminal);
- return;
- }
-
- // Gérer les différents types d'événements
- switch(event.eventCode) {
- case 0:
- console.log('📞 Code 0: Appel entrant/sonnerie (non implémenté)');
- break;
- case 1: // Appel décroché
- console.log('✅ Code 1: Appel décroché - Traitement...');
- handleCallPickedUp(event);
- break;
- case 2: // Appel raccroché
- console.log('📴 Code 2: Appel raccroché - Traitement...');
- handleCallHungUp(event);
- break;
- case 3:
- console.log('⏸️ Code 3: Mise en attente (non implémenté)');
- break;
- case 4:
- console.log('↔️ Code 4: Transfert d\'appel (non implémenté)');
- break;
- case 5:
- console.log('👥 Code 5: Conférence (non implémenté)');
- break;
- default:
- console.log('❓ Code événement inconnu:', event.eventCode);
- console.log('Données complètes:', JSON.stringify(event, null, 2));
- }
- }
-
- // Logger aussi les méthodes qu'on peut invoquer
- console.log('📋 MÉTHODES SIGNALR DISPONIBLES POUR INVOCATION:');
- console.log('- AgentLogin(email, password, terminal)');
- console.log('- AgentLogoff(accessCode)');
- console.log('- GetTerminalListByServiceProvider(serviceProvider)');
- console.log('ℹ️ D\'autres méthodes peuvent être disponibles sur le serveur');
+
+ request.end();
+ };
+
+ // Check immediat puis toutes les 10s
+ checkHealth();
+ healthCheckInterval = setInterval(checkHealth, 10000);
}
-// Gérer un appel entrant
+function sendServerStatus() {
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.send('server-status', serverStatus);
+ }
+}
+
+// Gerer un appel entrant
function handleCallPickedUp(event) {
if (!mainWindow || !agentConnectionInfo) return;
-
- // Identifier le centre correspondant à la file
+
const centres = processApplicationUrls(agentConnectionInfo.connList);
const centre = centres.find(c => c.queueName === event.queueName);
-
+
if (centre) {
console.log('Basculement vers le centre:', centre.nom);
-
- // Envoyer l'instruction de basculement à la fenêtre
mainWindow.webContents.send('switch-to-center', {
centreId: centre.id,
centreName: centre.nom,
@@ -281,17 +157,15 @@ function handleCallPickedUp(event) {
eventType: 'call_pickup'
});
} else {
- console.warn('Aucun centre trouvé pour la file:', event.queueName);
+ console.warn('Aucun centre trouve pour la file:', event.queueName);
}
}
-// Gérer la fin d'un appel
+// Gerer la fin d'un appel
function handleCallHungUp(event) {
if (!mainWindow) return;
-
+
console.log('Fin d\'appel sur la file:', event.queueName);
-
- // Envoyer l'instruction de libération à la fenêtre
mainWindow.webContents.send('release-center', {
queueName: event.queueName,
terminal: event.terminal,
@@ -299,68 +173,45 @@ function handleCallHungUp(event) {
});
}
-async function startSignalRConnection() {
- try {
- signalRStatus = 'connecting';
- sendSignalRStatus();
- logSignalR('🔌 Tentative de connexion au serveur...', {
- serverUrl: config.ServerIp || config.signalR.serverUrl,
- status: 'connecting'
- });
-
- // Le ConnectionManager gère le fallback automatiquement
- const connection = await signalRConnection.connect();
-
- // Déterminer quel type de connexion a réussi
- const connectionInfo = signalRConnection.getConnectionInfo();
-
- console.log(`Connexion établie via ${connectionInfo.type}`);
- logSignalR(`✅ Connexion établie avec succès (${connectionInfo.type})`, {
- connectionType: connectionInfo.type,
- isSignalR: connectionInfo.isSignalR,
- isRestSocketIO: connectionInfo.isRestSocketIO,
- status: 'connected',
- serverUrl: connectionInfo.serverUrl
- });
-
- // Maintenant configurer les handlers sur la connexion active
- setupSignalRHandlers();
-
- signalRStatus = 'connected';
- sendSignalRStatus();
-
- } catch (error) {
- console.error('Erreur connexion SignalR:', error);
- logSignalR('❌ Erreur de connexion SignalR', {
- error: error.message,
- stack: error.stack,
- serverUrl: config.signalR.serverUrl
- });
- signalRStatus = 'error';
- sendSignalRStatus();
-
- // Réessayer dans 5 secondes
- setTimeout(() => {
- if (signalRConnection && signalRStatus !== 'connected') {
- startSignalRConnection();
- }
- }, 5000);
- }
-}
+// Configurer les handlers d'evenements Socket.IO apres connexion
+function setupEventHandlers() {
+ if (!adapter) return;
-function sendSignalRStatus() {
- if (mainWindow && !mainWindow.isDestroyed()) {
- mainWindow.webContents.send('signalr-status', signalRStatus);
- }
+ log('Session Socket.IO demarree', {
+ serverUrl: config.socketio.serverUrl,
+ serviceProvider: config.socketio.serviceProvider
+ });
+
+ // Ecouter les evenements d'appels IPBX
+ adapter.on('ipbx_event', (data) => {
+ log('ipbx_event recu', data);
+
+ if (!agentConnectionInfo) return;
+
+ // Verifier que l'evenement est pour notre terminal
+ if (data.terminal !== currentTerminal) {
+ console.log('Evenement ignore - terminal different:', data.terminal, '!==', currentTerminal);
+ return;
+ }
+
+ switch (data.eventCode) {
+ case 1:
+ handleCallPickedUp(data);
+ break;
+ case 2:
+ handleCallHungUp(data);
+ break;
+ default:
+ log('Code evenement non gere:', data);
+ }
+ });
}
// Initialisation de l'application
app.whenReady().then(() => {
- // Supprimer le menu de l'application complètement (toutes plateformes)
const { Menu } = require('electron');
Menu.setApplicationMenu(null);
-
- // Configuration de la session pour éviter les problèmes CORS
+
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
details.requestHeaders['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
callback({ requestHeaders: details.requestHeaders });
@@ -377,33 +228,30 @@ app.whenReady().then(() => {
loadConfig();
createWindow();
-
- // Initialiser SignalR après le chargement de la config
- initializeSignalR();
+ initializeSocketIO();
});
-// Quitter quand toutes les fenêtres sont fermées
+// Quitter quand toutes les fenetres sont fermees
app.on('window-all-closed', async () => {
- // Déconnexion propre avant de quitter
- if (currentAgent && signalRConnection && signalRStatus === 'connected') {
+ if (healthCheckInterval) {
+ clearInterval(healthCheckInterval);
+ }
+
+ // Deconnexion propre avant de quitter
+ if (currentAgent && adapter) {
try {
- await signalRConnection.invoke('AgentLogoff', currentAgent.accessCode);
- console.log('Déconnexion agent avant fermeture');
+ await adapter.logoff();
+ console.log('Agent deconnecte avant fermeture');
} catch (error) {
- console.error('Erreur déconnexion:', error);
+ console.error('Erreur deconnexion:', error);
}
}
-
- // Ne pas appeler disconnect() pour éviter l'envoi du CloseMessage
- // Le serveur .NET ne supporte pas ce message
- // On laisse la connexion se fermer naturellement avec l'application
-
+
if (process.platform !== 'darwin') {
app.quit();
}
});
-// Réactiver l'app sur macOS
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
@@ -412,108 +260,107 @@ app.on('activate', () => {
// === IPC HANDLERS ===
-// Obtenir la configuration
ipcMain.handle('get-config', () => {
return config;
});
-// Obtenir la version de l'application
ipcMain.handle('get-app-version', () => {
return app.getVersion();
});
-// Obtenir le statut SignalR
-ipcMain.handle('get-signalr-status', () => {
- return signalRStatus;
+// Renomme : get-signalr-status -> get-server-status
+ipcMain.handle('get-server-status', () => {
+ return serverStatus;
});
-// Récupérer la liste des terminaux téléphoniques
+// Recuperer la liste des terminaux via REST
ipcMain.handle('get-terminal-list', async () => {
- // Mode simulation si SignalR non connecté
- if (!signalRConnection || signalRStatus !== 'connected') {
- console.log('SignalR non connecté, utilisation des terminaux de simulation');
- return config.signalR.terminalsSimulation || ['3001', '3002', '3003'];
+ if (!config.socketio || !config.socketio.serverUrl) {
+ return [];
}
-
+
try {
- console.log('Récupération des terminaux pour:', config.signalR.serviceProvider);
- logSignalR('📡 Invocation SignalR: GetTerminalListByServiceProvider', {
- method: 'GetTerminalListByServiceProvider',
- serviceProvider: config.signalR.serviceProvider
+ 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 = '';
+
+ request.on('response', (response) => {
+ response.on('data', (chunk) => {
+ body += chunk.toString();
+ });
+
+ response.on('end', () => {
+ if (response.statusCode === 200) {
+ try {
+ const terminals = JSON.parse(body);
+ log('Terminaux recuperes', { count: terminals.length, terminals });
+ resolve(Array.isArray(terminals) ? terminals : []);
+ } catch (e) {
+ console.error('Erreur parsing terminaux:', e);
+ resolve([]);
+ }
+ } else {
+ console.error('Erreur terminaux HTTP', response.statusCode);
+ resolve([]);
+ }
+ });
+ });
+
+ request.on('error', (error) => {
+ console.error('Erreur recuperation terminaux:', error);
+ resolve([]);
+ });
+
+ request.end();
});
-
- const terminals = await signalRConnection.invoke(
- 'GetTerminalListByServiceProvider',
- config.signalR.serviceProvider
- );
-
- console.log('Terminaux disponibles:', terminals);
- logSignalR('📞 Terminaux récupérés', {
- count: terminals.length,
- terminals: terminals
- });
- return terminals || [];
} catch (error) {
- console.error('Erreur récupération terminaux:', error);
- // Retourner les terminaux de simulation en cas d'erreur
- return config.signalR.terminalsSimulation || ['3001', '3002', '3003'];
+ console.error('Erreur recuperation terminaux:', error);
+ return [];
}
});
-// Connexion agent via SignalR
+// Connexion agent via Socket.IO
ipcMain.handle('login-agent', async (event, credentials) => {
- // Vérifier que SignalR est connecté
- if (!signalRConnection || signalRStatus !== 'connected') {
- return {
- success: false,
- message: 'Connexion au serveur SignalR non établie. Veuillez réessayer.'
+ if (!adapter) {
+ return {
+ success: false,
+ message: 'Socket.IO non initialise. Veuillez reessayer.'
};
}
try {
console.log('Tentative de connexion agent:', credentials.email, 'Terminal:', credentials.terminal);
- logSignalR('🔐 Tentative de connexion agent', {
+ log('Tentative de connexion agent', {
email: credentials.email,
terminal: credentials.terminal,
forceDisconnect: credentials.forceDisconnect || false
});
-
- // Si déconnexion forcée demandée, déconnecter d'abord la session précédente
- if (credentials.forceDisconnect) {
- console.log('Déconnexion forcée demandée pour:', credentials.email);
- try {
- // Tenter la déconnexion avec le code d'accès
- await signalRConnection.invoke('AgentLogoff', credentials.email);
- console.log('Session précédente déconnectée avec succès');
- logSignalR('🔓 Session précédente déconnectée (forceDisconnect)', {
- email: credentials.email
- });
- } catch (logoffError) {
- console.warn('Erreur lors de la déconnexion forcée (session peut-être déjà fermée):', logoffError.message);
- logSignalR('⚠️ Erreur déconnexion forcée', {
- email: credentials.email,
- error: logoffError.message
- });
- // Continuer même si la déconnexion échoue - la session est peut-être déjà fermée
- }
+
+ // Deconnecter l'adapter precedent s'il y en a un
+ if (adapter.state === 'connected') {
+ adapter.disconnect();
}
-
- // Appel SignalR pour l'authentification
- logSignalR('📡 Invocation SignalR: AgentLogin', {
- method: 'AgentLogin',
- email: credentials.email,
- terminal: credentials.terminal
- });
-
- const result = await signalRConnection.invoke('AgentLogin',
+
+ // Recreer l'adapter pour une connexion fraiche
+ adapter = new SocketIOAdapter(config.socketio.serverUrl);
+
+ // Configurer les handlers d'evenements AVANT connect
+ setupEventHandlers();
+
+ // Connexion avec auth au handshake
+ const result = await adapter.connect(
credentials.email,
credentials.password,
credentials.terminal
);
-
+
if (result) {
- console.log('Connexion réussie:', result);
- logSignalR('✅ Connexion agent réussie', {
+ console.log('Connexion reussie:', result);
+ log('Connexion agent reussie', {
accessCode: result.accessCode,
firstName: result.firstName,
lastName: result.lastName,
@@ -521,12 +368,10 @@ ipcMain.handle('login-agent', async (event, credentials) => {
connListCount: result.connList ? result.connList.length : 0,
connList: result.connList
});
-
- // Stocker les informations de connexion
+
agentConnectionInfo = result;
currentTerminal = credentials.terminal;
-
- // Créer l'objet agent pour compatibilité
+
currentAgent = {
id: result.accessCode,
accessCode: result.accessCode,
@@ -536,31 +381,32 @@ ipcMain.handle('login-agent', async (event, credentials) => {
lastName: result.lastName,
terminal: credentials.terminal
};
-
- // Traiter les URLs des applications et créer les centres
+
const centres = processApplicationUrls(result.connList);
-
- // Mettre à jour le titre de la fenêtre
+
if (mainWindow) {
mainWindow.setTitle(
`SimpleConnect v${app.getVersion()} - Agent: ${currentAgent.accessCode} (${result.firstName} ${result.lastName}) - Tel: ${credentials.terminal}`
);
}
-
+
+ serverStatus = 'connected';
+ sendServerStatus();
+
return {
success: true,
agent: currentAgent,
centres: centres
};
}
-
- return { success: false, message: 'Échec de l\'authentification' };
-
+
+ return { success: false, message: 'Echec de l\'authentification' };
+
} catch (error) {
console.error('Erreur lors de la connexion agent:', error);
- return {
- success: false,
- message: error.message || 'Erreur de connexion au serveur'
+ return {
+ success: false,
+ message: error.message || 'Erreur de connexion au serveur'
};
}
});
@@ -568,10 +414,10 @@ ipcMain.handle('login-agent', async (event, credentials) => {
// Traiter les URLs des applications avec les placeholders
function processApplicationUrls(connList) {
if (!connList || connList.length === 0) return [];
-
+
return connList.map((conn, index) => {
let url = conn.applicationName;
-
+
// Remplacer les placeholders
if (url.includes('#CA#')) {
url = url.replace('#CA#', conn.accessCode || '');
@@ -579,8 +425,8 @@ function processApplicationUrls(connList) {
if (url.includes('#MP#')) {
url = url.replace('#MP#', conn.password || '');
}
-
- // Gérer les cas spécifiques des plateformes connues
+
+ // Gerer les cas specifiques des plateformes connues
if (url === 'pro.mondocteur.fr' || url.includes('mondocteur.fr')) {
if (!url.startsWith('http')) {
url = 'https://pro.mondocteur.fr/backoffice.do';
@@ -590,11 +436,9 @@ function processApplicationUrls(connList) {
url = 'https://pro.doctolib.fr/signin';
}
} else if (!url.startsWith('http')) {
- // Ajouter https:// par défaut si pas de protocole
url = `https://${url}`;
}
-
- // Créer l'objet centre compatible avec l'interface
+
return {
id: conn.code || `centre${index + 1}`,
nom: conn.queueName || conn.code || `Centre ${index + 1}`,
@@ -604,68 +448,54 @@ function processApplicationUrls(connList) {
username: conn.accessCode,
password: conn.password
},
- queueName: conn.queueName // Garder pour le mapping avec les événements IPBX
+ queueName: conn.queueName
};
});
}
-// Fonction helper pour attribuer des couleurs aux centres
function getColorForIndex(index) {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#DDA0DD', '#FFD93D', '#6BCB77'];
return colors[index % colors.length];
}
-// Déconnexion agent via SignalR
+// Deconnexion agent via Socket.IO
ipcMain.handle('logout', async () => {
- if (currentAgent && signalRConnection && signalRStatus === 'connected') {
+ if (currentAgent && adapter) {
try {
- // Appeler SignalR pour la déconnexion
- logSignalR('📡 Invocation SignalR: AgentLogoff', {
- method: 'AgentLogoff',
+ log('Logoff agent', {
accessCode: currentAgent.accessCode
});
- await signalRConnection.invoke('AgentLogoff', currentAgent.accessCode);
- console.log('Agent déconnecté du serveur SignalR');
- logSignalR('👋 Agent déconnecté avec succès', {
+ await adapter.logoff();
+ console.log('Agent deconnecte du serveur');
+ log('Agent deconnecte avec succes', {
accessCode: currentAgent.accessCode,
name: currentAgent.name
});
} catch (error) {
- console.error('Erreur lors de la déconnexion SignalR:', error);
+ console.error('Erreur lors de la deconnexion:', error);
}
}
-
- // Réinitialiser les variables locales
+
currentAgent = null;
currentTerminal = null;
agentConnectionInfo = null;
-
- // Réinitialiser le titre de la fenêtre
+
if (mainWindow) {
mainWindow.setTitle(`SimpleConnect v${app.getVersion()}`);
}
-
+
return { success: true };
});
-// Handler pour quitter l'application proprement
ipcMain.handle('quit-app', async () => {
- // Ne pas appeler disconnect() pour éviter l'envoi du CloseMessage
- // Le serveur .NET ne supporte pas ce message
- // On laisse la connexion se fermer naturellement avec l'application
- // (comme le fait le client de prod)
-
- // Quitter l'application
app.quit();
});
-// Obtenir l'agent actuel
ipcMain.handle('get-current-agent', () => {
if (!currentAgent || !agentConnectionInfo) return null;
-
- // Retourner les centres traités depuis SignalR
+
const centres = processApplicationUrls(agentConnectionInfo.connList);
-
+
return {
agent: currentAgent,
centres: centres,
@@ -673,17 +503,15 @@ ipcMain.handle('get-current-agent', () => {
};
});
-// Simuler un appel entrant
ipcMain.handle('simulate-call', (event, callData) => {
- // Envoyer l'événement d'appel entrant à la fenêtre
mainWindow.webContents.send('incoming-call', callData);
return { success: true };
});
-// Obtenir les données pour simuler des appels
ipcMain.handle('get-simulated-calls', () => {
+ if (!config.cti || !config.cti.appelSimules) return [];
return config.cti.appelSimules.map(appel => {
- const centre = config.centres.find(c => c.id === appel.centreId);
+ const centre = config.centres ? config.centres.find(c => c.id === appel.centreId) : null;
return {
...appel,
centreNom: centre ? centre.nom : 'Centre inconnu'
@@ -691,18 +519,16 @@ ipcMain.handle('get-simulated-calls', () => {
});
});
-// Sauvegarder les notes de l'agent (un seul fichier par agent)
+// Sauvegarder les notes de l'agent
ipcMain.handle('save-notes', (event, noteData) => {
const notesDir = path.join(__dirname, 'notes');
if (!fs.existsSync(notesDir)) {
fs.mkdirSync(notesDir);
}
-
- // Un seul fichier par agent, mis à jour à chaque sauvegarde
+
const fileName = `notes_${currentAgent.id}.json`;
const filePath = path.join(notesDir, fileName);
-
- // Lire l'historique existant si le fichier existe
+
let notesData = {
agent: currentAgent.id,
agentName: currentAgent.name,
@@ -711,12 +537,10 @@ ipcMain.handle('save-notes', (event, noteData) => {
centre: noteData.centre,
history: []
};
-
- // Si le fichier existe, préserver l'historique
+
if (fs.existsSync(filePath)) {
try {
const existingData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
- // Ajouter l'ancienne note à l'historique si elle a changé
if (existingData.currentNote && existingData.currentNote !== noteData.content) {
notesData.history = existingData.history || [];
notesData.history.unshift({
@@ -724,27 +548,25 @@ ipcMain.handle('save-notes', (event, noteData) => {
date: existingData.lastModified,
centre: existingData.centre
});
- // Limiter l'historique à 50 entrées
notesData.history = notesData.history.slice(0, 50);
}
} catch (error) {
console.error('Erreur lecture notes existantes:', error);
}
}
-
+
fs.writeFileSync(filePath, JSON.stringify(notesData, null, 2));
-
+
return { success: true, file: fileName };
});
-// Récupérer les notes de l'agent
ipcMain.handle('get-notes', () => {
if (!currentAgent) return null;
-
+
const notesDir = path.join(__dirname, 'notes');
const fileName = `notes_${currentAgent.id}.json`;
const filePath = path.join(notesDir, fileName);
-
+
if (fs.existsSync(filePath)) {
try {
const data = fs.readFileSync(filePath, 'utf8');
@@ -754,11 +576,10 @@ ipcMain.handle('get-notes', () => {
return null;
}
}
-
+
return null;
});
-// Obtenir l'historique des appels
ipcMain.handle('get-call-history', () => {
const historyFile = path.join(__dirname, 'call_history.json');
if (fs.existsSync(historyFile)) {
@@ -768,30 +589,27 @@ ipcMain.handle('get-call-history', () => {
return [];
});
-// Sauvegarder un appel dans l'historique
ipcMain.handle('save-call-history', (event, callData) => {
const historyFile = path.join(__dirname, 'call_history.json');
let history = [];
-
+
if (fs.existsSync(historyFile)) {
const data = fs.readFileSync(historyFile, 'utf8');
history = JSON.parse(data);
}
-
+
history.unshift({
...callData,
agentId: currentAgent?.id,
timestamp: new Date().toISOString()
});
-
- // Garder seulement les 100 derniers appels
+
history = history.slice(0, 100);
-
+
fs.writeFileSync(historyFile, JSON.stringify(history, null, 2));
return { success: true };
});
-// Vérifier si on est en mode développement
ipcMain.handle('is-development', () => {
return process.env.NODE_ENV === 'development';
-});
\ No newline at end of file
+});
diff --git a/package.json b/package.json
index d468a92..b916c8f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "simpleconnect-electron",
- "version": "1.5.0",
+ "version": "2.0.0",
"description": "Application de gestion centralisée des plannings médicaux pour centres d'appels",
"main": "main.js",
"scripts": {
diff --git a/renderer.js b/renderer.js
index 718a3f8..3e1a04c 100644
--- a/renderer.js
+++ b/renderer.js
@@ -29,18 +29,18 @@ document.addEventListener('DOMContentLoaded', async () => {
versionLoginElement.textContent = `v${appVersion}`;
}
- // Initialiser l'indicateur SignalR
+ // Initialiser l'indicateur de statut serveur
- // Écouter les changements de statut SignalR
- ipcRenderer.on('signalr-status', (event, status) => {
- updateSignalRIndicator(status);
- // Recharger les terminaux à chaque changement de statut
+ // Ecouter les changements de statut serveur
+ ipcRenderer.on('server-status', (event, status) => {
+ updateServerIndicator(status);
+ // Recharger les terminaux a chaque changement de statut
loadTerminals();
});
- // Obtenir le statut initial SignalR
- const initialStatus = await ipcRenderer.invoke('get-signalr-status');
- updateSignalRIndicator(initialStatus);
+ // Obtenir le statut initial
+ const initialStatus = await ipcRenderer.invoke('get-server-status');
+ updateServerIndicator(initialStatus);
// Charger immédiatement les terminaux pour la page de login
await loadTerminals();
@@ -129,7 +129,7 @@ document.addEventListener('DOMContentLoaded', async () => {
handleIncomingCall(callData);
});
- // Écouter les événements SignalR de basculement de centre
+ // Ecouter les evenements de basculement de centre
ipcRenderer.on('switch-to-center', (event, data) => {
console.log('Basculement vers le centre:', data.centreName);
@@ -162,7 +162,7 @@ document.addEventListener('DOMContentLoaded', async () => {
});
});
-// Connexion via SignalR
+// Connexion agent
async function handleLogin(e) {
e.preventDefault();
@@ -221,7 +221,7 @@ async function handleLogin(e) {
await new Promise(resolve => setTimeout(resolve, 300));
try {
- // Préparer les credentials pour SignalR
+ // Preparer les credentials
const credentials = {
email: accessCode, // Utiliser directement le code agent comme email
password: password,
@@ -229,7 +229,7 @@ async function handleLogin(e) {
forceDisconnect: forceDisconnect // Ajouter l'option de déconnexion forcée
};
- // Appeler l'authentification SignalR
+ // Appeler l'authentification
const result = await ipcRenderer.invoke('login-agent', credentials);
if (result.success) {
@@ -835,7 +835,7 @@ async function loadTerminals() {
console.log('Chargement des terminaux...');
try {
- // Récupérer les terminaux depuis le serveur SignalR
+ // Recuperer les terminaux depuis le serveur
const terminals = await ipcRenderer.invoke('get-terminal-list');
availableTerminals = terminals || [];
console.log(`${terminals.length} terminaux récupérés`);
@@ -1235,20 +1235,20 @@ function refreshCurrentWebview() {
}
}
-// === GESTION INDICATEUR SIGNALR ===
-function updateSignalRIndicator(status) {
+// === GESTION INDICATEUR STATUT SERVEUR ===
+function updateServerIndicator(status) {
const indicator = document.getElementById('signalrIndicator');
const text = document.getElementById('signalrText');
-
+
if (!indicator || !text) return;
-
- // Réinitialiser les classes
+
+ // Reinitialiser les classes
indicator.className = 'signalr-indicator';
-
+
switch(status) {
case 'connected':
indicator.classList.add('connected');
- text.textContent = 'Connecté au serveur';
+ text.textContent = 'Serveur connecte';
break;
case 'connecting':
indicator.classList.add('connecting');
@@ -1256,17 +1256,17 @@ function updateSignalRIndicator(status) {
break;
case 'disconnected':
indicator.classList.add('disconnected');
- text.textContent = 'Serveur déconnecté';
+ text.textContent = 'Serveur deconnecte';
break;
case 'error':
indicator.classList.add('error');
- text.textContent = 'Erreur de connexion';
+ text.textContent = 'Serveur injoignable';
break;
case 'disabled':
indicator.classList.add('disabled');
- text.textContent = 'SignalR désactivé';
+ text.textContent = 'Non configure';
break;
default:
- text.textContent = 'État inconnu';
+ text.textContent = 'Etat inconnu';
}
}
\ No newline at end of file
diff --git a/socketio-adapter.js b/socketio-adapter.js
new file mode 100644
index 0000000..2ff8015
--- /dev/null
+++ b/socketio-adapter.js
@@ -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