Comprendre le protocole WebSocket de zéro et construire un système de chat complet : connexion, utilisateurs en mémoire, messages privés et groupes — sans base de données.
Un message privé ne rejoint qu'une seule personne : le destinataire. L'expéditeur en reçoit une copie pour confirmation. Personne d'autre ne voit rien.
C'est là que le Map de l'étape précédente prend tout son sens : retrouver le socket d'un utilisateur par son nom se fait en O(1).
dm (direct message)case 'dm':
handleDM(socket, msg)
breakfunction handleDM(socket, msg) {
const to = (msg.to || '').trim()
const content = (msg.content || '').trim()
if (!to || !content) {
socket.send(JSON.stringify({ type: 'error', content: 'Destinataire ou contenu manquant' }))
return
}
if (to === socket.username) {
socket.send(JSON.stringify({ type: 'error', content: 'Impossible de s\'écrire à soi-même' }))
return
}
const target = users.get(to)
if (!target) {
socket.send(JSON.stringify({ type: 'error', content: `${to} n'est pas connecté` }))
return
}
const message = {
type: 'dm',
from: socket.username,
to,
content,
at: new Date().toISOString(),
}
// Envoyer au destinataire
target.send(JSON.stringify(message))
// Envoyer une copie à l'expéditeur (confirmation)
socket.send(JSON.stringify(message))
}ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
switch (msg.type) {
case 'chat':
console.log(`[global] ${msg.from} : ${msg.content}`)
break
case 'dm':
const isMine = msg.from === myUsername
const other = isMine ? msg.to : msg.from
const direction = isMine ? '→' : '←'
console.log(`[dm:${other}] ${direction} ${msg.content}`)
break
case 'system':
console.log(`--- ${msg.content} ---`)
break
case 'error':
console.error('Erreur :', msg.content)
break
}
}// Alice envoie un message privé à Bob
ws.send(JSON.stringify({
type: 'dm',
to: 'bob',
content: 'Salut, c\'est privé !'
}))Alice voit dans sa console :
[dm:bob] → Salut, c'est privé !Bob voit dans sa console :
[dm:bob] ← Salut, c'est privé !Carol, si elle est connectée, ne reçoit rien.
Si Bob se déconnecte entre le moment où Alice tape son message et le moment où elle l'envoie, users.get('bob') renvoie undefined. Le check if (!target) couvre ce cas et renvoie une erreur claire à Alice.
Pour signaler que le destinataire a vu le message, on peut ajouter un acquittement :
case 'dm_read':
const sender = users.get(msg.from)
if (sender) {
sender.send(JSON.stringify({
type: 'dm_read',
by: socket.username,
at: new Date().toISOString(),
}))
}
breakLe client l'envoie dès qu'il affiche le message :
// Quand on reçoit un DM et qu'on le rend visible :
ws.send(JSON.stringify({ type: 'dm_read', from: msg.from }))Les DMs fonctionnent. Prochaine étape : les groupes, où plusieurs utilisateurs peuvent rejoindre un canal partagé.