web-realtime-socket-io · git:20260202.b236384 · 2026-02-02 · sha256 cd8d17a3e0615a99
web-realtime-socket-io git:20260202.b236384A
Immutable. This exact content is served forever at /api/v1/blob/cd8d17a3e0615a99.
---
name: web-realtime-socket-io
description: Socket.IO v4.x client patterns, connection lifecycle, reconnection, authentication, rooms, namespaces, acknowledgments, binary data, TypeScript integration
---
# Socket.IO Real-Time Communication Patterns
> **Quick Guide:** Use Socket.IO for real-time bidirectional communication when you need rooms, namespaces, automatic reconnection, acknowledgments, or transport fallback. Socket.IO is NOT a WebSocket implementation - it adds a protocol layer with additional features.
---
<critical_requirements>
## CRITICAL: Before Using This Skill
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST define typed interfaces for ALL Socket.IO events - ServerToClientEvents and ClientToServerEvents)**
**(You MUST use the `auth` option for authentication tokens - NEVER pass tokens in query strings)**
**(You MUST clean up event listeners on component unmount using socket.off())**
**(You MUST handle connection errors and implement proper reconnection state management)**
**(You MUST use named constants for all timeout values, retry limits, and intervals)**
</critical_requirements>
---
**Auto-detection:** Socket.IO, socket.io-client, io(), useSocket, socket.emit, socket.on, rooms, namespaces, acknowledgments, real-time
**When to use:**
- Building real-time features requiring rooms or namespaces (chat, multiplayer)
- Need automatic reconnection with connection state recovery
- Need acknowledgments/callbacks for message delivery confirmation
- Building applications that must work in restrictive network environments (fallback transports)
- Need server-side broadcasting patterns (emit to room, namespace, all clients)
**Key patterns covered:**
- TypeScript event interfaces (ServerToClientEvents, ClientToServerEvents)
- Client connection configuration and lifecycle
- Authentication via auth option and middleware
- Rooms and namespaces for logical grouping
- Acknowledgments and callbacks
- Connection state recovery (v4.6.0+)
- React integration hooks
**When NOT to use:**
- Simple WebSocket needs without rooms/namespaces (use native WebSocket)
- Need to connect to non-Socket.IO WebSocket servers (incompatible protocols)
- Minimal bundle size is critical (Socket.IO adds overhead)
**Detailed Resources:**
- For code examples, see [examples/](examples/)
- For decision frameworks and anti-patterns, see [reference.md](reference.md)
---
<philosophy>
## Philosophy
Socket.IO provides a layer on top of WebSocket with additional features: automatic reconnection, room-based broadcasting, acknowledgments, and transport fallback. **It is NOT a WebSocket implementation** - a plain WebSocket client cannot connect to a Socket.IO server and vice versa.
**Key Architectural Concepts:**
1. **Transport Abstraction:** Socket.IO uses WebSocket when available but falls back to HTTP long-polling for restrictive networks. This happens automatically.
2. **Rooms:** Server-side grouping mechanism for targeted broadcasting. Clients don't know about rooms - they're purely a server concept for organizing sockets.
3. **Namespaces:** Separate communication channels on the same connection. Used to separate concerns (e.g., `/chat`, `/admin`, `/notifications`).
4. **Connection State Recovery (v4.6.0+):** Missed events can be automatically delivered after brief disconnections, reducing manual state sync.
**Connection Lifecycle:**
```
CONNECTING → CONNECTED ↔ (events) → DISCONNECTING → DISCONNECTED
↓ ↓
(error) ← reconnect ← (disconnect)
```
**Socket.IO vs Native WebSocket:**
| Feature | Socket.IO | Native WebSocket |
| ------------------ | --------------------- | -------------------- |
| Transport fallback | Automatic | Manual |
| Reconnection | Built-in | Manual |
| Rooms | Built-in | Manual (server-side) |
| Namespaces | Built-in | Not available |
| Acknowledgments | Built-in | Manual |
| Protocol | Custom (incompatible) | Standard WebSocket |
| Bundle size | ~14.5KB gzipped | Native (0KB) |
</philosophy>
---
<patterns>
## Core Patterns
### Pattern 1: TypeScript Event Interfaces
Socket.IO v4 has first-class TypeScript support. Define interfaces for type-safe bidirectional communication.
#### Event Type Definitions
```typescript
// types/socket-events.ts
// Events sent from server to client
interface ServerToClientEvents {
"user:joined": (user: User) => void;
"user:left": (userId: string) => void;
"message:received": (message: ChatMessage) => void;
"room:updated": (room: Room) => void;
"typing:start": (data: { userId: string; username: string }) => void;
"typing:stop": (data: { userId: string }) => void;
error: (error: SocketError) => void;
pong: () => void;
}
// Events sent from client to server
interface ClientToServerEvents {
"message:send": (
content: string,
callback: (response: MessageResponse) => void,
) => void;
"room:join": (roomId: string, callback: (result: JoinResult) => void) => void;
"room:leave": (roomId: string) => void;
"typing:start": (roomId: string) => void;
"typing:stop": (roomId: string) => void;
ping: () => void;
}
// Supporting types
interface User {
id: string;
username: string;
avatar?: string;
}
interface ChatMessage {
id: string;
content: string;
senderId: string;
roomId: string;
createdAt: Date;
}
interface Room {
id: string;
name: string;
memberCount: number;
}
interface SocketError {
code: string;
message: string;
}
interface MessageResponse {
success: boolean;
messageId?: string;
error?: string;
}
interface JoinResult {
success: boolean;
room?: Room;
error?: string;
}
export type {
ServerToClientEvents,
ClientToServerEvents,
User,
ChatMessage,
Room,
SocketError,
MessageResponse,
JoinResult,
};
```
**Why good:** Discriminated event names enable type narrowing, callback types are enforced, separate interfaces for each direction, supporting types are explicit
---
### Pattern 2: Client Configuration
Configure Socket.IO client with proper authentication, reconnection settings, and transport options.
#### Constants
```typescript
const RECONNECTION_DELAY_MS = 1000;
const RECONNECTION_DELAY_MAX_MS = 5000;
const MAX_RECONNECTION_ATTEMPTS = 10;
const REQUEST_TIMEOUT_MS = 10000;
```
#### Implementation
```typescript
// lib/socket-client.ts
import { io, Socket } from "socket.io-client";
import type {
ServerToClientEvents,
ClientToServerEvents,
} from "../types/socket-events";
type TypedSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
interface SocketConfig {
url: string;
token?: string;
autoConnect?: boolean;
}
export function createSocket(config: SocketConfig): TypedSocket {
const socket: TypedSocket = io(config.url, {
// Authentication - token in auth object, NOT query string
auth: config.token ? { token: config.token } : undefined,
// Connection settings
autoConnect: config.autoConnect ?? false,
// Reconnection settings
reconnection: true,
reconnectionAttempts: MAX_RECONNECTION_ATTEMPTS,
reconnectionDelay: RECONNECTION_DELAY_MS,
reconnectionDelayMax: RECONNECTION_DELAY_MAX_MS,
// Transport settings
transports: ["websocket", "polling"],
upgrade: true,
// Request timeout for emitWithAck
timeout: REQUEST_TIMEOUT_MS,
});
return socket;
}
// Singleton pattern for app-wide socket
let socketInstance: TypedSocket | null = null;
export function initializeSocket(token: string): TypedSocket {
if (socketInstance) {
socketInstance.disconnect();
}
const url = process.env.NEXT_PUBLIC_SOCKET_URL ?? "http://localhost:3000";
socketInstance = createSocket({
url,
token,
autoConnect: true,
});
return socketInstance;
}
export function getSocket(): TypedSocket {
if (!socketInstance) {
throw new Error("Socket not initialized. Call initializeSocket first.");
}
return socketInstance;
}
export function disconnectSocket(): void {
if (socketInstance) {
socketInstance.disconnect();
socketInstance = null;
}
}
```
**Why good:** Token in auth object (not query string), named constants for all timing values, typed socket with generics, singleton pattern prevents multiple connections, explicit error for uninitialized access
```typescript
// WRONG - Token in query string (visible in logs)
const socket = io(`http://localhost:3000?token=${token}`);
// WRONG - Magic numbers
const socket = io(url, {
reconnectionDelay: 1000,
reconnectionAttempts: 10,
});
```
**Why bad:** Query string tokens appear in server logs and may be cached by proxies, magic numbers make configuration unclear and hard to maintain
---
### Pattern 3: Connection Lifecycle Management
Track connection state and handle reconnection events properly.
#### Constants
```typescript
const INITIAL_RECONNECT_ATTEMPTS = 0;
```
#### Implementation
```typescript
// lib/socket-lifecycle.ts
import type { Socket } from "socket.io-client";
interface ConnectionState {
isConnected: boolean;
isReconnecting: boolean;
reconnectAttempts: number;
lastError: Error | null;
recovered: boolean;
}
type StateChangeCallback = (state: ConnectionState) => void;
export function setupConnectionLifecycle(
socket: Socket,
onStateChange: StateChangeCallback,
): () => void {
const state: ConnectionState = {
isConnected: socket.connected,
isReconnecting: false,
reconnectAttempts: INITIAL_RECONNECT_ATTEMPTS,
lastError: null,
recovered: false,
};
const updateState = (updates: Partial<ConnectionState>): void => {
Object.assign(state, updates);
onStateChange({ ...state });
};
// Connection established
const handleConnect = (): void => {
updateState({
isConnected: true,
isReconnecting: false,
reconnectAttempts: INITIAL_RECONNECT_ATTEMPTS,
lastError: null,
recovered: socket.recovered ?? false,
});
};
// Connection lost
const handleDisconnect = (reason: string): void => {
const willReconnect = socket.active;
updateState({
isConnected: false,
isReconnecting: willReconnect,
recovered: false,
});
// Log for debugging
if (!willReconnect) {
console.log("Connection closed permanently:", reason);
}
};
// Connection error
const handleConnectError = (error: Error): void => {
updateState({
isConnected: false,
lastError: error,
});
};
// Manager-level events for reconnection tracking
const handleReconnectAttempt = (attempt: number): void => {
updateState({
isReconnecting: true,
reconnectAttempts: attempt,
});
};
const handleReconnect = (): void => {
updateState({
isConnected: true,
isReconnecting: false,
reconnectAttempts: INITIAL_RECONNECT_ATTEMPTS,
});
};
const handleReconnectFailed = (): void => {
updateState({
isReconnecting: false,
lastError: new Error("Max reconnection attempts reached"),
});
};
// Socket-level events
socket.on("connect", handleConnect);
socket.on("disconnect", handleDisconnect);
socket.on("connect_error", handleConnectError);
// Manager-level events (socket.io property is the Manager)
socket.io.on("reconnect_attempt", handleReconnectAttempt);
socket.io.on("reconnect", handleReconnect);
socket.io.on("reconnect_failed", handleReconnectFailed);
// Return cleanup function
return () => {
socket.off("connect", handleConnect);
socket.off("disconnect", handleDisconnect);
socket.off("connect_error", handleConnectError);
socket.io.off("reconnect_attempt", handleReconnectAttempt);
socket.io.off("reconnect", handleReconnect);
socket.io.off("reconnect_failed", handleReconnectFailed);
};
}
```
**Why good:** Distinguishes socket-level vs manager-level events, tracks recovery state for v4.6.0+, returns cleanup function for React integration, state updates are immutable
---
### Pattern 4: Acknowledgments with Timeout and Retries
Use acknowledgments to confirm message delivery with timeout handling. Socket.IO v4.6.0+ adds automatic retry support.
#### Constants
```typescript
const ACK_TIMEOUT_MS = 5000;
const DEFAULT_TIMEOUT_MS = 10000;
const MAX_RETRIES = 3;
```
#### Automatic Retries (v4.6.0+)
```typescript
// Configure socket with automatic retries
const socket = io(url, {
ackTimeout: ACK_TIMEOUT_MS, // Timeout per attempt
retries: MAX_RETRIES, // Max retry attempts
});
// Events are automatically retried on timeout
socket.emit("message:send", content, (response) => {
// Will be retried up to MAX_RETRIES times if no ack received
console.log("Message confirmed:", response);
});
```
**Why good:** Automatic retry handling reduces boilerplate, consistent timeout behavior across all emits, server must be idempotent for retried packets
#### Manual Implementation
```typescript
// lib/socket-utils.ts
import type { Socket } from "socket.io-client";
// Callback-based acknowledgment
export function emitWithCallback<T>(
socket: Socket,
event: string,
data: unknown,
callback: (response: T) => void,
): void {
socket.emit(event, data, callback);
}
// Promise-based acknowledgment with timeout
export async function emitWithTimeout<T>(
socket: Socket,
event: string,
data: unknown,
timeoutMs: number = DEFAULT_TIMEOUT_MS,
): Promise<T> {
try {
const response = await socket.timeout(timeoutMs).emitWithAck(event, data);
return response as T;
} catch (error) {
if ((error as Error).message?.includes("timeout")) {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
throw error;
}
}
// Usage example
async function sendMessage(
socket: Socket,
content: string,
): Promise<MessageResponse> {
return emitWithTimeout<MessageResponse>(
socket,
"message:send",
content,
ACK_TIMEOUT_MS,
);
}
```
**Why good:** Two patterns for different use cases (callback vs Promise), explicit timeout handling, named constants for timeout values, typed response generic
---
### Pattern 5: Connection State Recovery
Leverage Socket.IO v4.6.0+ connection state recovery to handle brief disconnections gracefully.
```typescript
// lib/socket-recovery.ts
import type { Socket } from "socket.io-client";
interface RecoveryHandler {
onRecovered: () => void;
onNewSession: () => void;
}
export function setupRecoveryHandler(
socket: Socket,
handlers: RecoveryHandler,
): () => void {
const handleConnect = (): void => {
if (socket.recovered) {
// Connection recovered - missed events will be delivered automatically
// No need to re-fetch initial state
console.log("Connection recovered, state restored");
handlers.onRecovered();
} else {
// New session or recovery failed
// Need to re-sync state from server
console.log("New session, fetching initial state...");
handlers.onNewSession();
}
};
socket.on("connect", handleConnect);
return () => {
socket.off("connect", handleConnect);
};
}
// Usage with initial data fetch
function setupWithRecovery(
socket: Socket,
fetchInitialState: () => void,
): void {
setupRecoveryHandler(socket, {
onRecovered: () => {
// Missed events delivered automatically - just update UI
console.log("Session recovered, no refetch needed");
},
onNewSession: () => {
// Need full state sync
fetchInitialState();
},
});
}
```
**Why good:** Differentiates recovered vs new sessions, prevents unnecessary refetches after brief disconnections, cleanup function for React
---
### Pattern 6: Sending and Receiving Binary Data
Socket.IO automatically handles binary data including Buffer, ArrayBuffer, and Blob.
#### Constants
```typescript
const BINARY_CHUNK_SIZE = 64 * 1024; // 64KB chunks
```
#### Implementation
```typescript
// lib/binary-transfer.ts
interface FileMetadata {
name: string;
type: string;
size: number;
}
interface UploadResponse {
success: boolean;
fileId?: string;
error?: string;
}
// Sending binary data
export function sendFile(
socket: Socket,
file: File,
onProgress?: (percent: number) => void,
): Promise<UploadResponse> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const arrayBuffer = reader.result as ArrayBuffer;
const metadata: FileMetadata = {
name: file.name,
type: file.type,
size: file.size,
};
// Socket.IO handles ArrayBuffer automatically
socket.emit(
"file:upload",
arrayBuffer,
metadata,
(response: UploadResponse) => {
if (response.success) {
resolve(response);
} else {
reject(new Error(response.error ?? "Upload failed"));
}
},
);
};
reader.onerror = () => reject(reader.error);
reader.readAsArrayBuffer(file);
});
}
// Receiving binary data
export function setupBinaryReceiver(
socket: Socket,
onFileReceived: (data: ArrayBuffer, metadata: FileMetadata) => void,
): () => void {
const handler = (data: ArrayBuffer, metadata: FileMetadata): void => {
onFileReceived(data, metadata);
};
socket.on("file:received", handler);
return () => {
socket.off("file:received", handler);
};
}
```
**Why good:** Socket.IO handles binary serialization automatically, metadata travels with file data, callback confirms delivery, cleanup function returned
</patterns>
---
<integration>
## Integration Guide
**Socket.IO is a transport solution.** This skill covers Socket.IO client patterns only.
**Works with:**
- Your React framework via custom hooks (see examples/core.md)
- Your state management solution for connection state tracking
- Your authentication system for token management
**Defers to:**
- Backend Socket.IO server implementation (backend skills)
- Native WebSocket patterns (websockets skill)
- State management for storing received data (state management skills)
</integration>
---
<critical_reminders>
## CRITICAL REMINDERS
> **All code must follow project conventions in CLAUDE.md**
**(You MUST define typed interfaces for ALL Socket.IO events - ServerToClientEvents and ClientToServerEvents)**
**(You MUST use the `auth` option for authentication tokens - NEVER pass tokens in query strings)**
**(You MUST clean up event listeners on component unmount using socket.off())**
**(You MUST handle connection errors and implement proper reconnection state management)**
**(You MUST use named constants for all timeout values, retry limits, and intervals)**
**Failure to follow these rules will result in security vulnerabilities, memory leaks, and type-unsafe code.**
</critical_reminders>