Add RestSocketAdapter that uses: - REST API for actions (login, logout, terminals) - Socket.IO for real-time events (IpbxEvent) ConnectionManager now tries SignalR first (.NET server), then falls back to REST+SocketIO (Python server). This enables the client to work with both servers during migration.
286 lines
9.6 KiB
JavaScript
286 lines
9.6 KiB
JavaScript
/**
|
|
* Adaptateur REST + Socket.IO pour le serveur Python
|
|
*
|
|
* Utilise REST pour les actions (login, logout, terminaux)
|
|
* et Socket.IO pour les événements temps réel (IpbxEvent)
|
|
*
|
|
* Émule l'API SignalR pour compatibilité avec le code existant
|
|
*/
|
|
|
|
const io = require('socket.io-client');
|
|
|
|
class RestSocketAdapter {
|
|
constructor(serverUrl, options = {}) {
|
|
// Nettoyer l'URL
|
|
this.serverUrl = serverUrl;
|
|
if (this.serverUrl.includes('/signalR')) {
|
|
this.serverUrl = this.serverUrl.replace('/signalR', '');
|
|
}
|
|
if (!this.serverUrl.startsWith('http://') && !this.serverUrl.startsWith('https://')) {
|
|
this.serverUrl = `http://${this.serverUrl}`;
|
|
}
|
|
|
|
this.socket = null;
|
|
this.connected = false;
|
|
this.handlers = new Map();
|
|
this.options = options;
|
|
|
|
// Token JWT pour les requêtes authentifiées
|
|
this.authToken = null;
|
|
this.currentAgent = null;
|
|
}
|
|
|
|
/**
|
|
* Émule connection.start() de SignalR
|
|
* Établit la connexion Socket.IO pour les événements
|
|
*/
|
|
async start() {
|
|
return new Promise((resolve, reject) => {
|
|
console.log(`[RestSocketAdapter] Connexion à ${this.serverUrl}...`);
|
|
|
|
// Vérifier d'abord que le serveur Python répond
|
|
fetch(`${this.serverUrl}/health`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`Health check failed: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(health => {
|
|
console.log(`[RestSocketAdapter] Serveur Python détecté:`, health);
|
|
|
|
// Connecter Socket.IO pour les événements temps réel
|
|
this.socket = io(this.serverUrl, {
|
|
path: '/socket.io/',
|
|
transports: ['websocket', 'polling'],
|
|
reconnection: true,
|
|
reconnectionAttempts: 5,
|
|
reconnectionDelay: 2000,
|
|
...this.options
|
|
});
|
|
|
|
this.socket.on('connect', () => {
|
|
console.log('[RestSocketAdapter] Socket.IO connecté');
|
|
this.connected = true;
|
|
resolve();
|
|
});
|
|
|
|
this.socket.on('connect_error', (error) => {
|
|
console.error('[RestSocketAdapter] Erreur Socket.IO:', error.message);
|
|
// On reste connecté même si Socket.IO échoue (REST fonctionne)
|
|
if (!this.connected) {
|
|
this.connected = true;
|
|
console.log('[RestSocketAdapter] Mode REST seul (Socket.IO indisponible)');
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
this.socket.on('disconnect', (reason) => {
|
|
console.log('[RestSocketAdapter] Socket.IO déconnecté:', reason);
|
|
});
|
|
|
|
// Configurer les handlers d'événements existants
|
|
this.handlers.forEach((handler, eventName) => {
|
|
this.socket.on(eventName, (...args) => {
|
|
console.log(`[RestSocketAdapter] Événement reçu: ${eventName}`, args);
|
|
handler(eventName, args);
|
|
});
|
|
});
|
|
|
|
// Timeout de connexion
|
|
setTimeout(() => {
|
|
if (!this.connected) {
|
|
reject(new Error('Timeout de connexion au serveur Python'));
|
|
}
|
|
}, 10000);
|
|
})
|
|
.catch(error => {
|
|
console.error('[RestSocketAdapter] Serveur Python non disponible:', error.message);
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Émule connection.stop() de SignalR
|
|
*/
|
|
async stop() {
|
|
if (this.socket) {
|
|
this.socket.disconnect();
|
|
}
|
|
this.connected = false;
|
|
this.authToken = null;
|
|
this.currentAgent = null;
|
|
console.log('[RestSocketAdapter] Déconnecté');
|
|
}
|
|
|
|
/**
|
|
* Émule connection.invoke() de SignalR
|
|
* Redirige vers les endpoints REST appropriés
|
|
*/
|
|
async invoke(methodName, ...args) {
|
|
if (!this.connected) {
|
|
throw new Error('Non connecté au serveur');
|
|
}
|
|
|
|
console.log(`[RestSocketAdapter] invoke: ${methodName}`, args);
|
|
|
|
switch (methodName) {
|
|
case 'GetTerminalListByServiceProvider':
|
|
return this._getTerminalList(args[0]);
|
|
|
|
case 'AgentLogin':
|
|
return this._agentLogin(args[0], args[1], args[2]);
|
|
|
|
case 'AgentLogoff':
|
|
return this._agentLogoff(args[0]);
|
|
|
|
default:
|
|
console.warn(`[RestSocketAdapter] Méthode non implémentée: ${methodName}`);
|
|
throw new Error(`Méthode non supportée: ${methodName}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/terminals/{serviceProvider}
|
|
*/
|
|
async _getTerminalList(serviceProvider) {
|
|
const response = await fetch(
|
|
`${this.serverUrl}/api/v1/terminals/${encodeURIComponent(serviceProvider)}`
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ detail: response.statusText }));
|
|
throw new Error(error.detail || 'Erreur récupération terminaux');
|
|
}
|
|
|
|
const data = await response.json();
|
|
// Retourner juste la liste des numéros de terminaux (format SignalR)
|
|
return data.terminals.map(t => t.number || t);
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/auth/login
|
|
*/
|
|
async _agentLogin(email, password, terminal) {
|
|
const response = await fetch(`${this.serverUrl}/api/v1/auth/login`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
access_code: email,
|
|
password: password,
|
|
terminal: terminal,
|
|
force_disconnect: false
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ detail: response.statusText }));
|
|
throw new Error(error.detail || 'Erreur de connexion');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Stocker le token si présent
|
|
if (data.token) {
|
|
this.authToken = data.token;
|
|
}
|
|
this.currentAgent = data;
|
|
|
|
// Enregistrer le terminal sur Socket.IO pour recevoir les événements
|
|
if (this.socket && this.socket.connected) {
|
|
this.socket.emit('register', {
|
|
terminal: terminal,
|
|
agentId: data.accessCode || data.access_code
|
|
});
|
|
}
|
|
|
|
// Retourner au format attendu par le client (compatible SignalR)
|
|
return {
|
|
accessCode: data.accessCode || data.access_code,
|
|
firstName: data.firstName || data.first_name,
|
|
lastName: data.lastName || data.last_name,
|
|
connList: data.connList || data.conn_list || []
|
|
};
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/auth/logout
|
|
*/
|
|
async _agentLogoff(accessCode) {
|
|
// Désenregistrer du Socket.IO
|
|
if (this.socket && this.socket.connected) {
|
|
this.socket.emit('unregister', { terminal: this.currentAgent?.terminal });
|
|
}
|
|
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
// Ajouter le token si disponible
|
|
if (this.authToken) {
|
|
headers['Authorization'] = `Bearer ${this.authToken}`;
|
|
}
|
|
|
|
const response = await fetch(`${this.serverUrl}/api/v1/auth/logout`, {
|
|
method: 'POST',
|
|
headers: headers,
|
|
body: JSON.stringify({ access_code: accessCode })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ detail: response.statusText }));
|
|
throw new Error(error.detail || 'Erreur de déconnexion');
|
|
}
|
|
|
|
this.authToken = null;
|
|
this.currentAgent = null;
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
/**
|
|
* Émule connection.on() de SignalR
|
|
*/
|
|
on(eventName, handler) {
|
|
console.log(`[RestSocketAdapter] Enregistrement handler: ${eventName}`);
|
|
this.handlers.set(eventName, handler);
|
|
|
|
// Si Socket.IO est déjà connecté, ajouter le handler
|
|
if (this.socket) {
|
|
this.socket.on(eventName, (...args) => {
|
|
console.log(`[RestSocketAdapter] Événement reçu: ${eventName}`, args);
|
|
handler(eventName, args);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Émule connection.off() de SignalR
|
|
*/
|
|
off(eventName) {
|
|
this.handlers.delete(eventName);
|
|
if (this.socket) {
|
|
this.socket.off(eventName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* État de la connexion (émule SignalR HubConnectionState)
|
|
*/
|
|
get state() {
|
|
return this.connected ? 'Connected' : 'Disconnected';
|
|
}
|
|
|
|
/**
|
|
* Identifie ce type d'adaptateur
|
|
*/
|
|
get isRestSocketAdapter() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
module.exports = RestSocketAdapter;
|