Notifications temps réel · Reconnexion · File d'attente offline
Utilisez les flèches, cliquez ou glissez pour naviguer
1. Comprendre WebSocket vs HTTP
Protocole persistant, bidirectionnel, temps réel
2. Connexion WebSocket + reconnexion
Exponential backoff, heartbeat ping/pong
3. Système de notifications
Toasts éphémères + badges persistants via Zustand
4. Afficher le partenaire de pair programming
Assignation quotidienne depuis le WebSocket
5. Gérer le mode offline
File d'attente de messages, reprise à la reconnexion
Pair Programming dans Bootcode
Assignation quotidienne, affichage du partenaire via WS
WebSocket natif du navigateur
Connexion, envoi de messages, fermeture propre
Stratégies de reconnexion
Exponential backoff, heartbeat ping/pong
Architecture notification : WS → store → UI
Toast/badge/liste via Zustand dédié
File d'attente offline
Stocker les events quand l'utilisateur est déconnecté
dans l'application Bootcode
👥
Assignation quotidienne
Un partenaire différent chaque jour
⚡
Mis à jour en temps réel
Affiché via WebSocket dès la connexion
🔔
Notifications
Toast + badge non-lu
Composant React — PairPartner.tsx
function PairPartner() {
const { partner } = usePairStore();
if (!partner) return <PartnerSkeleton />;
return (
<div className="flex items-center gap-3 p-4 bg-cyan-50 rounded-lg">
<Avatar src={partner.avatar} />
<div>
<p className="font-semibold">{partner.name}</p>
<p className="text-sm text-cyan-600">Partenaire du jour</p>
</div>
</div>
);
}
💡 Le composant lit depuis un store Zustand — le WS écrit dans le store, pas directement dans le composant
HTTP
🔄 Requête → Réponse → Connexion fermée
📤 Client initie toujours
⏱️ Polling : re-demander toutes les X secondes
💸 Overhead élevé (headers HTTP à chaque fois)
// Polling toutes les 2s (mauvais)
setInterval(() => fetch('/api/partner'), 2000)
WebSocket
🔗 Connexion persistante bidirectionnelle
📨 Serveur peut pousser à tout moment
⚡ Latence très faible
✅ Overhead minimal après handshake initial
// Une seule connexion
new WebSocket('wss://api.bootcode.dev/ws')
// 1. Créer la connexion
const ws = new WebSocket('wss://api.bootcode.dev/ws');
// 2. Connexion ouverte
ws.onopen = () => console.log('Connecté !');
// 3. Recevoir un message
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Reçu :', data);
};
// 4. Envoyer un message
ws.send(JSON.stringify({ type: 'ping' }));
// 5. Connexion fermée
ws.onclose = () => console.log('Déconnecté');
onopen
Connecté
onmessage
Message reçu
send()
Envoyer
onclose
Déconnecté
useEffect(() => {
// Connexion au montage du composant
const ws = new WebSocket('wss://api.bootcode.dev/ws');
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
handleMessage(msg);
};
// ⚠️ Cleanup OBLIGATOIRE
return () => ws.close();
}, []);
⚠️ Sans le return () => ws.close()
La connexion reste ouverte même si le composant est démonté → memory leak + connexions fantômes
Les connexions se coupent — toujours
📶
Réseau instable
WiFi, mobile, tunnel...
😴
Mise en veille
Le PC dort, la connexion meurt
🔄
Redémarrage serveur
Déploiement, maintenance
Attendre de plus en plus longtemps entre chaque tentative
Évite de surcharger le serveur avec des reconnexions en rafale
1s
Tentative 1
2s
Tentative 2
4s
Tentative 3
8s
Tentative 4
30s
Max
const reconnect = (attempt = 0) => {
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
setTimeout(() => connect(attempt + 1), delay);
};
Détecter les connexions "zombies" — ouvertes mais silencieuses
// Envoyer un ping toutes les 30 secondes
const heartbeat = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
// Écouter le pong du serveur
ws.onmessage = (e) => {
const { type } = JSON.parse(e.data);
if (type === 'pong') lastPong = Date.now();
};
💡 Si aucun pong reçu depuis 60s → connexion morte → forcer la reconnexion
function useWebSocket(url: string) {
const [status, setStatus] = useState('connecting');
const wsRef = useRef(null);
const attemptRef = useRef(0);
const connect = useCallback(() => {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => { setStatus('connected'); attemptRef.current = 0; };
ws.onclose = () => { setStatus('reconnecting'); scheduleReconnect(); };
}, [url]);
useEffect(() => { connect(); return () => wsRef.current?.close(); }, []);
return { status, ws: wsRef.current };
}
WS → Store → UI
⚡
WebSocket
Reçoit les events
🗃️
Zustand Store
État centralisé
🎨
Composants UI
Toast · Badge · Liste
Les composants ne parlent jamais directement au WebSocket
interface NotifStore {
notifications: Notification[];
unreadCount: number;
addNotif: (n: Notification) => void;
markAllRead: () => void;
}
const useNotifStore = create<NotifStore>((set) => ({
notifications: [],
unreadCount: 0,
addNotif: (n) => set((s) => ({
notifications: [n, ...s.notifications],
unreadCount: s.unreadCount + 1,
})),
markAllRead: () => set({ unreadCount: 0 }),
}));
Toast
⏱️ Disparaît après 5 secondes
📍 Position fixe (coin bas-droite)
🔔 Pour les events importants
// Disparaît automatiquement
setTimeout(() => removeToast(id), 5000);
Badge
♾️ Persistant jusqu'au clic
🔢 Affiche le nombre non-lu
📬 Sur l'icône cloche
// Réinitialisé au clic
onClick={() => markAllRead()}
// Composant badge notification
const { unreadCount } = useNotifStore();
return <span className="badge">{unreadCount}</span>
function ToastContainer() {
const { toasts, removeToast } = useNotifStore();
return (
<div className="fixed bottom-4 right-4 flex flex-col gap-2">
{toasts.map((toast) => (
<Toast
key={toast.id}
message={toast.message}
onClose={() => removeToast(toast.id)}
/>
))}
</div>
);
}
💡 Ce composant est placé une seule fois dans App.tsx — il est toujours présent dans le DOM
L'app doit fonctionner sans WebSocket
📦
File d'attente
Stocker les events reçus hors-ligne pour les rejouer à la reconnexion
🚦
Indicateur de statut
Afficher clairement : connecté / reconnexion en cours / offline
const messageQueue: string[] = [];
// Envoyer ou mettre en file si déconnecté
function sendMessage(msg: object) {
const payload = JSON.stringify(msg);
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(payload);
} else {
messageQueue.push(payload); // en attente
}
}
// À la reconnexion, vider la file
ws.onopen = () => {
while (messageQueue.length > 0) {
ws.send(messageQueue.shift()!);
}
};
connected
WebSocket actif et opérationnel
reconnecting
Tentative de reconnexion en cours
offline
Hors ligne, messages en file
const statusColors = {
connected: 'bg-green-500',
reconnecting: 'bg-amber-500 animate-spin',
offline: 'bg-red-500',
};
À éviter absolument
Memory Leak
Pas de cleanup → connexions fantômes
DDoS involontaire
Reconnexion sans backoff → rafale de requêtes
DOM direct pour toasts
Manipuler le DOM au lieu d'un store
App cassée sans WS
Ne pas gérer le mode offline
❌ L'erreur
useEffect(() => {
const ws = new WebSocket(url);
// ...
// Pas de return!
}, []);
✅ La solution
useEffect(() => {
const ws = new WebSocket(url);
// ...
return () => ws.close();
}, []);
❌ Reconnexion immédiate
ws.onclose = () => {
connect(); // Immédiat!
};
✅ Exponential backoff
ws.onclose = () => {
const d = Math.min(
1000 * 2 ** attempt, 30000
);
setTimeout(connect, d);
};
❌ DOM direct
ws.onmessage = (e) => {
const el = document.createElement('div');
el.textContent = e.data;
document.body.appendChild(el);
};
✅ Via le store Zustand
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
addNotif({ message: msg.text });
};
⚡ Un seul WebSocket au démarrage de l'app
Pas dans chaque composant — dans un hook partagé ou un store
🔄 Toujours implémenter la reconnexion avec backoff
Les connexions se coupent — 1s, 2s, 4s, 8s… max 30s
🗃️ Les notifications passent par un store Zustand dédié
Jamais directement dans un composant ou dans le DOM
💓 Heartbeat pour détecter les connexions mortes
ping toutes les 30s, reconnexion si aucun pong depuis 60s
📦 File d'attente en mode offline
L'app doit fonctionner sans WS, rejouer les messages à la reconnexion
WebSocket
Connexion persistante · bidirectionnelle · temps réel · un seul connect()
Reconnexion
Exponential backoff · heartbeat ping/pong · jamais en boucle infinie
Notifications
WS → Zustand store → composants · toasts 5s · badges persistants
Offline
File d'attente · indicateur de statut · reprise à la reconnexion
🧹 Toujours fermer le WebSocket dans le useEffect cleanup
WebSocket · Pair Programming · Notifications temps réel
Pratiquez avec le projet Bootcode Hub !
useWebSocket → store → toast → badge → mode offline