- Système de logging SignalR complet dans ~/.simpleconnect-ng/signalr.log - Capture de tous les événements SignalR avec format JSON structuré - Remplacement des emojis par icônes SVG pour compatibilité Linux - Suppression complète de la barre de menu Electron - Configuration build Linux multi-architecture (x64/arm64) - Support AppImage, .deb et .rpm - Bump version 1.2.16
807 lines
26 KiB
JavaScript
807 lines
26 KiB
JavaScript
const { app, BrowserWindow, ipcMain, session } = require('electron');
|
||
const path = require('path');
|
||
const fs = require('fs');
|
||
const os = require('os');
|
||
const signalR = require('@microsoft/signalr');
|
||
|
||
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
|
||
|
||
// 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');
|
||
|
||
// 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}`);
|
||
}
|
||
}
|
||
|
||
// Fonction pour écrire dans le fichier de log SignalR
|
||
function logToSignalRFile(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');
|
||
}
|
||
|
||
// Logger dans la console ET dans le fichier
|
||
function logSignalR(message, data = null) {
|
||
console.log(message, data || '');
|
||
logToSignalRFile(message, data);
|
||
}
|
||
|
||
// Charger la configuration
|
||
function loadConfig() {
|
||
const configPath = path.join(__dirname, 'config.json');
|
||
const configData = fs.readFileSync(configPath, 'utf8');
|
||
config = JSON.parse(configData);
|
||
}
|
||
|
||
// Créer la fenêtre principale
|
||
function createWindow() {
|
||
mainWindow = new BrowserWindow({
|
||
width: 1400,
|
||
height: 900,
|
||
webPreferences: {
|
||
nodeIntegration: true,
|
||
contextIsolation: false,
|
||
webviewTag: true,
|
||
webSecurity: false
|
||
},
|
||
icon: path.join(__dirname, 'icon.png'),
|
||
title: 'SimpleConnect',
|
||
autoHideMenuBar: true // Cache la barre de menu par défaut
|
||
});
|
||
|
||
// Supprimer complètement le menu (pour Linux/Windows)
|
||
mainWindow.setMenuBarVisibility(false);
|
||
|
||
// Charger l'interface HTML
|
||
mainWindow.loadFile('index.html');
|
||
|
||
// 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 ===
|
||
function initializeSignalR() {
|
||
if (!config.signalR || !config.signalR.enabled) {
|
||
console.log('SignalR désactivé dans la configuration');
|
||
signalRStatus = 'disabled';
|
||
sendSignalRStatus();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Créer la connexion SignalR
|
||
signalRConnection = new signalR.HubConnectionBuilder()
|
||
.withUrl(config.signalR.serverUrl)
|
||
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
|
||
.configureLogging(signalR.LogLevel.Information)
|
||
.build();
|
||
|
||
// Gérer les changements d'état
|
||
signalRConnection.onreconnecting(() => {
|
||
console.log('SignalR: Reconnexion en cours...');
|
||
logSignalR('🔄 SignalR en reconnexion...', {
|
||
previousStatus: signalRStatus,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
signalRStatus = 'connecting';
|
||
sendSignalRStatus();
|
||
});
|
||
|
||
signalRConnection.onreconnected(() => {
|
||
console.log('SignalR: Reconnecté');
|
||
logSignalR('🔗 SignalR reconnecté avec succès', {
|
||
connectionId: signalRConnection.connectionId,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
signalRStatus = 'connected';
|
||
sendSignalRStatus();
|
||
});
|
||
|
||
signalRConnection.onclose(() => {
|
||
console.log('SignalR: Connexion fermée');
|
||
logSignalR('🔌 SignalR déconnecté', {
|
||
lastConnectionId: signalRConnection.connectionId,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
signalRStatus = 'disconnected';
|
||
sendSignalRStatus();
|
||
});
|
||
|
||
// Configurer les méthodes SignalR
|
||
setupSignalRMethods();
|
||
|
||
// Démarrer la connexion
|
||
startSignalRConnection();
|
||
|
||
} catch (error) {
|
||
console.error('Erreur initialisation SignalR:', error);
|
||
signalRStatus = 'error';
|
||
sendSignalRStatus();
|
||
}
|
||
}
|
||
|
||
function setupSignalRMethods() {
|
||
// === 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 => {
|
||
signalRConnection.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);
|
||
}
|
||
});
|
||
});
|
||
|
||
// 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
|
||
});
|
||
|
||
// 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');
|
||
}
|
||
|
||
// Gérer 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,
|
||
queueName: event.queueName,
|
||
terminal: event.terminal,
|
||
eventType: 'call_pickup'
|
||
});
|
||
} else {
|
||
console.warn('Aucun centre trouvé pour la file:', event.queueName);
|
||
}
|
||
}
|
||
|
||
// Gérer 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,
|
||
eventType: 'call_hangup'
|
||
});
|
||
}
|
||
|
||
async function startSignalRConnection() {
|
||
try {
|
||
signalRStatus = 'connecting';
|
||
sendSignalRStatus();
|
||
logSignalR('🔌 Tentative de connexion SignalR...', {
|
||
serverUrl: config.signalR.serverUrl,
|
||
status: 'connecting'
|
||
});
|
||
|
||
await signalRConnection.start();
|
||
console.log('SignalR: Connexion établie');
|
||
logSignalR('✅ Connexion SignalR établie avec succès', {
|
||
connectionId: signalRConnection.connectionId,
|
||
status: 'connected',
|
||
serverUrl: config.signalR.serverUrl
|
||
});
|
||
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);
|
||
}
|
||
}
|
||
|
||
function sendSignalRStatus() {
|
||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||
mainWindow.webContents.send('signalr-status', signalRStatus);
|
||
}
|
||
}
|
||
|
||
// 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 });
|
||
});
|
||
|
||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||
callback({
|
||
responseHeaders: {
|
||
...details.responseHeaders,
|
||
'Content-Security-Policy': ['default-src * \'unsafe-inline\' \'unsafe-eval\' data: blob:;']
|
||
}
|
||
});
|
||
});
|
||
|
||
loadConfig();
|
||
createWindow();
|
||
|
||
// Initialiser SignalR après le chargement de la config
|
||
initializeSignalR();
|
||
});
|
||
|
||
// Quitter quand toutes les fenêtres sont fermées
|
||
app.on('window-all-closed', async () => {
|
||
// Déconnexion propre avant de quitter
|
||
if (currentAgent && signalRConnection && signalRStatus === 'connected') {
|
||
try {
|
||
await signalRConnection.invoke('AgentLogoff', currentAgent.accessCode);
|
||
console.log('Déconnexion agent avant fermeture');
|
||
} catch (error) {
|
||
console.error('Erreur déconnexion:', error);
|
||
}
|
||
}
|
||
|
||
// Arrêter SignalR
|
||
if (signalRConnection) {
|
||
await signalRConnection.stop();
|
||
}
|
||
|
||
if (process.platform !== 'darwin') {
|
||
app.quit();
|
||
}
|
||
});
|
||
|
||
// Réactiver l'app sur macOS
|
||
app.on('activate', () => {
|
||
if (mainWindow === null) {
|
||
createWindow();
|
||
}
|
||
});
|
||
|
||
// === IPC HANDLERS ===
|
||
|
||
// Obtenir la configuration
|
||
ipcMain.handle('get-config', () => {
|
||
return config;
|
||
});
|
||
|
||
// Obtenir le statut SignalR
|
||
ipcMain.handle('get-signalr-status', () => {
|
||
return signalRStatus;
|
||
});
|
||
|
||
// Récupérer la liste des terminaux téléphoniques
|
||
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'];
|
||
}
|
||
|
||
try {
|
||
console.log('Récupération des terminaux pour:', config.signalR.serviceProvider);
|
||
logSignalR('📡 Invocation SignalR: GetTerminalListByServiceProvider', {
|
||
method: 'GetTerminalListByServiceProvider',
|
||
serviceProvider: config.signalR.serviceProvider
|
||
});
|
||
|
||
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'];
|
||
}
|
||
});
|
||
|
||
// Connexion agent via SignalR
|
||
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.'
|
||
};
|
||
}
|
||
|
||
try {
|
||
console.log('Tentative de connexion agent:', credentials.email, 'Terminal:', credentials.terminal);
|
||
logSignalR('🔐 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
|
||
}
|
||
}
|
||
|
||
// Appel SignalR pour l'authentification
|
||
logSignalR('📡 Invocation SignalR: AgentLogin', {
|
||
method: 'AgentLogin',
|
||
email: credentials.email,
|
||
terminal: credentials.terminal
|
||
});
|
||
|
||
const result = await signalRConnection.invoke('AgentLogin',
|
||
credentials.email,
|
||
credentials.password,
|
||
credentials.terminal
|
||
);
|
||
|
||
if (result) {
|
||
console.log('Connexion réussie:', result);
|
||
logSignalR('✅ Connexion agent réussie', {
|
||
accessCode: result.accessCode,
|
||
firstName: result.firstName,
|
||
lastName: result.lastName,
|
||
terminal: credentials.terminal,
|
||
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,
|
||
name: `${result.firstName} ${result.lastName}`,
|
||
email: credentials.email,
|
||
firstName: result.firstName,
|
||
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 - Agent: ${currentAgent.accessCode} (${result.firstName} ${result.lastName}) - Tel: ${credentials.terminal}`
|
||
);
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
agent: currentAgent,
|
||
centres: centres
|
||
};
|
||
}
|
||
|
||
return { success: false, message: 'Échec 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'
|
||
};
|
||
}
|
||
});
|
||
|
||
// 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 || '');
|
||
}
|
||
if (url.includes('#MP#')) {
|
||
url = url.replace('#MP#', conn.password || '');
|
||
}
|
||
|
||
// Gérer les cas spécifiques des plateformes connues
|
||
if (url === 'pro.mondocteur.fr' || url.includes('mondocteur.fr')) {
|
||
if (!url.startsWith('http')) {
|
||
url = 'https://pro.mondocteur.fr/backoffice.do';
|
||
}
|
||
} else if (url === 'pro.doctolib.fr' || url.includes('doctolib.fr')) {
|
||
if (!url.startsWith('http')) {
|
||
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}`,
|
||
url: url,
|
||
couleur: getColorForIndex(index),
|
||
credentials: {
|
||
username: conn.accessCode,
|
||
password: conn.password
|
||
},
|
||
queueName: conn.queueName // Garder pour le mapping avec les événements IPBX
|
||
};
|
||
});
|
||
}
|
||
|
||
// 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
|
||
ipcMain.handle('logout', async () => {
|
||
if (currentAgent && signalRConnection && signalRStatus === 'connected') {
|
||
try {
|
||
// Appeler SignalR pour la déconnexion
|
||
logSignalR('📡 Invocation SignalR: AgentLogoff', {
|
||
method: 'AgentLogoff',
|
||
accessCode: currentAgent.accessCode
|
||
});
|
||
await signalRConnection.invoke('AgentLogoff', currentAgent.accessCode);
|
||
console.log('Agent déconnecté du serveur SignalR');
|
||
logSignalR('👋 Agent déconnecté avec succès', {
|
||
accessCode: currentAgent.accessCode,
|
||
name: currentAgent.name
|
||
});
|
||
} catch (error) {
|
||
console.error('Erreur lors de la déconnexion SignalR:', 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 - Gestion Centralisée des Plannings');
|
||
}
|
||
|
||
return { success: true };
|
||
});
|
||
|
||
// Handler pour quitter l'application proprement
|
||
ipcMain.handle('quit-app', async () => {
|
||
// Fermer la connexion SignalR si elle existe
|
||
if (signalRConnection) {
|
||
try {
|
||
await signalRConnection.stop();
|
||
console.log('Connexion SignalR fermée');
|
||
} catch (error) {
|
||
console.error('Erreur lors de la fermeture de SignalR:', error);
|
||
}
|
||
}
|
||
|
||
// 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,
|
||
terminal: currentTerminal
|
||
};
|
||
});
|
||
|
||
// 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', () => {
|
||
return config.cti.appelSimules.map(appel => {
|
||
const centre = config.centres.find(c => c.id === appel.centreId);
|
||
return {
|
||
...appel,
|
||
centreNom: centre ? centre.nom : 'Centre inconnu'
|
||
};
|
||
});
|
||
});
|
||
|
||
// Sauvegarder les notes de l'agent (un seul fichier par 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,
|
||
currentNote: noteData.content,
|
||
lastModified: new Date().toISOString(),
|
||
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({
|
||
content: existingData.currentNote,
|
||
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');
|
||
return JSON.parse(data);
|
||
} catch (error) {
|
||
console.error('Erreur lecture notes:', error);
|
||
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)) {
|
||
const data = fs.readFileSync(historyFile, 'utf8');
|
||
return JSON.parse(data);
|
||
}
|
||
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';
|
||
}); |