package dev.lions.services; import dev.lions.dtos.NotificationDTO; import dev.lions.exceptions.WebSocketException; import jakarta.enterprise.context.ApplicationScoped; import jakarta.websocket.*; import jakarta.websocket.server.ServerEndpoint; import lombok.extern.slf4j.Slf4j; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Service gérant les communications WebSocket de l'application. * Cette classe assure la gestion des connexions temps réel et la diffusion * des notifications aux clients connectés. */ @Slf4j @ApplicationScoped @ServerEndpoint("/ws/notifications") public class WebSocketService { private static final Map sessions = new ConcurrentHashMap<>(); private static final int MAX_MESSAGE_SIZE = 8192; private static final long IDLE_TIMEOUT = 300000; // 5 minutes /** * Gère l'ouverture d'une nouvelle connexion WebSocket. * * @param session Session WebSocket ouverte */ @OnOpen public void onOpen(Session session) { log.info("Nouvelle connexion WebSocket établie : {}", session.getId()); try { configureSession(session); sessions.put(session.getId(), session); sendWelcomeMessage(session); } catch (Exception e) { log.error("Erreur lors de l'initialisation de la session WebSocket", e); closeSession(session, "Erreur d'initialisation"); } } /** * Configure une nouvelle session WebSocket. */ private void configureSession(Session session) { session.setMaxIdleTimeout(IDLE_TIMEOUT); session.setMaxTextMessageBufferSize(MAX_MESSAGE_SIZE); session.setMaxBinaryMessageBufferSize(MAX_MESSAGE_SIZE); } /** * Envoie un message de bienvenue au client connecté. */ private void sendWelcomeMessage(Session session) { try { String message = "{\"type\":\"welcome\",\"message\":\"Connexion établie\"}"; session.getBasicRemote().sendText(message); } catch (Exception e) { log.warn("Impossible d'envoyer le message de bienvenue", e); } } /** * Gère la réception d'un message depuis un client. * * @param message Message reçu * @param session Session WebSocket active */ @OnMessage public void onMessage(String message, Session session) { String sessionId = session.getId(); log.debug("Message reçu de la session {} : {}", sessionId, message); try { validateMessage(message); processMessage(message, session); } catch (Exception e) { log.error("Erreur lors du traitement du message", e); sendErrorResponse(session, "Erreur de traitement du message"); } } /** * Valide le contenu d'un message reçu. */ private void validateMessage(String message) { if (message == null || message.trim().isEmpty()) { throw new WebSocketException("Message vide non autorisé"); } if (message.length() > MAX_MESSAGE_SIZE) { throw new WebSocketException("Message trop long"); } } /** * Traite un message reçu. */ private void processMessage(String message, Session session) { // Implémentation du traitement des messages selon les besoins log.debug("Traitement du message de la session {}", session.getId()); } /** * Gère la fermeture d'une connexion WebSocket. * * @param session Session WebSocket fermée */ @OnClose public void onClose(Session session) { String sessionId = session.getId(); log.info("Fermeture de la connexion WebSocket : {}", sessionId); sessions.remove(sessionId); cleanupSession(session); } /** * Gère les erreurs survenant sur une connexion WebSocket. * * @param session Session WebSocket concernée * @param throwable Erreur survenue */ @OnError public void onError(Session session, Throwable throwable) { String sessionId = session.getId(); log.error("Erreur WebSocket sur la session {} : {}", sessionId, throwable.getMessage(), throwable); closeSession(session, "Erreur interne"); } /** * Diffuse une notification à tous les clients connectés. * * @param notification Notification à diffuser */ public void broadcastToAdmins(NotificationDTO notification) { log.debug("Diffusion d'une notification à {} clients", sessions.size()); String message = notification.toJson(); sessions.values().forEach(session -> { try { if (session.isOpen()) { session.getBasicRemote().sendText(message); } } catch (Exception e) { log.warn("Erreur lors de l'envoi à la session {}", session.getId(), e); } }); } /** * Envoie une réponse d'erreur à un client. */ private void sendErrorResponse(Session session, String errorMessage) { try { String message = String.format( "{\"type\":\"error\",\"message\":\"%s\"}", errorMessage ); session.getBasicRemote().sendText(message); } catch (Exception e) { log.error("Impossible d'envoyer la réponse d'erreur", e); } } /** * Ferme une session WebSocket. */ private void closeSession(Session session, String reason) { try { session.close(new CloseReason( CloseReason.CloseCodes.NORMAL_CLOSURE, reason )); } catch (Exception e) { log.warn("Erreur lors de la fermeture de la session", e); } } /** * Nettoie les ressources d'une session. */ private void cleanupSession(Session session) { try { session.close(); } catch (Exception e) { log.warn("Erreur lors du nettoyage de la session", e); } } /** * Récupère le nombre de clients connectés. */ public int getConnectedClientsCount() { return sessions.size(); } }