# Socket.IO — User Guide

## 1. Quick Start

Socket.IO is a real-time, bidirectional, event-based communication library for the web. It enables instant data exchange between a Node.js server and any connected client (browser, mobile app, IoT device, or another server). Socket.IO is NOT a plain WebSocket wrapper — it is a higher-level framework that uses WebSocket as one of its transports while adding a rich feature set on top.

### Key Features

- **Automatic transport negotiation** — starts with HTTP long-polling, upgrades to WebSocket seamlessly. Falls back gracefully if WebSocket is blocked by proxies or firewalls.
- **Automatic reconnection** — clients reconnect with exponential backoff by default; no manual retry logic needed.
- **Rooms** — server-side grouping mechanism for broadcasting to subsets of clients (chat rooms, game lobbies, notification channels).
- **Namespaces** — multiplexing multiple logical connections over a single underlying transport (e.g., `/chat` and `/admin` on one socket).
- **Broadcasting** — emit to all clients, all clients in a room, or all clients except specific ones, with a fluent chainable API.
- **Acknowledgements** — request-response pattern built into emit; the receiver can call back with data.
- **Binary support** — send and receive `Buffer`, `ArrayBuffer`, `Blob`, and `File` objects natively; the parser handles serialization.
- **Connection state recovery** — after a brief disconnection, the server can restore room memberships and replay missed events automatically.

### Prerequisites

- **Node.js** 16 or later (LTS recommended)
- **npm** 7+ or **yarn** 1.22+
- A modern browser (Chrome, Firefox, Safari, Edge) for client-side usage

### Minimal Install

```bash
mkdir my-realtime-app && cd my-realtime-app
npm init -y
npm install socket.io socket.io-client
```

### Minimal Example

**Server (`server.js`):**

```javascript
const { Server } = require("socket.io");

const io = new Server(3000, {
  cors: { origin: "*" }
});

io.on("connection", (socket) => {
  console.log(`Client connected: ${socket.id}`);

  socket.on("hello", (msg) => {
    console.log(`Received: ${msg}`);
    socket.emit("hello", `Echo: ${msg}`);
  });

  socket.on("disconnect", (reason) => {
    console.log(`Client disconnected: ${reason}`);
  });
});

console.log("Socket.IO server listening on port 3000");
```

**Client (`client.js`):**

```javascript
const { io } = require("socket.io-client");

const socket = io("http://localhost:3000");

socket.on("connect", () => {
  console.log(`Connected as ${socket.id}`);
  socket.emit("hello", "world");
});

socket.on("hello", (msg) => {
  console.log(`Server says: ${msg}`);
});
```

**Run it:**

```bash
node server.js &
node client.js
```

**Expected output:**

```text
Socket.IO server listening on port 3000
Client connected: abc123XYZ
Connected as abc123XYZ
Received: world
Server says: Echo: world
```

> [!TIP]
> You do not need to set up Express or any HTTP framework to get started. The `Server` constructor can create its own HTTP server internally when you pass a port number directly.

### What to Try Next

After verifying the minimal example works, explore Section 4 (Core Usage) for rooms, namespaces, and broadcasting. Section 5 has 25+ task-oriented recipes covering chat apps, authentication, TypeScript, and more.

---

## 2. Installation

### From npm (recommended)

**Server:**

```bash
npm install socket.io
```

**Client (Node.js or bundler):**

```bash
npm install socket.io-client
```

**Both together (for full-stack projects):**

```bash
npm install socket.io socket.io-client
```

> [!IMPORTANT]
> The server package (`socket.io`) and the client package (`socket.io-client`) are versioned independently but designed to work together within the same major version. Always use matching major versions (e.g., both v4.x). Mixing v3 server with v4 client will cause silent connection failures.

### CDN for Browser Usage

For quick prototyping or static HTML pages, load the client from a CDN:

```html
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<script>
  const socket = io("http://localhost:3000");
  socket.on("connect", () => console.log("Connected!"));
</script>
```

Alternatively, when `serveClient` is enabled on the server (the default), the server itself serves the client bundle at the configured path:

```html
<script src="/socket.io/socket.io.js"></script>
```

### ES Module Import (Browser)

```html
<script type="module">
  import { io } from "https://cdn.socket.io/4.8.1/socket.io.esm.min.js";
  const socket = io("http://localhost:3000");
</script>
```

### From Source

```bash
git clone https://github.com/socketio/socket.io.git
cd socket.io
npm install
npm run build
```

The monorepo uses npm workspaces. Building from source compiles all 12 packages (server, client, engine.io, parsers, adapters, etc.).

### System Requirements

| Requirement | Minimum | Recommended |
|---|---|---|
| Node.js | 16.x | 20.x LTS or later |
| npm | 7.x | 10.x |
| Browser | Any modern (ES6+) | Latest Chrome/Firefox/Safari/Edge |
| OS | Any that runs Node.js | Linux for production servers |

### Post-Install Verification

**Server check:**

```javascript
const { Server } = require("socket.io");
console.log("socket.io loaded successfully");
const io = new Server();
console.log(`Server instance created, path: ${io.path()}`);
io.close();
```

**Client check:**

```javascript
const { io } = require("socket.io-client");
console.log("socket.io-client loaded successfully");
const socket = io("http://localhost:1", { autoConnect: false });
console.log(`Socket created, connected: ${socket.connected}`);
```

Run each with `node -e` or as a script. If no errors appear, the packages are installed correctly.

---

## 3. Configuration

This section covers the user-facing configuration options you need for day-to-day development. For production tuning, adapter configuration, cluster setup, and security hardening, see the Admin Guide.

### Server Options

When creating a `Server` instance, pass an options object as the second argument (or the only argument if not attaching to an existing HTTP server):

```javascript
const { Server } = require("socket.io");

const io = new Server(httpServer, {
  path: "/socket.io",
  serveClient: true,
  connectTimeout: 45000,
  cors: {
    origin: "https://myapp.com",
    methods: ["GET", "POST"]
  }
});
```

**Socket.IO Server Options:**

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `"/socket.io"` | The path the server listens on. Must match the client's `path` option. |
| `serveClient` | boolean | `true` | Whether to serve the client JS bundle at `{path}/socket.io.js`. |
| `adapter` | constructor | in-memory Adapter | The adapter class for managing rooms. Swap for Redis/Postgres adapter in multi-server deployments. |
| `parser` | object | `socket.io-parser` | Custom parser for encoding/decoding packets. |
| `connectTimeout` | number | `45000` | Milliseconds to wait for a client to join a namespace before disconnecting it. |
| `connectionStateRecovery` | object | `undefined` | Enable connection state recovery (see Section 4). |
| `cleanupEmptyChildNamespaces` | boolean | `false` | Automatically remove dynamic child namespaces when all clients disconnect. |

**Engine.IO Options (passed through to the transport layer):**

| Option | Type | Default | Description |
|---|---|---|---|
| `pingTimeout` | number | `20000` | Ms without a pong before considering the connection closed. |
| `pingInterval` | number | `25000` | Ms between ping packets. |
| `upgradeTimeout` | number | `10000` | Ms to complete a transport upgrade (polling to WebSocket). |
| `maxHttpBufferSize` | number | `1000000` | Maximum packet size in bytes (1 MB). Rejects larger payloads. |
| `transports` | string[] | `["polling", "websocket"]` | Allowed transports. Add `"webtransport"` to enable it. |
| `allowUpgrades` | boolean | `true` | Whether to allow transport upgrades (polling to WebSocket). |
| `perMessageDeflate` | boolean/object | `false` | Enable WebSocket per-message compression. |
| `httpCompression` | boolean/object | `true` | Enable compression for HTTP long-polling responses. |
| `cors` | object | — | CORS configuration (passed to the `cors` npm package). |
| `cookie` | object/boolean | `false` | Enable and configure Engine.IO session cookies. |
| `allowEIO3` | boolean | `false` | Allow Engine.IO v3 clients to connect (backward compatibility). |

### Client Options

The client `io()` factory accepts a URL and an options object:

```javascript
const { io } = require("socket.io-client");

const socket = io("https://myserver.com", {
  path: "/socket.io",
  reconnection: true,
  reconnectionAttempts: Infinity,
  reconnectionDelay: 1000,
  timeout: 20000,
  auth: { token: "my-jwt-token" }
});
```

**Manager Options (connection-level):**

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `"/socket.io"` | Must match the server's `path`. |
| `forceNew` | boolean | `false` | Force a new Manager (new connection) instead of reusing. |
| `multiplex` | boolean | `true` | Enable connection multiplexing for multiple namespaces. |
| `reconnection` | boolean | `true` | Enable automatic reconnection. |
| `reconnectionAttempts` | number | `Infinity` | Max reconnection attempts before giving up. |
| `reconnectionDelay` | number | `1000` | Initial delay (ms) before first reconnection attempt. |
| `reconnectionDelayMax` | number | `5000` | Maximum delay (ms) between reconnection attempts. |
| `randomizationFactor` | number | `0.5` | Jitter factor (0-1) applied to reconnection delay. |
| `timeout` | number | `20000` | Connection timeout (ms) before a `connect_error` is emitted. |
| `autoConnect` | boolean | `true` | Connect automatically on creation. Set `false` to connect manually. |

**Socket Options (passed alongside Manager options):**

| Option | Type | Default | Description |
|---|---|---|---|
| `auth` | object/function | — | Authentication payload sent during handshake. |
| `transports` | string[] | `["polling", "websocket"]` | Transports to use. |
| `query` | object | — | Query parameters appended to the connection URL. |
| `extraHeaders` | object | — | Additional HTTP headers (only for polling and WebSocket in Node.js). |

### Matching Server and Client Configuration

The `path` option must be identical on both sides. If you change it on the server, change it on every client:

```javascript
// Server
const io = new Server(httpServer, { path: "/my-custom-path" });

// Client
const socket = io("https://myserver.com", { path: "/my-custom-path" });
```

If they do not match, the client will receive 404 errors and never connect.

---

## 4. Core Usage

### 4.1 Connecting

#### Client-Side Connection

The `io()` function creates a socket connection:

```javascript
const { io } = require("socket.io-client");

// Connect to the same host (browser) or specify URL (Node.js)
const socket = io("http://localhost:3000");

// Connect to a specific namespace
const adminSocket = io("http://localhost:3000/admin");

// Connect with options
const socket = io("http://localhost:3000", {
  auth: { token: "abc123" },
  transports: ["websocket"],
  autoConnect: false
});

// Manually connect when autoConnect is false
socket.connect();
```

#### Connection Events

```javascript
socket.on("connect", () => {
  console.log("Connected:", socket.id);
});

socket.on("connect_error", (err) => {
  console.log("Connection error:", err.message);
});

socket.on("disconnect", (reason, details) => {
  console.log("Disconnected:", reason);
  // reason can be:
  //   "io server disconnect" — server called socket.disconnect()
  //   "io client disconnect" — client called socket.disconnect()
  //   "ping timeout" — server didn't respond in time
  //   "transport close" — connection was lost
  //   "transport error" — connection encountered an error
  //   "parse error" — received invalid packet
  //   "forced close" — client called manager.close()
});
```

> [!NOTE]
> When the server forcefully disconnects a socket with `socket.disconnect(true)`, the client receives `"io server disconnect"` and will NOT automatically reconnect. You must explicitly call `socket.connect()` if you want to reconnect.

#### Manager Events

The underlying Manager handles the transport connection and exposes its own events:

```javascript
socket.io.on("open", () => console.log("Transport opened"));
socket.io.on("close", (reason) => console.log("Transport closed:", reason));
socket.io.on("ping", () => console.log("Ping from server"));
socket.io.on("reconnect", (attempt) => console.log(`Reconnected after ${attempt} attempts`));
socket.io.on("reconnect_attempt", (attempt) => console.log(`Reconnect attempt #${attempt}`));
socket.io.on("reconnect_error", (err) => console.log("Reconnect error:", err));
socket.io.on("reconnect_failed", () => console.log("Reconnect failed — gave up"));
```

#### Server-Side Connection Handling

```javascript
io.on("connection", (socket) => {
  console.log("New client:", socket.id);
  console.log("Auth data:", socket.handshake.auth);
  console.log("Query params:", socket.handshake.query);
  console.log("Headers:", socket.handshake.headers);
  console.log("Remote address:", socket.handshake.address);

  socket.on("disconnect", (reason) => {
    console.log(`Client ${socket.id} disconnected: ${reason}`);
  });
});
```

### 4.2 Emitting Events

Socket.IO uses a publish-subscribe model. Any string can be an event name (except reserved names like `connect`, `disconnect`, `error`).

#### Basic Emit

```javascript
// Client to server
socket.emit("chat message", "Hello, world!");

// Server to client
socket.emit("chat message", "Welcome!");

// Multiple arguments
socket.emit("user:update", { name: "Alice" }, [1, 2, 3], true);
```

#### Emit with Acknowledgement (Callback)

The sender passes a callback as the last argument. The receiver invokes it to send a response:

```javascript
// Client sends, expects response
socket.emit("save:document", { title: "Draft" }, (response) => {
  console.log("Server acknowledged:", response);
});

// Server handles, responds
socket.on("save:document", (doc, callback) => {
  const savedId = saveToDb(doc);
  callback({ status: "ok", id: savedId });
});
```

#### Emit with Ack and Timeout

```javascript
socket.timeout(5000).emit("save:document", { title: "Draft" }, (err, response) => {
  if (err) {
    console.log("Server did not acknowledge in time");
  } else {
    console.log("Saved:", response);
  }
});
```

#### Promise-Based Emit (emitWithAck)

```javascript
// Client
try {
  const response = await socket.emitWithAck("save:document", { title: "Draft" });
  console.log("Response:", response);
} catch (err) {
  console.log("Timeout or error:", err);
}

// Server handler remains the same — just call the callback
socket.on("save:document", (doc, callback) => {
  callback({ status: "ok" });
});
```

> [!WARNING]
> When using `emitWithAck`, always set a timeout with `socket.timeout(ms).emitWithAck(...)`. Without a timeout, the promise will hang forever if the other side never acknowledges. This was identified as a memory leak vector in production environments.

### 4.3 Listening for Events

#### Standard Listeners

```javascript
// Listen for a specific event
socket.on("chat message", (msg) => {
  console.log("Message:", msg);
});

// Listen once (auto-removes after first call)
socket.once("welcome", (data) => {
  console.log("Welcome message:", data);
});

// Remove a specific listener
const handler = (msg) => console.log(msg);
socket.on("chat message", handler);
socket.off("chat message", handler);

// Remove all listeners for an event
socket.removeAllListeners("chat message");
```

#### Catch-All Listeners

```javascript
// Listen to every incoming event
socket.onAny((eventName, ...args) => {
  console.log(`Event: ${eventName}`, args);
});

// Listen to every outgoing event
socket.onAnyOutgoing((eventName, ...args) => {
  console.log(`Sending: ${eventName}`, args);
});

// Remove catch-all listeners
socket.offAny();
socket.offAnyOutgoing();
```

#### Prepend Listeners

```javascript
// Add listener to beginning of the listener array
socket.prependAny((eventName, ...args) => {
  console.log("First handler for any event:", eventName);
});
```

### 4.4 Rooms

Rooms are a server-side concept. They are arbitrary channels that sockets can join and leave. Rooms exist only on the server — clients do not know which rooms they are in.

#### Joining and Leaving

```javascript
io.on("connection", (socket) => {
  // Join a room
  socket.join("room1");

  // Join multiple rooms
  socket.join(["room1", "room2", "room3"]);

  // Leave a room
  socket.leave("room1");

  // Every socket automatically joins a room named after its socket.id
  console.log(socket.rooms); // Set { socket.id, "room1", "room2", ... }
});
```

#### Broadcasting to Rooms

```javascript
// Send to everyone in "room1" (including sender if in the room)
io.to("room1").emit("event", data);

// Same as to() — in() is an alias
io.in("room1").emit("event", data);

// Send to multiple rooms (union)
io.to("room1").to("room2").emit("event", data);

// Send to "room1" but exclude "room2"
io.to("room1").except("room2").emit("event", data);

// From within a socket handler: broadcast to room EXCLUDING the sender
socket.to("room1").emit("event", data);
```

#### Listing Sockets in a Room

```javascript
// Get all socket IDs in a room
const sockets = await io.in("room1").fetchSockets();
for (const s of sockets) {
  console.log(s.id, s.data, s.rooms);
}
```

#### Bulk Room Operations

```javascript
// Make all sockets in "room1" also join "room2"
io.in("room1").socketsJoin("room2");

// Make all sockets in "room1" leave "room2"
io.in("room1").socketsLeave("room2");

// Disconnect all sockets in "room1"
io.in("room1").disconnectSockets(true);
```

> [!TIP]
> Use the socket's own ID as a "personal room" to send messages to a specific user. Every socket automatically joins a room matching its `socket.id`, so `io.to(targetSocketId).emit("private", data)` works out of the box. For persistent identity across reconnections, use a user-specific room (see Section 5 recipes).

### 4.5 Namespaces

Namespaces let you split your application logic over a single shared connection. They act as separate communication channels with their own events, rooms, and middleware.

#### Creating Namespaces (Server)

```javascript
const { Server } = require("socket.io");
const io = new Server(3000);

// Main namespace (default)
io.on("connection", (socket) => {
  console.log("Main namespace:", socket.id);
});

// Custom namespace
const adminNsp = io.of("/admin");
adminNsp.on("connection", (socket) => {
  console.log("Admin namespace:", socket.id);
});

// Dynamic namespaces (regex)
const dynamicNsp = io.of(/^\/user-\w+$/);
dynamicNsp.on("connection", (socket) => {
  const namespace = socket.nsp.name; // e.g., "/user-alice"
  console.log(`Dynamic namespace ${namespace}:`, socket.id);
});

// Dynamic namespaces (function)
io.of((name, auth, next) => {
  // Validate and authorize
  if (name.startsWith("/project-")) {
    next(null, true);
  } else {
    next(new Error("Invalid namespace"));
  }
}).on("connection", (socket) => {
  console.log("Project namespace:", socket.nsp.name);
});
```

#### Connecting to Namespaces (Client)

```javascript
// Default namespace
const socket = io("http://localhost:3000");

// Custom namespace
const adminSocket = io("http://localhost:3000/admin");

// Dynamic namespace
const userSocket = io("http://localhost:3000/user-alice");
```

When multiplexing is enabled (default), all namespace connections to the same server share a single underlying transport connection.

#### Namespace Middleware

```javascript
adminNsp.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (isValidAdmin(token)) {
    next();
  } else {
    next(new Error("Unauthorized"));
  }
});
```

### 4.6 Broadcasting

The server provides a fluent, chainable API for targeting specific recipients.

#### Broadcast Targets

```javascript
// To ALL connected clients (current namespace)
io.emit("announcement", "Server is restarting");

// To all EXCEPT the sender
socket.broadcast.emit("user:joined", socket.id);

// To a specific room
io.to("room1").emit("event", data);

// To multiple rooms (clients in room1 OR room2)
io.to("room1").to("room2").emit("event", data);

// Exclude specific rooms
io.except("room1").emit("event", data);
io.to("room2").except("room3").emit("event", data);
```

#### Broadcast Modifiers

```javascript
// Volatile — drop the message if the client is not ready to receive
io.volatile.emit("cursor:move", { x: 100, y: 200 });

// With compression
io.compress(true).emit("large:data", bigPayload);

// Local — only broadcast to clients on THIS server (skip adapter broadcast in clusters)
io.local.emit("event", data);

// Timeout — expect acknowledgement from all recipients within a time window
io.timeout(5000).emit("important:event", data, (err, responses) => {
  if (err) {
    // Some clients did not acknowledge
  } else {
    // responses is an array of all acknowledgements
  }
});
```

#### Chain Everything Together

```javascript
io.to("room1")
  .to("room2")
  .except("room3")
  .compress(true)
  .timeout(5000)
  .emit("complex:broadcast", data, (err, responses) => {
    console.log(`${responses.length} clients acknowledged`);
  });
```

#### emitWithAck for Broadcasts

```javascript
try {
  const responses = await io.timeout(5000).emitWithAck("ping");
  console.log(`${responses.length} clients responded`);
} catch (err) {
  console.log("Some clients did not respond in time");
}
```

### 4.7 Binary Data

Socket.IO natively supports binary data. The parser automatically detects binary content and handles serialization.

```javascript
// Server sends a Buffer
const buffer = Buffer.from("Hello binary world");
socket.emit("file:chunk", buffer);

// Client sends an ArrayBuffer (browser)
const arrayBuffer = new ArrayBuffer(8);
const view = new Uint8Array(arrayBuffer);
view.set([1, 2, 3, 4, 5, 6, 7, 8]);
socket.emit("binary:data", arrayBuffer);

// Mixed: object with binary inside
socket.emit("upload", {
  filename: "photo.jpg",
  mimetype: "image/jpeg",
  data: fileBuffer
});

// The parser handles nested binary in arrays and objects automatically
socket.emit("complex", {
  text: "metadata",
  files: [buffer1, buffer2],
  nested: { deep: { binary: buffer3 } }
});
```

> [!NOTE]
> Binary payloads are extracted from the packet, sent as separate WebSocket frames, and reassembled on the receiving end. This is transparent to your code. The parser enforces attachment limits — if a packet claims more binary attachments than actually arrive, you will see a `"too many attachments"` parser error.

### 4.8 Error Handling

#### Client-Side Errors

```javascript
// Connection error (failed to connect or lost during reconnection)
socket.on("connect_error", (err) => {
  console.error("Connection error:", err.message);
  // err.message contains the error reason
  // Common: CORS, auth failure, server down
});

// Disconnection
socket.on("disconnect", (reason) => {
  if (reason === "io server disconnect") {
    // The server explicitly disconnected this socket
    // Must manually reconnect
    socket.connect();
  }
  // Otherwise, the client will auto-reconnect
});
```

#### Server-Side Errors

```javascript
io.on("connection", (socket) => {
  // Catch errors on individual sockets
  socket.on("error", (err) => {
    console.error(`Socket ${socket.id} error:`, err);
  });

  // Wrap event handlers with try/catch
  socket.on("risky:operation", async (data, callback) => {
    try {
      const result = await doSomethingRisky(data);
      callback({ status: "ok", result });
    } catch (err) {
      callback({ status: "error", message: err.message });
    }
  });
});

// Engine-level errors
io.engine.on("connection_error", (err) => {
  console.log("Engine error:", err.code, err.message, err.context);
});
```

#### Middleware Errors

When middleware calls `next(err)`, the client receives a `connect_error` event:

```javascript
// Server middleware
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (!token) {
    return next(new Error("Authentication required"));
  }
  next();
});

// Client handling
socket.on("connect_error", (err) => {
  console.log("Auth failed:", err.message); // "Authentication required"
});
```

---

## 5. Common Tasks

### 5.1 Chat Application

```javascript
// Server
const { Server } = require("socket.io");
const io = new Server(3000, { cors: { origin: "*" } });

io.on("connection", (socket) => {
  socket.on("join room", (room) => {
    socket.join(room);
    socket.to(room).emit("system", `${socket.id} joined the room`);
  });

  socket.on("chat message", ({ room, message }) => {
    io.to(room).emit("chat message", {
      from: socket.id,
      message,
      timestamp: Date.now()
    });
  });

  socket.on("typing", (room) => {
    socket.to(room).emit("typing", socket.id);
  });

  socket.on("disconnect", () => {
    // Socket automatically leaves all rooms on disconnect
  });
});
```

```javascript
// Client
const socket = io("http://localhost:3000");

socket.emit("join room", "general");

socket.on("chat message", ({ from, message, timestamp }) => {
  console.log(`[${new Date(timestamp).toLocaleTimeString()}] ${from}: ${message}`);
});

socket.on("system", (msg) => console.log(`*** ${msg} ***`));

socket.emit("chat message", { room: "general", message: "Hello everyone!" });
```

### 5.2 Real-Time Notifications

```javascript
// Server — push notifications to specific users
const userSockets = new Map(); // userId -> Set<socketId>

io.on("connection", (socket) => {
  const userId = socket.handshake.auth.userId;
  socket.join(`user:${userId}`);

  if (!userSockets.has(userId)) userSockets.set(userId, new Set());
  userSockets.get(userId).add(socket.id);

  socket.on("disconnect", () => {
    userSockets.get(userId)?.delete(socket.id);
    if (userSockets.get(userId)?.size === 0) userSockets.delete(userId);
  });
});

// Called from your application logic (e.g., after a DB write)
function notifyUser(userId, notification) {
  io.to(`user:${userId}`).emit("notification", notification);
}
```

### 5.3 Room-Based Multiplayer Game

```javascript
// Server
const games = new Map();

io.on("connection", (socket) => {
  socket.on("game:create", (gameId, callback) => {
    games.set(gameId, { players: [socket.id], state: {} });
    socket.join(`game:${gameId}`);
    callback({ status: "ok", gameId });
  });

  socket.on("game:join", (gameId, callback) => {
    const game = games.get(gameId);
    if (!game) return callback({ status: "error", message: "Game not found" });
    if (game.players.length >= 4) return callback({ status: "error", message: "Game full" });

    game.players.push(socket.id);
    socket.join(`game:${gameId}`);
    io.to(`game:${gameId}`).emit("game:player_joined", {
      playerId: socket.id,
      playerCount: game.players.length
    });
    callback({ status: "ok" });
  });

  socket.on("game:move", ({ gameId, move }) => {
    const game = games.get(gameId);
    if (!game) return;
    // Validate and apply move
    game.state = applyMove(game.state, move);
    io.to(`game:${gameId}`).emit("game:state", game.state);
  });

  socket.on("disconnect", () => {
    for (const [gameId, game] of games) {
      const idx = game.players.indexOf(socket.id);
      if (idx !== -1) {
        game.players.splice(idx, 1);
        io.to(`game:${gameId}`).emit("game:player_left", socket.id);
        if (game.players.length === 0) games.delete(gameId);
      }
    }
  });
});
```

### 5.4 Private Messaging

```javascript
// Server — using user-specific rooms for persistence across reconnections
io.use((socket, next) => {
  const userId = socket.handshake.auth.userId;
  if (!userId) return next(new Error("userId required"));
  socket.data.userId = userId;
  next();
});

io.on("connection", (socket) => {
  // Join a personal room keyed by user ID (survives reconnection with new socket.id)
  socket.join(`user:${socket.data.userId}`);

  socket.on("private message", ({ to, content }) => {
    const message = {
      from: socket.data.userId,
      to,
      content,
      timestamp: Date.now()
    };
    // Send to recipient
    io.to(`user:${to}`).emit("private message", message);
    // Send back to sender (for multi-device sync)
    socket.emit("private message", message);
  });
});
```

### 5.5 File Upload Progress

```javascript
// Client (browser)
async function uploadFile(file) {
  const chunkSize = 64 * 1024; // 64 KB chunks
  const totalChunks = Math.ceil(file.size / chunkSize);

  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);
    const buffer = await chunk.arrayBuffer();

    await socket.timeout(10000).emitWithAck("file:chunk", {
      name: file.name,
      chunk: buffer,
      index: i,
      total: totalChunks
    });

    console.log(`Progress: ${Math.round(((i + 1) / totalChunks) * 100)}%`);
  }
}
```

```javascript
// Server
const chunks = new Map();

io.on("connection", (socket) => {
  socket.on("file:chunk", ({ name, chunk, index, total }, callback) => {
    const key = `${socket.id}:${name}`;
    if (!chunks.has(key)) chunks.set(key, []);

    chunks.get(key)[index] = Buffer.from(chunk);

    if (chunks.get(key).filter(Boolean).length === total) {
      const fullFile = Buffer.concat(chunks.get(key));
      saveFile(name, fullFile);
      chunks.delete(key);
      callback({ status: "complete" });
    } else {
      callback({ status: "ok" });
    }
  });
});
```

### 5.6 Connection State Recovery

Connection state recovery allows the server to restore a client's room memberships and replay missed events after a brief disconnection, without any application-level logic.

```javascript
// Server — enable connection state recovery
const io = new Server(3000, {
  connectionStateRecovery: {
    maxDisconnectionDuration: 2 * 60 * 1000, // 2 minutes (default)
    skipMiddlewares: true                      // skip auth on recovery (default)
  }
});

io.on("connection", (socket) => {
  if (socket.recovered) {
    // Connection was recovered — rooms are restored, missed events replayed
    console.log("Client recovered:", socket.id);
  } else {
    // Fresh connection — set up from scratch
    console.log("New client:", socket.id);
    socket.join("default-room");
  }
});
```

```javascript
// Client — no special configuration needed, just handle reconnection
socket.on("connect", () => {
  if (socket.recovered) {
    console.log("Session recovered — rooms and missed events restored");
  } else {
    console.log("New session — re-subscribe to data");
  }
});
```

> [!IMPORTANT]
> Connection state recovery only works within the `maxDisconnectionDuration` window (default 2 minutes). After that, the server discards the recovery state. The feature relies on the in-memory adapter by default; for multi-server deployments, you need the cluster adapter.

### 5.7 Custom Parser

Socket.IO supports custom parsers for performance optimization or alternative serialization formats. The `msgpack` parser is a popular choice for reducing payload size.

```javascript
// Install
// npm install @socket.io/msgpack-parser

// Server
const { Server } = require("socket.io");
const msgpackParser = require("@socket.io/msgpack-parser");

const io = new Server(3000, {
  parser: msgpackParser
});

// Client
const { io } = require("socket.io-client");
const msgpackParser = require("@socket.io/msgpack-parser");

const socket = io("http://localhost:3000", {
  parser: msgpackParser
});
```

Both server and client MUST use the same parser. Mismatched parsers will cause `"parse error"` disconnections.

### 5.8 TypeScript — Typed Events

Socket.IO has first-class TypeScript support with typed event maps:

```typescript
// shared/types.ts — define event maps
interface ServerToClientEvents {
  "chat:message": (msg: { from: string; body: string; timestamp: number }) => void;
  "user:joined": (userId: string) => void;
  "user:left": (userId: string) => void;
}

interface ClientToServerEvents {
  "chat:send": (body: string, callback: (status: "ok" | "error") => void) => void;
  "room:join": (roomId: string) => void;
}

interface InterServerEvents {
  ping: () => void;
}

interface SocketData {
  userId: string;
  username: string;
}
```

```typescript
// server.ts
import { Server } from "socket.io";

const io = new Server<
  ClientToServerEvents,
  ServerToClientEvents,
  InterServerEvents,
  SocketData
>(3000);

io.on("connection", (socket) => {
  // socket.data is typed as SocketData
  socket.data.userId = "abc";

  // Event handlers are fully typed
  socket.on("chat:send", (body, callback) => {
    io.emit("chat:message", {
      from: socket.data.userId,
      body,
      timestamp: Date.now()
    });
    callback("ok");
  });

  // TypeScript errors if you emit an undefined event or wrong argument types
  // socket.emit("nonexistent", 123); // TS error!
});
```

```typescript
// client.ts
import { io, Socket } from "socket.io-client";

const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io("http://localhost:3000");

socket.on("chat:message", (msg) => {
  // msg is typed as { from: string; body: string; timestamp: number }
  console.log(`${msg.from}: ${msg.body}`);
});

socket.emit("chat:send", "Hello!", (status) => {
  // status is typed as "ok" | "error"
  console.log(status);
});
```

### 5.9 Using with Express

```javascript
const express = require("express");
const { createServer } = require("http");
const { Server } = require("socket.io");

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: { origin: "*" }
});

// Express routes
app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});

// Socket.IO events
io.on("connection", (socket) => {
  console.log("Client connected");
});

// IMPORTANT: call httpServer.listen(), NOT app.listen()
httpServer.listen(3000, () => {
  console.log("Server running on port 3000");
});
```

### 5.10 Using with Koa

```javascript
const Koa = require("koa");
const { createServer } = require("http");
const { Server } = require("socket.io");

const app = new Koa();
const httpServer = createServer(app.callback());
const io = new Server(httpServer);

io.on("connection", (socket) => {
  console.log("Client connected");
});

httpServer.listen(3000);
```

### 5.11 Using with NestJS

The Socket.IO monorepo includes a NestJS example. The pattern uses NestJS WebSocket gateways:

```typescript
import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  OnGatewayConnection,
  OnGatewayDisconnect
} from "@nestjs/websockets";
import { Server, Socket } from "socket.io";

@WebSocketGateway({ cors: { origin: "*" } })
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server: Server;

  handleConnection(client: Socket) {
    console.log("Client connected:", client.id);
  }

  handleDisconnect(client: Socket) {
    console.log("Client disconnected:", client.id);
  }

  @SubscribeMessage("message")
  handleMessage(client: Socket, payload: string): void {
    this.server.emit("message", payload);
  }
}
```

### 5.12 Authentication with Middleware

```javascript
const jwt = require("jsonwebtoken");

// Server-side middleware
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (!token) {
    return next(new Error("Authentication required"));
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    socket.data.user = decoded;
    next();
  } catch (err) {
    next(new Error("Invalid token"));
  }
});

io.on("connection", (socket) => {
  console.log("Authenticated user:", socket.data.user.name);
});
```

```javascript
// Client-side
const socket = io("http://localhost:3000", {
  auth: {
    token: localStorage.getItem("jwt")
  }
});

socket.on("connect_error", (err) => {
  if (err.message === "Authentication required" || err.message === "Invalid token") {
    // Redirect to login
    window.location.href = "/login";
  }
});
```

For dynamic tokens that may refresh between reconnection attempts, pass a function:

```javascript
const socket = io("http://localhost:3000", {
  auth: (cb) => {
    cb({ token: getLatestToken() });
  }
});
```

### 5.13 Reconnection Handling

```javascript
const socket = io("http://localhost:3000", {
  reconnection: true,
  reconnectionAttempts: 10,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000,
  randomizationFactor: 0.5
});

socket.io.on("reconnect_attempt", (attempt) => {
  console.log(`Reconnection attempt ${attempt}/10`);
  // Update UI: "Reconnecting..."
});

socket.io.on("reconnect", (attempt) => {
  console.log(`Reconnected after ${attempt} attempts`);
  // Re-fetch state, re-subscribe
  socket.emit("sync:state");
});

socket.io.on("reconnect_failed", () => {
  console.log("Failed to reconnect after all attempts");
  // Show "Connection lost" UI with manual retry button
});

// Manual reconnect button
document.getElementById("retry").addEventListener("click", () => {
  socket.connect();
});
```

> [!TIP]
> The `randomizationFactor` (default 0.5) adds jitter to prevent a "thundering herd" when all clients reconnect simultaneously after a server restart. A value of 0.5 means the actual delay will be between 50% and 150% of the computed delay. Do not set this to 0 in production.

### 5.14 Broadcasting to Specific Users

When users can have multiple tabs or devices connected, use a user-keyed room instead of `socket.id`:

```javascript
io.use((socket, next) => {
  socket.data.userId = socket.handshake.auth.userId;
  next();
});

io.on("connection", (socket) => {
  // All of this user's sockets join the same room
  socket.join(`user:${socket.data.userId}`);
});

// Send to a specific user (all their devices/tabs)
function sendToUser(userId, event, data) {
  io.to(`user:${userId}`).emit(event, data);
}

// Check if a user is online
async function isUserOnline(userId) {
  const sockets = await io.in(`user:${userId}`).fetchSockets();
  return sockets.length > 0;
}
```

### 5.15 Monitoring Connection Count

```javascript
// Simple counter
let connectionCount = 0;

io.on("connection", (socket) => {
  connectionCount++;
  console.log(`Connections: ${connectionCount}`);

  socket.on("disconnect", () => {
    connectionCount--;
    console.log(`Connections: ${connectionCount}`);
  });
});

// Using the adapter (more accurate, works across clusters)
setInterval(async () => {
  const sockets = await io.fetchSockets();
  console.log(`Active sockets: ${sockets.length}`);
}, 30000);

// Per-room counts
async function getRoomSize(room) {
  const sockets = await io.in(room).fetchSockets();
  return sockets.length;
}
```

### 5.16 Cross-Server Communication (serverSideEmit)

In a multi-server deployment (with Redis or Postgres adapter), `serverSideEmit` lets servers communicate directly:

```javascript
// Server A sends to all other servers
io.serverSideEmit("deploy:started", { version: "2.0.0" });

// Server B listens
io.on("deploy:started", ({ version }) => {
  console.log(`Deploy started: ${version}`);
  // Warm caches, prepare for restart, etc.
});

// With acknowledgement — every server responds
try {
  const responses = await io.serverSideEmitWithAck("stats:request");
  // responses is an array, one per server
  const totalConnections = responses.reduce((sum, r) => sum + r.connections, 0);
  console.log(`Total connections across cluster: ${totalConnections}`);
} catch (err) {
  console.log("Not all servers responded");
}
```

### 5.17 Using with CORS

CORS is the most common source of connection failures when the client and server are on different origins.

```javascript
// Allow a single origin
const io = new Server(3000, {
  cors: {
    origin: "https://myapp.com"
  }
});

// Allow multiple origins
const io = new Server(3000, {
  cors: {
    origin: ["https://myapp.com", "https://staging.myapp.com"]
  }
});

// Allow all origins (development only)
const io = new Server(3000, {
  cors: {
    origin: "*"
  }
});

// Full CORS configuration
const io = new Server(3000, {
  cors: {
    origin: "https://myapp.com",
    methods: ["GET", "POST"],
    allowedHeaders: ["Authorization"],
    credentials: true
  }
});
```

> [!CAUTION]
> Never use `cors: { origin: "*" }` in production. It allows any website to connect to your Socket.IO server. Always specify the exact origins that should be allowed. If you need credentials (cookies, auth headers), you must specify explicit origins — `"*"` does not work with `credentials: true`.

### 5.18 Volatile Events

Volatile events are dropped if the client is not ready to receive them. Useful for data that becomes stale quickly, like cursor positions or live scores:

```javascript
// Server
setInterval(() => {
  io.volatile.emit("stock:price", {
    symbol: "AAPL",
    price: getCurrentPrice("AAPL"),
    timestamp: Date.now()
  });
}, 100);

// Client — if processing is slow, some ticks are simply skipped
socket.on("stock:price", (data) => {
  updateChart(data);
});
```

### 5.19 Event Timeout Patterns

```javascript
// Client: emit with timeout
socket.timeout(5000).emit("save:data", payload, (err, response) => {
  if (err) {
    showError("Server did not respond in time");
  } else {
    showSuccess("Saved!");
  }
});

// Server: broadcast with ack timeout
io.to("room1").timeout(3000).emit("poll:question", question, (err, responses) => {
  if (err) {
    console.log("Some clients did not respond");
  }
  console.log(`${responses.length} responses collected`);
});

// Async/await version
try {
  const response = await socket.timeout(5000).emitWithAck("save:data", payload);
  console.log("Response:", response);
} catch (err) {
  console.log("Timeout:", err.message);
}
```

### 5.20 Using socket.data for Per-Socket State

```javascript
io.on("connection", (socket) => {
  // Attach arbitrary data to the socket
  socket.data.username = socket.handshake.auth.username;
  socket.data.role = "user";
  socket.data.joinedAt = Date.now();

  // socket.data is included when fetching remote sockets
  socket.on("promote", async (targetId) => {
    const [target] = await io.in(targetId).fetchSockets();
    if (target) {
      target.data.role = "moderator";
    }
  });
});
```

### 5.21 Multiple Manager Instances

By default, `io()` reuses the same Manager for connections to the same host (multiplexing). Force separate connections when needed:

```javascript
// These share one Manager (one transport connection)
const chatSocket = io("http://localhost:3000/chat");
const feedSocket = io("http://localhost:3000/feed");

// Force a separate connection
const isolatedSocket = io("http://localhost:3000/isolated", {
  forceNew: true
});

// Access the shared manager
chatSocket.io === feedSocket.io; // true
chatSocket.io === isolatedSocket.io; // false
```

### 5.22 Handling Middleware Rejection Gracefully

```javascript
// Server
io.use((socket, next) => {
  if (!socket.handshake.auth.token) {
    const err = new Error("AUTH_REQUIRED");
    err.data = { hint: "Provide auth.token" };
    return next(err);
  }
  next();
});

// Client
socket.on("connect_error", (err) => {
  console.log("Error:", err.message);   // "AUTH_REQUIRED"
  console.log("Details:", err.data);     // { hint: "Provide auth.token" }

  // Update auth and retry
  socket.auth = { token: promptUserForToken() };
  socket.connect();
});
```

### 5.23 Catch-All Event Logging (Debug/Development)

```javascript
// Server — log all incoming events
io.on("connection", (socket) => {
  socket.onAny((event, ...args) => {
    console.log(`[${socket.id}] >>> ${event}`, args);
  });

  socket.onAnyOutgoing((event, ...args) => {
    console.log(`[${socket.id}] <<< ${event}`, args);
  });
});

// Client
socket.onAny((event, ...args) => {
  console.log(`Server event: ${event}`, args);
});
```

### 5.24 Graceful Server Shutdown

```javascript
process.on("SIGTERM", () => {
  console.log("SIGTERM received, shutting down...");

  // Notify all clients
  io.emit("server:shutdown", { reason: "maintenance", reconnectIn: 30 });

  // Close the server (stops accepting new connections)
  io.close(() => {
    console.log("All connections closed");
    process.exit(0);
  });
});
```

### 5.25 WebTransport Support

WebTransport is a newer transport that offers lower latency than WebSocket. It must be explicitly enabled:

```javascript
// Server
const { Server } = require("socket.io");

const io = new Server(3000, {
  transports: ["polling", "websocket", "webtransport"]
});

// Client (browser only, requires HTTPS)
const socket = io("https://myserver.com", {
  transports: ["websocket", "webtransport"]
});

// Check which transport is active
socket.on("connect", () => {
  console.log("Transport:", socket.io.engine.transport.name);
});
```

WebTransport requires HTTPS and a browser that supports the WebTransport API (Chrome 97+).

### 5.26 Room Events via Adapter

Track when rooms are created and destroyed:

```javascript
io.of("/").adapter.on("create-room", (room) => {
  console.log(`Room created: ${room}`);
});

io.of("/").adapter.on("delete-room", (room) => {
  console.log(`Room deleted: ${room}`);
});

io.of("/").adapter.on("join-room", (room, id) => {
  console.log(`Socket ${id} joined room ${room}`);
});

io.of("/").adapter.on("leave-room", (room, id) => {
  console.log(`Socket ${id} left room ${room}`);
});
```

### 5.27 Using Query Parameters

```javascript
// Client — pass data as query params (visible in URL, use auth for secrets)
const socket = io("http://localhost:3000", {
  query: {
    version: "2.0",
    platform: "web"
  }
});

// Server — read query params
io.on("connection", (socket) => {
  console.log("Version:", socket.handshake.query.version);
  console.log("Platform:", socket.handshake.query.platform);
});
```

### 5.28 Namespace-Level Middleware Chains

```javascript
const adminNsp = io.of("/admin");

// Middleware 1: authentication
adminNsp.use((socket, next) => {
  const token = socket.handshake.auth.token;
  try {
    socket.data.user = jwt.verify(token, SECRET);
    next();
  } catch {
    next(new Error("Invalid token"));
  }
});

// Middleware 2: authorization
adminNsp.use((socket, next) => {
  if (socket.data.user.role !== "admin") {
    return next(new Error("Admin role required"));
  }
  next();
});

// Middleware 3: rate limiting
const connectionCounts = new Map();
adminNsp.use((socket, next) => {
  const ip = socket.handshake.address;
  const count = connectionCounts.get(ip) || 0;
  if (count >= 5) return next(new Error("Too many connections"));
  connectionCounts.set(ip, count + 1);
  next();
});

adminNsp.on("connection", (socket) => {
  console.log(`Admin connected: ${socket.data.user.name}`);
});
```

### 5.29 Disconnecting Event (Before Rooms Are Left)

The `disconnecting` event fires before the socket leaves its rooms, allowing you to broadcast a farewell or inspect room membership one last time:

```javascript
io.on("connection", (socket) => {
  socket.on("disconnecting", (reason) => {
    // socket.rooms is still populated at this point
    for (const room of socket.rooms) {
      if (room !== socket.id) {
        socket.to(room).emit("user:leaving", {
          userId: socket.data.userId,
          room
        });
      }
    }
  });

  socket.on("disconnect", (reason) => {
    // socket.rooms is empty at this point
  });
});
```

### 5.30 Rate Limiting Events

Prevent clients from flooding the server with events:

```javascript
io.on("connection", (socket) => {
  const rateLimiter = {
    tokens: 10,
    maxTokens: 10,
    lastRefill: Date.now(),
    refillRate: 1000 // 1 token per second
  };

  socket.use(([event, ...args], next) => {
    const now = Date.now();
    const elapsed = now - rateLimiter.lastRefill;
    rateLimiter.tokens = Math.min(
      rateLimiter.maxTokens,
      rateLimiter.tokens + (elapsed / rateLimiter.refillRate)
    );
    rateLimiter.lastRefill = now;

    if (rateLimiter.tokens < 1) {
      return next(new Error("Rate limit exceeded"));
    }

    rateLimiter.tokens--;
    next();
  });

  socket.on("error", (err) => {
    if (err.message === "Rate limit exceeded") {
      socket.emit("error:rate_limit", "Slow down! Too many events.");
    }
  });
});
```

---

## 6. Troubleshooting

### 6.1 CORS Errors

**Symptom:** Browser console shows `Access to XMLHttpRequest ... has been blocked by CORS policy`.

**Cause:** The Socket.IO server does not have CORS configured, or the configured origin does not match.

**Fix:**

```javascript
const io = new Server(httpServer, {
  cors: {
    origin: "https://your-frontend-domain.com",
    methods: ["GET", "POST"],
    credentials: true
  }
});
```

If you are using cookies or `credentials: true` on the client, you cannot use `origin: "*"`. You must specify the exact origin.

### 6.2 Firefox CORS Issues

**Symptom:** Connection works in Chrome but fails in Firefox with CORS errors. Related to GitHub issue #5288.

**Cause:** Firefox has stricter CORS enforcement in certain cases, especially with credentialed requests.

**Fix:** Ensure your CORS configuration includes `credentials: true` and an explicit (not wildcard) origin. Double-check that the `Access-Control-Allow-Origin` response header matches your page's origin exactly, including the protocol and port.

### 6.3 Connection Timeout

**Symptom:** Client emits `connect_error` after 20 seconds with a timeout message.

**Cause:** Server is unreachable, wrong URL, or firewall blocking.

**Fix:**
1. Verify the server is running and accessible: `curl http://localhost:3000/socket.io/?EIO=4&transport=polling`
2. Ensure the `path` option matches on client and server (default: `"/socket.io"`)
3. Check firewall rules
4. Increase `timeout` if the server is slow: `io("url", { timeout: 60000 })`

### 6.4 Reconnection Not Working After Server Disconnect

**Symptom:** After the server calls `socket.disconnect()`, the client does not reconnect. Related to GitHub issue #5197.

**Cause:** When the server forcefully disconnects a socket, the client receives the `"io server disconnect"` reason and intentionally does NOT auto-reconnect (by design).

**Fix:**

```javascript
socket.on("disconnect", (reason) => {
  if (reason === "io server disconnect") {
    // Must manually reconnect
    socket.connect();
  }
  // All other reasons auto-reconnect if reconnection is enabled
});
```

### 6.5 emitWithAck Memory Leak

**Symptom:** Memory usage grows over time when using `emitWithAck` or acknowledgement callbacks. Related to GitHub issue #4984.

**Cause:** If the receiver never calls the acknowledgement callback, the sender retains the callback in memory indefinitely.

**Fix:** Always use `.timeout()` before `emitWithAck`:

```javascript
// This can leak if the server never responds
const response = await socket.emitWithAck("event", data); // BAD

// This will reject after the timeout
const response = await socket.timeout(10000).emitWithAck("event", data); // GOOD
```

### 6.6 mTLS Connection Errors

**Symptom:** Connection fails with TLS errors when using mutual TLS (client certificates). Related to GitHub issue #5480.

**Cause:** The Engine.IO client creates separate TLS contexts for polling and WebSocket transports, and client certificates may not be forwarded correctly across upgrades.

**Fix:** Pass TLS options at the engine level:

```javascript
const socket = io("https://secure-server.com", {
  ca: fs.readFileSync("ca.pem"),
  cert: fs.readFileSync("client-cert.pem"),
  key: fs.readFileSync("client-key.pem"),
  rejectUnauthorized: true
});
```

### 6.7 Timer Reliability Issues

**Symptom:** Ping/pong timeouts fire incorrectly, causing unexpected disconnections. Related to GitHub issue #5357.

**Cause:** On some platforms (especially mobile browsers or Node.js with heavy event loop lag), timers may fire late, causing the server to believe the client has disconnected.

**Fix:** Increase `pingTimeout` on the server:

```javascript
const io = new Server(httpServer, {
  pingTimeout: 60000,  // 60 seconds instead of default 20
  pingInterval: 25000
});
```

### 6.8 ESM Placeholder Bug

**Symptom:** Importing Socket.IO with ESM (`import`) fails or imports an empty/placeholder module. Related to GitHub issue #5215.

**Cause:** Some bundler or module resolution configurations pick up a placeholder ESM entry point instead of the real module.

**Fix:** Ensure your `package.json` has `"type": "module"` if using ESM, or use the explicit import path:

```javascript
import { Server } from "socket.io/dist/index.js";
```

Alternatively, use `createRequire` as a workaround in ESM files:

```javascript
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const { Server } = require("socket.io");
```

### 6.9 CloudFlare Proxy Issues

**Symptom:** Connections drop after 100 seconds or WebSocket upgrades fail. Related to GitHub issue #2580.

**Cause:** CloudFlare terminates idle WebSocket connections after 100 seconds and may buffer/modify polling responses.

**Fix:**
- Reduce the `pingInterval` below 100 seconds (it is 25 seconds by default, which should be fine)
- Ensure CloudFlare's WebSocket support is enabled in the dashboard
- If using long-polling behind CloudFlare, be aware that CloudFlare may buffer chunked responses; consider starting with WebSocket only: `transports: ["websocket"]`

### 6.10 Parser Error: "illegal attachments"

**Symptom:** Connection drops with the error `"illegal attachments"`.

**Cause:** A packet declares binary attachments but the number of received attachments does not match the declared count. This can happen with corrupted packets, proxy interference, or mismatched parsers.

**Fix:** Ensure client and server use the same parser. If using a custom parser, verify it correctly handles binary attachment counts.

### 6.11 Parser Error: "too many attachments"

**Symptom:** The parser rejects a packet with `"too many attachments"`.

**Cause:** A packet declares an unusually high number of binary attachments, possibly from a malicious client.

**Fix:** This is a protection mechanism. Reduce `maxHttpBufferSize` to limit overall packet size. If legitimate, check that your serialization logic is not creating excessive attachments from deeply nested objects.

### 6.12 Parser Error: "unknown type"

**Symptom:** Parser error with `"unknown type: X"` where X is a number.

**Cause:** The packet type byte does not match any known Socket.IO packet type (0-6). This typically means a protocol version mismatch or corrupted data.

**Fix:** Ensure both client and server are on compatible versions. If connecting a v3 client to a v4 server, enable `allowEIO3: true`.

### 6.13 Parser Error: "invalid payload"

**Symptom:** Malformed packet error during parsing.

**Cause:** The JSON payload portion of a Socket.IO packet is not valid JSON.

**Fix:** Check for proxy or CDN interference that may modify response bodies. Ensure `httpCompression` settings are compatible with your proxy.

### 6.14 Parser Error: "got plaintext data when reconstructing a packet"

**Symptom:** This error appears during binary packet reconstruction.

**Cause:** The parser expects binary frames for attachments but received a text frame instead. This can happen when a proxy converts binary WebSocket frames to text.

**Fix:** Check your proxy/load balancer configuration for WebSocket frame handling. Some proxies do not properly forward binary WebSocket frames.

### 6.15 Rooms Not Receiving Events

**Symptom:** Events emitted to a room are not received by any client.

**Cause:** Multiple possibilities — the client never joined the room, the room name has a typo, or the join happened asynchronously after the emit.

**Fix:**

```javascript
// Verify the socket is actually in the room
io.on("connection", (socket) => {
  socket.join("room1");
  console.log("Rooms:", [...socket.rooms]); // Should include "room1"
});

// Check room membership before emitting
const sockets = await io.in("room1").fetchSockets();
console.log(`Sockets in room1: ${sockets.length}`);
```

### 6.16 Namespace Connection Denied

**Symptom:** Client receives `connect_error` when connecting to a namespace.

**Cause:** Namespace middleware rejected the connection, or the namespace does not exist and no catch-all dynamic namespace is configured.

**Fix:** Check the server error message:

```javascript
// Client
socket.on("connect_error", (err) => {
  console.log(err.message); // The error from next(new Error(...))
});
```

Ensure the namespace is registered on the server before clients try to connect.

### 6.17 Transport Upgrade Failures

**Symptom:** Connection stays on polling and never upgrades to WebSocket.

**Cause:** A reverse proxy, load balancer, or firewall is not configured to handle WebSocket upgrade requests.

**Fix:**

For nginx:

```text
location /socket.io/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}
```

For Apache, enable `mod_proxy_wstunnel`. For AWS ALB, ensure WebSocket support is enabled (it is by default on Application Load Balancers).

### 6.18 Client Receives Events Meant for Others

**Symptom:** A client receives events that should have been sent to a different socket or room.

**Cause:** Using `io.emit()` instead of `socket.to(room).emit()`, or accidentally joining sockets to wrong rooms.

**Fix:** Audit your emit calls. `io.emit()` sends to ALL connected sockets. Use `io.to(room).emit()` or `socket.to(room).emit()` for targeted delivery.

### 6.19 Multiple Connections from Same Client

**Symptom:** The server logs multiple `connection` events from the same browser tab.

**Cause:** Calling `io()` multiple times without `multiplex` (the default reuses the Manager, but calling `io()` for the same namespace twice creates two sockets). Or React/Vue component mounting twice in development mode.

**Fix:**

```javascript
// Singleton pattern
let socket;
function getSocket() {
  if (!socket) {
    socket = io("http://localhost:3000");
  }
  return socket;
}
```

In React with Strict Mode, components mount twice in development. Create the socket outside the component or use a ref.

### 6.20 Events Received Out of Order

**Symptom:** Events arrive at the client in a different order than they were emitted by the server.

**Cause:** If the connection upgrades from polling to WebSocket mid-stream, some events may be in-flight on the old transport while new events go through the new one.

**Fix:** Socket.IO guarantees ordering within a single transport. During the brief upgrade window, both transports run in parallel, and Engine.IO buffers to maintain ordering. If you are seeing out-of-order delivery, check for:
- Application logic emitting from different async contexts
- Multiple server instances without a shared adapter

### 6.21 High Memory Usage

**Symptom:** Server memory grows unboundedly.

**Cause:** Common causes include: accumulated event listeners (memory leak), unacknowledged callbacks, rooms never cleaned up, or buffered packets for disconnected sockets.

**Fix:**
- Use `socket.removeAllListeners()` on disconnect if you add many listeners
- Always use `.timeout()` with acknowledgements
- Enable `cleanupEmptyChildNamespaces: true` if using dynamic namespaces
- Monitor with `process.memoryUsage()` and the `fetchSockets()` count

### 6.22 Packet Size Exceeded

**Symptom:** Large events silently fail or the connection drops.

**Cause:** The payload exceeds `maxHttpBufferSize` (default 1 MB).

**Fix:**

```javascript
const io = new Server(httpServer, {
  maxHttpBufferSize: 5e6 // 5 MB
});
```

For very large payloads, chunk the data (see Section 5.5 for file upload pattern).

> [!WARNING]
> Increasing `maxHttpBufferSize` too much exposes your server to denial-of-service attacks. A single malicious client could send enormous payloads that consume all server memory. Use chunking for legitimately large data transfers instead.

### 6.23 "websocket" Transport Not Available in Node.js Client

**Symptom:** Client falls back to polling when you expect WebSocket.

**Cause:** The `ws` (or `bufferutil`/`utf-8-validate`) packages may not be installed, or native add-ons failed to compile.

**Fix:**

```bash
npm install ws bufferutil utf-8-validate
```

If native compilation fails, Socket.IO falls back to pure JS WebSocket which is functional but slower. The connection will still work — just without native performance optimizations.

### 6.24 Disconnect Fires Immediately After Connect

**Symptom:** Client connects and immediately disconnects.

**Cause:** The `connectTimeout` on the server (default 45 seconds) closes sockets that connect at the Engine.IO level but never join a namespace. If your client connects and then takes a long time before the Socket.IO handshake completes (e.g., heavy middleware), the server may time out.

**Fix:** Increase `connectTimeout` or optimize middleware:

```javascript
const io = new Server(httpServer, {
  connectTimeout: 60000
});
```

### 6.25 Events Not Reaching All Servers in a Cluster

**Symptom:** Events emitted with `io.emit()` only reach clients connected to the originating server.

**Cause:** You are running multiple server instances without a shared adapter (Redis, Postgres, or cluster adapter).

**Fix:** Install and configure the Redis streams adapter or Postgres adapter:

```javascript
const { Server } = require("socket.io");
const { createAdapter } = require("@socket.io/redis-streams-adapter");
const { createClient } = require("redis");

const pubClient = createClient({ url: "redis://localhost:6379" });
await pubClient.connect();

const io = new Server(httpServer, {
  adapter: createAdapter(pubClient)
});
```

### 6.26 Debug Logging

Socket.IO uses the `debug` module for internal logging. Enable it to see what is happening under the hood:

```bash
# Enable all Socket.IO debug output
DEBUG=socket.io*,engine.io* node server.js

# Enable only client debug
DEBUG=socket.io-client* node client.js

# Enable only engine-level transport debug
DEBUG=engine.io* node server.js
```

> [!TIP]
> In the browser, set `localStorage.debug = 'socket.io-client:*'` and reload the page to see client-side debug output in the developer console.

### 6.27 Connection Works Locally But Not in Production

**Symptom:** Everything works on localhost, but fails behind a reverse proxy.

**Cause:** The proxy needs to forward both polling (HTTP) and WebSocket upgrade requests, and the path must match.

**Fix checklist:**
1. Proxy forwards the correct path (`/socket.io/` by default)
2. Proxy supports WebSocket upgrades (`Upgrade` and `Connection` headers)
3. Proxy does not aggressively timeout idle connections (increase timeout to at least `pingInterval + pingTimeout`)
4. Sticky sessions are configured if running multiple server instances behind a load balancer (required for polling transport)

### 6.28 "Invalid namespace" Error

**Symptom:** Client receives `"Invalid namespace"` in `connect_error`.

**Cause:** The client is trying to connect to a namespace (e.g., `/admin`) that has not been registered on the server with `io.of("/admin")`.

**Fix:** Register the namespace on the server before any client connects:

```javascript
// Must exist BEFORE clients connect
const adminNsp = io.of("/admin");
adminNsp.on("connection", (socket) => { /* ... */ });
```

Or use a dynamic namespace with a regex or function validator to accept connections to unknown namespaces.

---

## 7. Frequently Asked Questions

### 7.1 Is Socket.IO a WebSocket library?

No. Socket.IO is a higher-level abstraction that uses WebSocket as one of its underlying transports. It adds rooms, namespaces, automatic reconnection, broadcasting, acknowledgements, and fallback transports (HTTP long-polling). A plain WebSocket client cannot connect to a Socket.IO server — the protocol is different.

### 7.2 Can I connect a plain WebSocket client to a Socket.IO server?

No. Socket.IO uses its own packet protocol (Engine.IO at the transport level, Socket.IO parser at the application level). You must use a Socket.IO client library. Socket.IO clients are available for JavaScript, Java, Swift, Dart, Python, C++, and Rust.

### 7.3 What is Engine.IO?

Engine.IO is the low-level transport layer that Socket.IO builds upon. It handles the actual connection (polling, WebSocket, WebTransport), heartbeat (ping/pong), and transport upgrades. You rarely interact with Engine.IO directly — Socket.IO wraps it.

### 7.4 What is the difference between `io.emit()` and `socket.emit()`?

`io.emit(event, data)` sends the event to ALL connected clients in the default namespace. `socket.emit(event, data)` sends the event to ONE specific client (the socket). For broadcasting to everyone EXCEPT the sender, use `socket.broadcast.emit(event, data)`.

### 7.5 What is the difference between `to()` and `in()`?

They are aliases — `io.to("room1")` and `io.in("room1")` are identical.

### 7.6 Do rooms exist on the client?

No. Rooms are a server-side concept only. Clients do not know which rooms they are in. The server manages room membership and uses it to determine broadcast targets.

### 7.7 How do I send a message to a specific user?

Use a user-specific room. When the user connects, have the socket join a room keyed by user ID: `socket.join("user:" + userId)`. Then send to that user with `io.to("user:" + userId).emit(...)`. This works across multiple tabs/devices.

### 7.8 What happens when the client loses connection?

By default, the client automatically attempts to reconnect with exponential backoff (starting at 1 second, maxing at 5 seconds, with jitter). The client will retry indefinitely unless `reconnectionAttempts` is set to a finite number.

### 7.9 What is connection state recovery?

A feature that allows the server to restore a client's room memberships and replay missed events after a brief disconnection (up to `maxDisconnectionDuration`, default 2 minutes). It must be explicitly enabled on the server.

### 7.10 Why does the connection start with polling?

This is by design. Starting with HTTP long-polling ensures the connection succeeds even in restrictive network environments (corporate firewalls, proxies that block WebSocket). Once the polling connection is established, Socket.IO upgrades to WebSocket transparently for better performance. To skip polling and start with WebSocket directly: `io("url", { transports: ["websocket"] })`.

### 7.11 How do I skip the polling phase and go straight to WebSocket?

```javascript
const socket = io("http://localhost:3000", {
  transports: ["websocket"]
});
```

This is faster but removes the fallback — if WebSocket is blocked, the connection will fail entirely.

### 7.12 What is the `path` option?

The `path` (default `"/socket.io"`) is the URL path the server listens on and the client connects to. All Socket.IO HTTP requests (polling, WebSocket upgrade) go through this path. Must match on both sides. This is useful when running multiple Socket.IO servers on the same host or behind a reverse proxy with path-based routing.

### 7.13 Can I use Socket.IO without Express?

Yes. The `Server` constructor can create its own HTTP server when you pass a port number. Express, Koa, or any HTTP framework is optional.

### 7.14 What is the maximum message size?

Controlled by `maxHttpBufferSize`, which defaults to 1 MB (1,000,000 bytes). Messages exceeding this limit cause the connection to close. Increase it carefully.

### 7.15 How do I handle authentication?

Use the `auth` option on the client and a `use()` middleware on the server. The `auth` data is available in `socket.handshake.auth` on the server. See Section 5.12 for a complete JWT example.

### 7.16 Can I use Socket.IO in a cluster (multiple servers)?

Yes, but you need a shared adapter (e.g., `@socket.io/redis-streams-adapter`, `@socket.io/postgres-adapter`, or `@socket.io/cluster-adapter`). Without one, events only reach clients connected to the emitting server. Sticky sessions are also required if using the polling transport.

### 7.17 What are sticky sessions and why are they needed?

Sticky sessions ensure that all HTTP requests from a single client reach the same server instance. This is required for the polling transport because it uses multiple HTTP requests that must all hit the same server. Sticky sessions are NOT needed if you only use the WebSocket transport.

### 7.18 What is the `volatile` flag?

Marking an emit as `volatile` tells Socket.IO that the message can be dropped if the client is not ready to receive it (e.g., if the internal buffer is full or the client is reconnecting). Useful for high-frequency, ephemeral data like cursor positions or live metrics.

### 7.19 What is the difference between `disconnect()` and `close()`?

`socket.disconnect()` disconnects a single socket. `io.close()` shuts down the entire server and all connections. On the client, `socket.disconnect()` cleanly closes the connection (the client will NOT auto-reconnect).

### 7.20 How do I broadcast to everyone except the sender?

```javascript
socket.broadcast.emit("event", data);
// or
socket.to("room").emit("event", data); // to a room, excluding sender
```

### 7.21 What is `fetchSockets()` and when should I use it?

`fetchSockets()` returns an array of matching socket instances (or remote socket descriptors in a cluster). Use it to inspect socket state, get connection counts, or perform bulk operations:

```javascript
const sockets = await io.fetchSockets();
console.log(`${sockets.length} clients connected`);

for (const s of sockets) {
  console.log(s.id, s.rooms, s.data);
}
```

### 7.22 What is `serverSideEmit`?

It allows servers in a multi-server deployment to communicate directly with each other (not with clients). Requires a shared adapter. Useful for coordination like cache invalidation or deployment signaling.

### 7.23 Does Socket.IO support WebTransport?

Yes, as of recent versions. WebTransport must be explicitly added to the transports array (`["polling", "websocket", "webtransport"]`). It requires HTTPS and is currently supported only in Chromium-based browsers.

### 7.24 How do I debug connection issues?

Enable debug logging with `DEBUG=socket.io*,engine.io*` (Node.js) or `localStorage.debug = 'socket.io-client:*'` (browser). This shows every transport event, packet exchange, and error.

### 7.25 What are the reserved event names I cannot use?

On both client and server, the following event names are reserved and cannot be used for application events: `connect`, `connect_error`, `disconnect`, `disconnecting`, `newListener`, `removeListener`. Emitting these names will throw an error or be silently consumed by the framework.

### 7.26 How do namespaces differ from rooms?

Namespaces create separate communication channels — each has its own connection event, middleware chain, and rooms. Clients explicitly connect to a namespace. Rooms are server-side groupings within a namespace — clients do not know about them. Use namespaces to separate application concerns (e.g., `/chat` vs `/admin`). Use rooms to group users within a namespace (e.g., "room:lobby", "room:game-123").

### 7.27 Can I use Socket.IO with TypeScript?

Yes. Socket.IO has built-in TypeScript definitions and supports typed event maps for full type safety on both client and server. See Section 5.8 for a complete example.

### 7.28 What happens to messages when a client is disconnected?

They are lost. Socket.IO does not persist or queue messages for offline clients by default. If you need guaranteed delivery, implement application-level message persistence (store in a database, deliver on reconnect) or use connection state recovery for short disconnections.

### 7.29 What is the `compress` flag?

`io.compress(true).emit(...)` enables per-message compression for that emit. For WebSocket, this uses the `permessage-deflate` extension. For polling, it uses HTTP compression. Useful for large payloads; adds CPU overhead for small ones.

### 7.30 How do I handle CORS?

Pass a `cors` object in the server options. The options are passed directly to the `cors` npm package. See Section 5.17 for examples. CORS only applies to cross-origin connections — same-origin connections are unaffected.

### 7.31 What is the `disconnecting` event?

The `disconnecting` event fires on the server BEFORE the socket leaves its rooms. This lets you inspect `socket.rooms` and broadcast a "user is leaving" message to rooms before the socket is fully removed. The `disconnect` event fires after rooms are cleared.

### 7.32 What is `socket.handshake`?

The `handshake` object contains metadata from the initial connection: `headers`, `query`, `auth`, `address`, `time`, `url`, `secure`, and `xdomain`. It is read-only and fixed for the lifetime of the socket.

### 7.33 Can I emit events from outside the connection handler?

Yes. If you store a reference to the `io` server instance, you can emit from anywhere — HTTP route handlers, timers, database event listeners:

```javascript
// Express route that triggers a Socket.IO event
app.post("/api/notifications", (req, res) => {
  io.to(`user:${req.body.userId}`).emit("notification", req.body.message);
  res.json({ sent: true });
});
```

### 7.34 What is the `local` flag?

`io.local.emit(...)` broadcasts only to clients connected to the current server instance, skipping the adapter's cross-server broadcast. Useful for server-specific events in a cluster.

### 7.35 How many concurrent connections can Socket.IO handle?

This depends on your hardware, OS settings (file descriptor limits), and workload. A single Node.js process can typically handle 10,000-50,000 concurrent WebSocket connections. For higher scale, use clustering with a shared adapter.

### 7.36 What packages are in the Socket.IO monorepo?

The monorepo contains 12 packages: `socket.io` (server), `socket.io-client`, `engine.io` (server transport), `engine.io-client`, `engine.io-parser`, `socket.io-parser`, `socket.io-adapter` (base adapter), `socket.io-cluster-adapter`, `socket.io-cluster-engine`, `socket.io-postgres-emitter`, `socket.io-redis-streams-emitter`, and `socket.io-component-emitter`.

### 7.37 What examples are included in the repository?

The repository includes example projects: `angular-todomvc`, `basic-crud-application`, `basic-websocket-client`, `custom-parsers`, `nestjs-example`, and `private-messaging`. Each demonstrates a specific use case with both server and client code.

### 7.38 What is the `except()` method?

`io.except("room1").emit(...)` broadcasts to all sockets EXCEPT those in `room1`. It can be chained with `to()`: `io.to("room2").except("room1").emit(...)` sends to sockets in `room2` but not in `room1`.

### 7.39 Can I use a custom ID instead of the auto-generated socket.id?

Not directly. The `socket.id` is generated by the server and cannot be overridden. To track users by custom identity, use `socket.data` to store your custom ID and a user-keyed room for addressing.

### 7.40 What is the difference between `emitWithAck` and emit with a callback?

`emitWithAck` returns a Promise, making it usable with `async/await`. A regular emit with a callback uses the traditional callback pattern. They do the same thing — `emitWithAck` is syntactic sugar that wraps the callback in a Promise.

---

_Generated May 2026 from socketio/socket.io at 439a8f6. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
