Socket.IO v4.x client patterns, connection lifecycle, reconnection, authentication, rooms, namespaces, acknowledgments, binary data, TypeScript integration
Quick Guide: Socket.IO is a protocol layered over WebSocket, not an implementation of it — its client and a plain WebSocket server cannot talk to each other in either direction. What the layer buys is transport fallback, automatic reconnection, rooms, namespaces and acknowledgments, for about 14.5KB gzipped. The facts that change the answer:
authaccepts a function that re-runs on every reconnection,timeoutandackTimeoutare different clocks, andsocket.recovered(v4.6.0+) tells you whether missed events were replayed or a full state refresh is owed.
Detailed Resources:
io(url, options) creates the socket and its Manager together. This is the shape in examples/core.md and covers most apps.new Manager(url) yourself and call manager.socket("/chat") per namespace. The namespaces share a single transport and can each carry their own auth, which is what makes per-namespace authorization possible. See examples/rooms.md.Calling io() more than once against the same URL opens a second transport rather than multiplexing — the Manager is what shares one.
<critical_requirements>
Declare ServerToClientEvents and ClientToServerEvents and type the socket with them. Event names are strings at runtime, so this is what turns "mesage" from a listener that never fires into a compile error.
Pass tokens through the auth option. They travel in the handshake rather than the URL, so they stay out of server logs, browser history and proxy logs — and the function form of auth is re-evaluated on every reconnection, which keeps a refreshed token from going stale.
Remove every listener you add, with the same function reference. socket.off(event, handler) in a useEffect cleanup is what stops handlers stacking up across re-renders and processing each message once per mount.
Handle connect_error and the manager's reconnect_failed. Between them they cover the two failures a user would otherwise experience as a screen that simply stopped updating.
Check socket.recovered after connect (v4.6.0+). It answers whether the server replayed what was missed or the client owes itself a full state refresh.
</critical_requirements>
Auto-detection: socket.io-client, io(), Manager, manager.socket(), socket.emit, socket.on, socket.off, emitWithAck, socket.timeout(), ackTimeout, socket.volatile, socket.recovered, socket.active, connect_error, reconnect_attempt, reconnect_failed, ServerToClientEvents, ClientToServerEvents, autoConnect, reconnectionDelayMax
Applies to:
Handled elsewhere:
auth yields.Socket.IO trades bundle size and protocol compatibility for four things that are otherwise hand-written: transport fallback, reconnection, server-side grouping and acknowledgments.
/chat or /admin explicitly, each with its own middleware and its own auth, and all of them share one transport.CONNECTING -> CONNECTED <-> (events) -> DISCONNECTING -> DISCONNECTED
| |
(error) <- reconnect <- (disconnect)
</philosophy>
<decision_framework>
What is being separated?
+-- A distinct feature area, with its own auth or middleware?
| -> namespace — the client connects to it, e.g. /chat, /admin
+-- A set of users inside one feature, for targeted broadcast?
| -> room — server-side only, joined on request
+-- Neither — one channel for everything?
-> the default namespace "/"
The auth option covers the token case, and its function form is what keeps the token fresh across reconnections. For session cookies, set withCredentials: true and leave auth alone — the server's CORS config has to allow credentials for this to work. Where one namespace needs elevated rights, give that namespace its own auth on manager.socket("/admin", { auth }) rather than gating in application code.
A plain emit is fire-and-forget. Add an acknowledgment when the sender needs to know it arrived: socket.timeout(ms).emitWithAck(...) for one call, or ackTimeout with retries (v4.6.0+) to have the client retransmit on its own — which makes the server's handler responsible for being idempotent, since the same packet can arrive twice. For data whose next update supersedes it — cursor positions, presence pings — socket.volatile.emit() drops rather than queues.
Binary payloads go directly in an event; the protocol serialises ArrayBuffer, Buffer and Blob, including inside objects mixed with JSON. Chunk large files yourself — send metadata first, then acknowledged chunks, so progress is reportable and a failure resumes.
</decision_framework>
v4 enforces both directions at compile time.
interface ServerToClientEvents {
"message:received": (message: ChatMessage) => void;
error: (error: SocketError) => void;
}
interface ClientToServerEvents {
"message:send": (content: string, ack: (res: SendResult) => void) => void;
}
type TypedSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
Full code: examples/core.md
timeout is the connection timeout, default 20000ms. ackTimeout is the per-emit acknowledgment clock and needs retries beside it — the two are unrelated despite the names.
const socket: TypedSocket = io(url, {
auth: { token },
reconnectionAttempts: MAX_RECONNECTION_ATTEMPTS,
reconnectionDelay: RECONNECTION_DELAY_MS,
timeout: CONNECTION_TIMEOUT_MS,
transports: ["websocket", "polling"],
});
Full code: examples/core.md
Socket-level events describe the socket; manager-level events (socket.io) describe the underlying connection. Reconnection progress only shows up on the second set.
socket.on("connect", () => {
if (!socket.recovered) refetchState();
});
socket.on("disconnect", (reason) => {
// socket.active === true means a reconnect is already scheduled
});
socket.on("connect_error", (error) => showError(error));
socket.io.on("reconnect_attempt", (attempt) => showReconnecting(attempt));
socket.io.on("reconnect_failed", () => showPermanentFailure());
Full code: examples/core.md
Two forms, one guarantee. emitWithAck awaits a single response; ackTimeout with retries retransmits automatically.
const response = await socket
.timeout(ACK_TIMEOUT_MS)
.emitWithAck("message:send", content);
// or: let the client retry, and make the server handler idempotent
const socket = io(url, { ackTimeout: ACK_TIMEOUT_MS, retries: MAX_RETRIES });
socket.emit("message:send", content, (response) => confirm(response));
Full code: examples/core.md
An auth object is read once, at construction. An auth function is called before every connection attempt, including reconnections — which is the difference between a session that survives a token refresh and one that does not.
const socket = io(url, {
auth: (cb) => {
cb({ token: getToken() });
},
});
// or update it just before the retry
socket.io.on("reconnect_attempt", () => {
socket.auth = { token: getToken() };
});
Full code: examples/authentication.md
A Manager owns the transport; each namespace socket rides it and can carry its own credentials.
const manager = new Manager(url, { autoConnect: false });
const chatSocket = manager.socket("/chat");
const adminSocket = manager.socket("/admin", { auth: { token: adminToken } });
manager.connect();
Full code: examples/rooms.md
off matches on the function reference, so the handler has to be a stable binding rather than an inline arrow.
useEffect(() => {
const handler = (msg: Message) => setMessages((prev) => [...prev, msg]);
socket.on("message", handler);
return () => {
socket.off("message", handler);
};
}, [socket]);
Full code: examples/core.md
</patterns><red_flags>
Breaks at runtime:
auth.socket.on(...) with no matching off — handlers accumulate per mount and each message is processed once per accumulation.off — it is a different reference from the one registered, so nothing is removed.socket.emit() on a disconnected socket — it is dropped silently — check socket.connected or queue.socket.id used as a user identifier — it is regenerated on every reconnection — key on a server-issued user id.auth object on a long session — the token expires and every reconnection is refused — use the function form.ackTimeout without retries — the retransmission behaviour it belongs to is never switched on.Surprising behaviour:
socket.recovered is only ever true when the server has connection state recovery enabled.volatile.emit() drops messages under congestion by design; anything that must arrive does not belong on it.retries enabled the same packet can be delivered more than once, so server handlers have to be idempotent.</red_flags>
npx skills add agents-inc/web-realtime-socket-io下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer