backend
Messagerie temps réel avec les gateways NestJS
Messagerie temps réel avec les gateways NestJS
La messagerie entre clients et organisations a besoin d'une livraison sans polling. Socket.io me donne une connexion WebSocket avec repli automatique en long-polling, plus une abstraction de room par-dessus.
@WebSocketGateway({ namespace: "messaging" })
export class MessagingGateway {
@SubscribeMessage("joinConversation")
handleJoin(client: Socket, conversationId: string) {
client.join(`conversation:${conversationId}`);
}
// émettre vers la room n'atteint que les sockets qui l'ont rejointe
this.server.to(`conversation:${conversationId}`).emit("newMessage", message);
}
Une room n'est que le mécanisme de regroupement de Socket.io. Chaque socket connecté à une conversation rejoint sa room ; émettre vers la room n'atteint que ces sockets. Ça se mappe proprement sur conversation_participants — seuls les participants reçoivent les événements.
L'authentification sur un WebSocket est différente. Le handshake ne porte pas d'en-têtes comme une requête normale, donc l'authentification se fait via un paramètre de requête ou une charge d'auth à la connexion, ou un cookie/jeton validé pendant le handshake, avec l'utilisateur résolu attaché au socket pour chaque handler ultérieur. Rate ça et soit les sockets sont non authentifiés, soit chaque handler réimplémente sa propre vérification. (J'ai aussi eu une course ici une fois : handleConnection est async, et un client rapide pouvait atteindre handleJoin avant que le jeton ne finisse de se résoudre. Le correctif était de re-dériver le jeton du handshake dans handleJoin si la valeur en cache n'était pas encore là.)
L'adaptateur Redis cesse d'être optionnel à l'échelle réelle. L'appartenance aux rooms vit dans la mémoire d'un seul processus par défaut. Avec plus d'une instance, server.to(room).emit(...) n'atteint que les sockets sur cette même instance, et rien n'est levé pour dire que le message est parti dans le vide. L'adaptateur fait publier par chaque instance via un canal pub/sub partagé.
Une note de débogage qui m'a coûté du temps une fois : les sockets se comportent très différemment sous le hot-reload en dev local. Un redémarrage de conteneur back-end lâche chaque connexion ouverte, et le front-end a besoin d'une logique de reconnexion qui rejoint réellement les rooms, pas juste rétablit la connexion brute. Une reconnexion silencieuse qui oublie de rejoindre ressemble exactement à « le temps réel a cessé de marcher » même si la connexion elle-même va bien.