socketio / socketio/socket.io

Connection State Recovery fails if client reconnects before server emits any events ("Zero-Event" reconnect)

Open
#5,538 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
TypeScript
Stars
63.2k
Forks
10.3k
Avg merge
11d 20h
Merged PRs (30d)
2

Description

Describe the bug
When connectionStateRecovery is enabled, a client that connects, joins rooms, and disconnects before the server emits any events cannot recover its session on reconnection.

Even if the client reconnects within maxDisconnectionDuration, socket.recovered evaluates to false, a new socket.id is generated, and room memberships / socket.data are lost. This happens because _lastOffset on the client is undefined in the zero event case, causing the server's typeof offset === "string" check in namespace.ts to fail and bypass restoreSession().

To Reproduce

Socket.IO server version: 4.8.1

Server

import { Server } from "socket.io";

const io = new Server(3000, {
  connectionStateRecovery: {
    maxDisconnectionDuration: 2 * 60 * 1000,
  },
});

io.on("connection", async (socket) => {
  if (socket.recovered) {
    console.log(`RECOVERED: ${socket.id} (rooms: ${Array.from(socket.rooms).join(", ")})`);
    return;
  }

  console.log(`INITIAL CONNECT: ${socket.id}`);
  await socket.join("room:alerts");
  // Deliberately no events emitted to the client
});

Socket.IO client version: 4.8.1

Client

import { io } from "socket.io-client";

const socket = io("http://localhost:3000", {
  reconnection: true,
});

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

  // Simulate a brief network drop shortly after connecting
  setTimeout(() => {
    socket.io.engine.close();
  }, 200);
});

Expected behavior
When the client reconnects after the transient drop, the server should recognize the pid, restore the previous session (socket.id, room memberships, socket.data), and set socket.recovered = true.

Platform:

  • Device: PC / Mac / Server
  • OS: Linux / macOS / Windows
  • Node.js version: 20.x / 22.x

Additional context

  • Root Cause: In socket.io-client, _lastOffset remains undefined until a packet of type EVENT with an offset is received. On reconnect, the handshake sends { pid: "<id>", offset: undefined }. In socket.io/lib/namespace.ts, recovery is gated by:

    if (
      this.server.opts.connectionStateRecovery &&
      typeof sessionId === "string" &&
      typeof offset === "string"
    ) {
      session = await this.adapter.restoreSession(sessionId, offset);
    }
    

    Because offset is undefined, restoreSession() is skipped entirely despite having a valid active pid.

  • Impact: Quiet subscriber channels (e.g. alert rooms, idle game lobbies) lose session state on brief network blips unless application code manually emits a synthetic dummy event on first connect to "prime" _lastOffset.

  • Potential Solutions:

    1. Include the current server stream offset in the initial CONNECT (packet type 0) handshake payload alongside { sid, pid } so _lastOffset is initialized at connection time.
    2. Allow restoreSession(sessionId, offset) to run when offset is undefined, restoring id, rooms, and data without replaying events.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the recovery gate in socket.io/lib/namespace.ts and trace how socket.io-client maintains _lastOffset and sends the reconnect handshake. Compare the zero-event path with restoreSession() and existing connection-state recovery behavior. Done means a reconnect without prior events preserves the session id, rooms, data, and reports socket.recovered as true.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, typescript
Domain
backend-api-design, networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
56/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.