refactor: Improve WebSocket support with implementation and documentation improvements

- Add create_websocket() method in client for simplified WebSocket management
- Expand WebSocket service with set_websocket() and find_websocket() methods
- Improve event handling and logging in WebSocketManager
- Add WebSocketConfig and WebSocketInfo models for event configuration
This commit is contained in:
Davidson Gomes 2025-03-06 09:47:44 -03:00
parent b0131a1141
commit 15e229cfb4
3 changed files with 127 additions and 44 deletions

View File

@ -9,6 +9,8 @@ from .services.chat import ChatService
from .services.label import LabelService from .services.label import LabelService
from .services.profile import ProfileService from .services.profile import ProfileService
from .services.group import GroupService from .services.group import GroupService
from .services.websocket import WebSocketService, WebSocketManager
class EvolutionClient: class EvolutionClient:
""" """
Cliente para interagir com a API Evolution. Cliente para interagir com a API Evolution.
@ -29,6 +31,7 @@ class EvolutionClient:
self.label = LabelService(self) self.label = LabelService(self)
self.profile = ProfileService(self) self.profile = ProfileService(self)
self.group = GroupService(self) self.group = GroupService(self)
self.websocket = WebSocketService(self)
def _get_headers(self, instance_token: str = None): def _get_headers(self, instance_token: str = None):
return { return {
@ -112,3 +115,24 @@ class EvolutionClient:
url = self._get_full_url(endpoint) url = self._get_full_url(endpoint)
response = requests.delete(url, headers=self._get_headers(instance_token)) response = requests.delete(url, headers=self._get_headers(instance_token))
return self._handle_response(response) return self._handle_response(response)
def create_websocket(self, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0) -> WebSocketManager:
"""
Create a WebSocket manager for the specified instance.
Args:
instance_id (str): The instance ID
api_token (str): The API token
max_retries (int): Maximum number of reconnection attempts
retry_delay (float): Initial delay between attempts in seconds
Returns:
WebSocketManager: The WebSocket manager instance
"""
return WebSocketManager(
base_url=self.base_url,
instance_id=instance_id,
api_token=api_token,
max_retries=max_retries,
retry_delay=retry_delay
)

View File

@ -0,0 +1,20 @@
from typing import List, Optional
from dataclasses import dataclass
@dataclass
class WebSocketConfig:
enabled: bool
events: List[str]
def __init__(self, enabled: bool, events: List[str]):
self.enabled = enabled
self.events = events
@dataclass
class WebSocketInfo:
enabled: bool
events: List[str]
def __init__(self, **kwargs):
self.enabled = kwargs.get('enabled', False)
self.events = kwargs.get('events', [])

View File

@ -1,21 +1,60 @@
import socketio import socketio
from typing import Callable, Dict, Any from typing import Callable, Dict, Any
import logging import logging
import ssl
import time import time
from typing import Optional from typing import Optional
from ..models.websocket import WebSocketConfig, WebSocketInfo
class WebSocketService:
def __init__(self, client):
self.client = client
def set_websocket(self, instance_id: str, config: WebSocketConfig, instance_token: str):
"""
Configure WebSocket settings for an instance
Args:
instance_id (str): The instance ID
config (WebSocketConfig): The WebSocket configuration
instance_token (str): The instance token
Returns:
dict: The response from the API
"""
return self.client.post(
f'websocket/set/{instance_id}',
data=config.__dict__,
instance_token=instance_token
)
def find_websocket(self, instance_id: str, instance_token: str) -> WebSocketInfo:
"""
Get WebSocket settings for an instance
Args:
instance_id (str): The instance ID
instance_token (str): The instance token
Returns:
WebSocketInfo: The WebSocket information
"""
response = self.client.get(
f'websocket/find/{instance_id}',
instance_token=instance_token
)
return WebSocketInfo(**response)
class WebSocketManager: class WebSocketManager:
def __init__(self, base_url: str, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0): def __init__(self, base_url: str, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0):
""" """
Inicializa o gerenciador de WebSocket Initialize the WebSocket manager
Args: Args:
base_url (str): URL base da API base_url (str): Base URL of the API
instance_id (str): ID da instância instance_id (str): Instance ID
api_token (str): Token de autenticação da API api_token (str): API authentication token
max_retries (int): Número máximo de tentativas de reconexão max_retries (int): Maximum number of reconnection attempts
retry_delay (float): Delay inicial entre tentativas em segundos retry_delay (float): Initial delay between attempts in seconds
""" """
self.base_url = base_url.rstrip('/') self.base_url = base_url.rstrip('/')
self.instance_id = instance_id self.instance_id = instance_id
@ -25,85 +64,85 @@ class WebSocketManager:
self.retry_count = 0 self.retry_count = 0
self.should_reconnect = True self.should_reconnect = True
# Configuração do Socket.IO # Socket.IO configuration
self.sio = socketio.Client( self.sio = socketio.Client(
ssl_verify=False, # Para desenvolvimento local ssl_verify=False, # For local development
logger=False, logger=False,
engineio_logger=False, engineio_logger=False,
request_timeout=30 request_timeout=30
) )
# Configura o logger da classe para INFO # Configure class logger to INFO
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.INFO) self.logger.setLevel(logging.INFO)
# Dicionário para armazenar os handlers registrados # Dictionary to store registered handlers
self._handlers = {} self._handlers = {}
# Configuração dos handlers de eventos # Configure event handlers
self.sio.on('connect', self._on_connect) self.sio.on('connect', self._on_connect)
self.sio.on('disconnect', self._on_disconnect) self.sio.on('disconnect', self._on_disconnect)
self.sio.on('error', self._on_error) self.sio.on('error', self._on_error)
# Registra o handler global no namespace específico da instância # Register global handler in instance-specific namespace
self.sio.on('*', self._handle_event, namespace=f'/{self.instance_id}') self.sio.on('*', self._handle_event, namespace=f'/{self.instance_id}')
def _on_connect(self): def _on_connect(self):
"""Handler para evento de conexão""" """Handler for connection event"""
self.logger.info("Socket.IO conectado") self.logger.info("Socket.IO connected")
self.retry_count = 0 # Reseta o contador de retry após conexão bem-sucedida self.retry_count = 0 # Reset retry counter after successful connection
def _on_disconnect(self): def _on_disconnect(self):
"""Handler para evento de desconexão""" """Handler for disconnection event"""
self.logger.warning(f"Socket.IO desconectado. Tentativa {self.retry_count + 1}/{self.max_retries}") self.logger.warning(f"Socket.IO disconnected. Attempt {self.retry_count + 1}/{self.max_retries}")
if self.should_reconnect and self.retry_count < self.max_retries: if self.should_reconnect and self.retry_count < self.max_retries:
self._attempt_reconnect() self._attempt_reconnect()
else: else:
self.logger.error("Número máximo de tentativas de reconexão atingido") self.logger.error("Maximum number of reconnection attempts reached")
def _on_error(self, error): def _on_error(self, error):
"""Handler para eventos de erro""" """Handler for error events"""
self.logger.error(f"Erro no Socket.IO: {str(error)}", exc_info=True) self.logger.error(f"Socket.IO error: {str(error)}", exc_info=True)
def _attempt_reconnect(self): def _attempt_reconnect(self):
"""Tenta reconectar com backoff exponencial""" """Attempt to reconnect with exponential backoff"""
try: try:
delay = self.retry_delay * (2 ** self.retry_count) # Backoff exponencial delay = self.retry_delay * (2 ** self.retry_count) # Exponential backoff
self.logger.info(f"Tentando reconectar em {delay:.2f} segundos...") self.logger.info(f"Attempting to reconnect in {delay:.2f} seconds...")
time.sleep(delay) time.sleep(delay)
self.connect() self.connect()
self.retry_count += 1 self.retry_count += 1
except Exception as e: except Exception as e:
self.logger.error(f"Erro durante tentativa de reconexão: {str(e)}", exc_info=True) self.logger.error(f"Error during reconnection attempt: {str(e)}", exc_info=True)
if self.retry_count < self.max_retries: if self.retry_count < self.max_retries:
self._attempt_reconnect() self._attempt_reconnect()
else: else:
self.logger.error("Todas as tentativas de reconexão falharam") self.logger.error("All reconnection attempts failed")
def _handle_event(self, event, *args): def _handle_event(self, event, *args):
"""Handler global para todos os eventos""" """Global handler for all events"""
# Só processa eventos que foram registrados # Only process registered events
if event in self._handlers: if event in self._handlers:
self.logger.debug(f"Evento recebido no namespace /{self.instance_id}: {event}") self.logger.debug(f"Event received in namespace /{self.instance_id}: {event}")
self.logger.debug(f"Dados do evento: {args}") self.logger.debug(f"Event data: {args}")
try: try:
# Extrai os dados do evento # Extract event data
raw_data = args[0] if args else {} raw_data = args[0] if args else {}
# Garante que estamos passando o objeto correto para o callback # Ensure we're passing the correct object to the callback
if isinstance(raw_data, dict): if isinstance(raw_data, dict):
self.logger.debug(f"Chamando handler para {event} com dados: {raw_data}") self.logger.debug(f"Calling handler for {event} with data: {raw_data}")
self._handlers[event](raw_data) self._handlers[event](raw_data)
else: else:
self.logger.error(f"Dados inválidos recebidos para evento {event}: {raw_data}") self.logger.error(f"Invalid data received for event {event}: {raw_data}")
except Exception as e: except Exception as e:
self.logger.error(f"Erro ao processar evento {event}: {str(e)}", exc_info=True) self.logger.error(f"Error processing event {event}: {str(e)}", exc_info=True)
def connect(self): def connect(self):
"""Conecta ao servidor Socket.IO""" """Connect to Socket.IO server"""
try: try:
# Conecta apenas ao namespace da instância com o header de autenticação # Connect only to instance namespace with authentication header
self.sio.connect( self.sio.connect(
f"{self.base_url}?apikey={self.api_token}", f"{self.base_url}?apikey={self.api_token}",
transports=['websocket'], transports=['websocket'],
@ -111,25 +150,25 @@ class WebSocketManager:
wait_timeout=30 wait_timeout=30
) )
# Entra na sala específica da instância # Join instance-specific room
self.sio.emit('subscribe', {'instance': self.instance_id}, namespace=f'/{self.instance_id}') self.sio.emit('subscribe', {'instance': self.instance_id}, namespace=f'/{self.instance_id}')
except Exception as e: except Exception as e:
self.logger.error(f"Erro ao conectar ao Socket.IO: {str(e)}", exc_info=True) self.logger.error(f"Error connecting to Socket.IO: {str(e)}", exc_info=True)
raise raise
def disconnect(self): def disconnect(self):
"""Desconecta do servidor Socket.IO""" """Disconnect from Socket.IO server"""
self.should_reconnect = False # Impede tentativas de reconexão self.should_reconnect = False # Prevent reconnection attempts
if self.sio.connected: if self.sio.connected:
self.sio.disconnect() self.sio.disconnect()
def on(self, event: str, callback: Callable): def on(self, event: str, callback: Callable):
""" """
Registra um callback para um evento específico Register a callback for a specific event
Args: Args:
event (str): Nome do evento event (str): Event name
callback (Callable): Função a ser chamada quando o evento ocorrer callback (Callable): Function to be called when the event occurs
""" """
self._handlers[event] = callback self._handlers[event] = callback