Socket.IO 4.8
Aperçu
Cette compétence contient les règles Socket.IO 4.8 qui sont faciles à mal comprendre : un socket.id éphémère utilisé comme clé utilisateur, des écouteurs enregistrés deux fois, des accusés de réception dont la forme du callback change avec timeout(), un CORS qui ne protège pas WebSocket, des sessions persistantes oubliées derrière un équilibreur de charge, et des API v2 ou v3 qui ne fonctionnent plus. Chaque affirmation porte l'URL de la page dont elle est issue. Les fichiers de référence contiennent le détail par domaine.
Version couverte
Socket.IO 4.8 (serveur socket.io, client socket.io-client), documentation v4. Les clients JS 3.x et 4.x communiquent avec les serveurs 3.x et 4.x. https://socket.io/docs/v4/client-installation/#version-compatibility
Démarrage rapide
- Attachez-vous au serveur HTTP que vous créez et appelez
listendessus, jamais sur l'application Express :app.listen()crée un autre serveur HTTP auquel Socket.IO n'est pas attaché. https://socket.io/docs/v4/server-initialization/#with-express
import express from "express";
import { createServer } from "node:http";
import { Server } from "socket.io";
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, { cors: { origin: ["https://example.com"] } });
io.on("connection", (socket) => {
socket.on("hello", (arg, callback) => callback("got it"));
});
httpServer.listen(3000);
https://socket.io/docs/v4/server-initialization/#with-express
- Client :
io()sur la même origine,io("https://server-domain.com")en inter-origine (CORS doit être activé sur le serveur). Importez avecimport { io } from "socket.io-client". https://socket.io/docs/v4/client-initialization/#from-a-different-domain
import { io } from "socket.io-client";
const socket = io("https://server-domain.com", { auth: { token: "abc" } });
const answer = await socket.timeout(5000).emitWithAck("hello", "world");
https://socket.io/docs/v4/client-api/#socketemitwithackeventname-args
- Les types sont livrés avec
socket.io-client. N'installez pas@types/socket.io-client, cela entre en conflit avec les typages intégrés. https://socket.io/docs/v4/client-installation/#from-npm
Règles et pièges
Connexion et identité
- Socket.IO n'est pas une implémentation WebSocket : un client WebSocket simple ne peut pas atteindre un serveur Socket.IO, et le client Socket.IO ne peut pas atteindre un serveur WebSocket simple. https://socket.io/docs/v4/#what-socketio-is-not
- À moins que la récupération d'état de connexion ne soit activée,
socket.idchange à chaque reconnexion et rechargement de page, diffère par onglet et n'a pas de file d'attente de messages. Identifiez les utilisateurs avec votre propre ID de session envoyé dans un cookie ou dansauth, et atteignez chaque onglet d'un utilisateur via une salle nommée d'après l'ID utilisateur. https://socket.io/docs/v4/server-socket-instance/#socketid
io.on("connection", async (socket) => {
const userId = await computeUserIdFromHeaders(socket.handshake.headers);
socket.join(userId);
io.to(userId).emit("hi");
});
https://socket.io/docs/v4/rooms/#sample-use-cases
- Authentifiez-vous dans un middleware de connexion avec
socket.handshake.auth. Appeleznext()dans chaque chemin de code, sinon la connexion reste en attente jusqu'à sa fermeture après un délai d'attente.next(err)atteint le client sous la formeconnect_error, avecerr.datapour les détails. https://socket.io/docs/v4/middlewares/
io.use((socket, next) => {
if (isValid(socket.handshake.auth.token)) return next();
const err = new Error("not authorized");
err.data = { content: "Please retry later" };
next(err);
});
https://socket.io/docs/v4/middlewares/#handling-middleware-error
- Une connexion refusée par un middleware n'est pas réessayée. Dans
connect_error,socket.active === falsesignifie que la connexion a été refusée par le serveur et le client doit appelersocket.connect()lui-même pour se reconnecter. https://socket.io/docs/v4/client-socket-instance/#connect_error - Enregistrez les écouteurs client au niveau supérieur, jamais à l'intérieur du gestionnaire
connect:connectse déclenche à nouveau à chaque reconnexion et ajoute un écouteur en double à chaque fois. https://socket.io/docs/v4/client-socket-instance/#connect - Sur le serveur, enregistrez
socket.on(...)de manière synchrone dans le gestionnaireconnection, avant toutawait, sinon les premiers paquets n'atteignent aucun gestionnaire. https://socket.io/docs/v4/troubleshooting-connection-issues/#delayed-event-handler-registration - Les middlewares Express vont dans
io.engine.use(...), pas dansio.use(...). https://socket.io/docs/v4/middlewares/#compatibility-with-express-middleware
Événements et accusés de réception
- Passez les objets tels quels. Ne faites jamais
JSON.stringify()une charge utile.Datearrive sous forme de chaîne,MapetSetdoivent être convertis manuellement. https://socket.io/docs/v4/emitting-events/#basic-emit - Le callback d'accusé de réception est le dernier argument. Sans
timeout(), il reçoit(response), avectimeout(ms)il reçoit(err, response). https://socket.io/docs/v4/emitting-events/#with-timeout - Préférez
await socket.timeout(ms).emitWithAck(...)dans un bloctry: il rejette en cas de délai d'attente dépassé. Le côté récepteur appelle son callback avec 1 argument. https://socket.io/docs/v4/client-api/#socketemitwithackeventname-args - Une diffusion avec accusé de réception nécessite
timeout()et renvoie 1 réponse par client. https://socket.io/docs/v4/server-api/#serveremiteventname-args
const responses = await io.timeout(10000).emitWithAck("some-event");
https://socket.io/docs/v4/server-api/#serveremitwithackeventname-args
- Les écouteurs n'ont pas de gestion d'erreur intégrée et aucune validation. Attrapez les erreurs dans chaque gestionnaire, validez les charges utiles avec une bibliothèque de schémas et vérifiez
typeof callback === "function"avant de l'utiliser. https://socket.io/docs/v4/listening-to-events/#validation - N'utilisez jamais
connect,connect_error,disconnect,disconnecting,newListenerouremoveListenercomme un événement d'application : émettre un nom réservé tel quesocket.emit("disconnecting")lève une exception. https://socket.io/docs/v4/emit-cheatsheet/
Diffusion et salles
socket.to(room)etsocket.broadcastexcluent l'expéditeur,io.to(room)ne le fait pas.socket.to(socket.id).emit()n'envoie rien : utilisezio.to(socket.id).emit(). https://socket.io/docs/v4/server-api/#sockettoroom- Les appels
to()chaînés sont une union, chaque socket reçoit l'événement une fois. https://socket.io/docs/v4/rooms/#joining-and-leaving - Chaque modificateur de diffusion renvoie un nouvel opérateur immuable : conservez-le dans une variable au lieu d'appeler
io.to()puisio.emit(). https://socket.io/docs/v4/migrating-from-3-x-to-4-0/#ioto-is-now-immutable - Les noms de salle ne sont pas convertis :
socket.join(42)ne correspond pas àio.to("42"). https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#room-names-are-not-coerced-to-string-anymore - Les salles sont quittées automatiquement à la déconnexion. Lisez-les dans
disconnecting, pas dansdisconnectoùsocket.roomsest vide. https://socket.io/docs/v4/rooms/#disconnection - Lisez l'adaptateur avec
io.of("/").adapter: la documentation prévient queio.adapter()ne fonctionnera pas pour cela, c'est le mutateur. https://socket.io/docs/v4/adapter/#api
Livraison
- La garantie par défaut est au plus une fois et le serveur ne stocke aucun message : un client déconnecté rate définitivement les diffusions. Rejouez à partir d'une base de données avec un décalage envoyé dans
auth, ou activez la récupération d'état de connexion. https://socket.io/docs/v4/delivery-guarantees#at-most-once - Le client met en mémoire tampon les émissions hors ligne et les vide à la reconnexion. Vérifiez
socket.connectedou utilisezsocket.volatile.emit(...)pour les données qui peuvent être abandonnées. https://socket.io/docs/v4/client-offline-behavior/#buffered-events - Client vers serveur au moins une fois :
io({ retries: 3, ackTimeout: 10000 }). Pour exactement une fois, stockez un ID généré par le client sous une contrainte d'unicité. Accusez réception en cas de succès et en cas de doublon, mais n'accusez pas réception en cas d'autre erreur, afin que le client expire et réessaie. https://socket.io/docs/v4/tutorial/step-8 - La récupération d'état de connexion est optionnelle, nécessite un
maxDisconnectionDurationborné et peut échouer : branchez sursocket.recovered. L'adaptateur Redis (Pub/Sub) ne la prend pas en charge, l'adaptateur Redis Streams le fait. https://socket.io/docs/v4/connection-state-recovery
const io = new Server(httpServer, {
connectionStateRecovery: { maxDisconnectionDuration: 2 * 60 * 1000, skipMiddlewares: false },
});
https://socket.io/docs/v4/connection-state-recovery#usage
CORS et déploiement
- CORS est désactivé par défaut. L'option est
cors.origin, jamaisorigins.origin: "*"aveccredentials: trueéchoue dans le navigateur : énumérez les origines explicites. https://socket.io/docs/v4/server-options/#cors - CORS ne couvre que les navigateurs sur HTTP long-polling, pas WebSocket. Restreignez qui se connecte avec
allowRequest. https://socket.io/docs/v4/handling-cors/ - Un proxy doit transférer les en-têtes
UpgradeetConnection, et son délai d'attente de lecture doit dépasserpingInterval + pingTimeout(45 secondes par défaut), sinon les clients voienttransport close. https://socket.io/docs/v4/reverse-proxy/#nginx - Avec plusieurs serveurs et le HTTP long-polling activé, les sessions persistantes sont toujours nécessaires quel que soit l'adaptateur (les alternatives sont la désactivation du long-polling ou l'équilibrage de charge côté client). https://socket.io/docs/v4/using-multiple-nodes/ et https://socket.io/docs/v4/adapter/
- Sans sessions persistantes, les clients reçoivent des réponses HTTP 400 avec
{"code":1,"message":"Session ID unknown"}. https://socket.io/docs/v4/troubleshooting-connection-issues/#you-didnt-enable-sticky-sessions-in-a-multi-server-setup path(par défaut/socket.io/) doit correspondre des deux côtés.io("/my-path/")sélectionne un espace de noms, pas un chemin. https://socket.io/docs/v4/troubleshooting-connection-issues/#the-request-path-does-not-match-on-both-sides- Fermez avec
io.close(), qui ferme également le serveur HTTP. https://socket.io/docs/v4/server-api/#serverclosecallback
Fichiers de référence
| Fichier | À lire pour |
|---|---|
| references/server-setup.md | Installation, initialisation avec Express, Koa, Fastify, Hono, HTTPS, HTTP/2, Bun, uWebSockets.js, options du serveur et valeurs par défaut, regroupement, disposition du gestionnaire |
| references/middleware-and-auth.md | Middlewares de connexion, socket et moteur, informations d'identification, identité utilisateur, hooks d'en-tête, allowRequest, certificats clients |
| references/cors-and-proxies.md | Options et erreurs CORS, cookies, nginx, Apache, Caddy, http-proxy, équilibreurs de charge cloud, partage d'un port avec ws |
| references/server-api.md | Instances Server et Socket, espaces de noms sur io, méthodes utilitaires, serverSideEmit, raisons de déconnexion, événements du moteur et codes d'erreur, fermeture |
| references/client.md | Bundles, initialisation, événements Socket, connect_error et disconnect, Manager et multiplexage, toutes les options client, bundlers |
| references/events-rooms-namespaces.md | Sérialisation, accusés de réception, écouteurs fourre-tout, validation, gestion d'erreurs, diffusion, salles, espaces de noms dynamiques |
| references/delivery-and-recovery.md | Garanties de livraison, nouvelles tentatives, exactement une fois, rejeu de décalage, récupération d'état de connexion |
| references/multiple-nodes.md | Sessions persistantes, configurations de proxy pour plusieurs nœuds, alternatives, cluster Node.js, PM2 |
| references/adapters.md | Adaptateurs Redis, Redis shardé, Redis Streams, Postgres, MongoDB, Cluster, AWS SQS, Azure, GCP, matrice de fonctionnalités, émetteurs |
| references/performance-and-operations.md | Modules complémentaires natifs, wsEngine, mémoire, analyseurs personnalisés, limites du système d'exploitation, tests de charge, interface d'administration, logs de débogage |
| references/typescript.md | Interfaces d'événements sur le serveur, le client et les espaces de noms, SocketData, tests typés |
| references/testing-and-troubleshooting.md | Tests d'intégration serveur, tests client avec un socket factice, échecs de connexion, déconnexions, long-polling bloqué |
| references/migrations-and-protocol.md | Changements de v2 à v3 et v3 à v4, mise à niveau progressive avec allowEIO3, protocoles filaires Engine.IO et Socket.IO |
Anciens motifs
| Ancienne utilisation | Remplacement | Source |
|---|---|---|
Option origins ou handlePreflightRequest | cors: { origin: [...] } | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#cors-handling |
io.set("authorization", fn) | io.use((socket, next) => ...) | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#ioset-is-removed |
Jeton dans l'option Socket query | Option auth, lue comme socket.handshake.auth | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Événement client error pour une connexion refusée | connect_error, avec err.message et err.data | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
socket.on("reconnect_attempt") sur le Socket | socket.io.on("reconnect_attempt") sur le Manager | https://socket.io/docs/v4/client-socket-instance/#events |
Événement client pong pour la latence | socket.volatile.emit("ping", cb) sur un intervalle | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/#no-more-pong-event-for-retrieving-latency |
Objet Namespace.connected | Namespace.sockets, une Map | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Object.keys(socket.rooms) | socket.rooms est un Set | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
socket.join(room, cb) | socket.join(room), attendu avec un adaptateur asynchrone | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Namespace.clients(cb) (v2), puis allSockets() (v3, à supprimer dans la prochaine version majeure) | fetchSockets() | https://socket.io/docs/v4/server-api/#namespaceallsockets |
socket.binary(false) | Un parser personnalisé | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
socket.emit("a").emit("b") | Appels séparés, emit() renvoie true | https://socket.io/docs/v4/migrating-from-2-x-to-3-0/ |
Import par défaut de socket.io ou socket.io-client | import { ... } from ... nommé | https://socket.io/docs/v4/migrating-from-2-x-to-4-0/ |