reuse socket id on reconnect, socket.io, node.js

It is possible

From the dates of previous responses I assume it might not have been possible in previous versions of socket.io, but I can confirm that I'm successfully reusing socket ids on reconnections with socket.io 2.3.0.

You just need to override io.engine.generateId. Whatever that method returns will be the id assigned to the socket. Here are the docs about generateId.

As far as I've experimented myself, there are two situations when that method is called. During connections and reconnections.

The method io.engine.generateId receives the originating request object as the argument, so we can use it to figure out if we want to reuse the id or get a fresh new one.

Example

As an example, I'll show how you would reuse an id sent from the client, or create a new one when the client doesn't send it. The id will be sent on the handshake request as the query param socketId.

1. Override io.engine.generateId

First you need to override io.engine.generateId, which is the method that assigns IDs. On the server you need to do something like this.

const url = require('url')
const base64id = require('base64id')

io.engine.generateId = req => {
  const parsedUrl = new url.parse(req.url)
  const prevId = parsedUrl.searchParams.get('socketId')
  // prevId is either a valid id or an empty string
  if (prevId) {
    return prevId
  }
  return base64id.generateId()
}

That way, whenever you send the query param socketId in the handshake request, it will be set as the socket id. If you don't send it you'll generate a new one using base64id. The reason to use that library in particular is because that's what the original method does. Here you can find the source code.

2. Send the information on the connection request

Once you have that, you need to send the socketId param from the client. This is described in the docs.

const socket = io.connect(process.env.WEBSOCKET_URL, {
  query: {
    socketId: existingSocketId || ''
  }
})

process.env.WEBSOCKET_URL would be the URL where your web socket is listening.

Note that this will work when connecting, but you might want to update the query on reconnection.

3. Send the information on the reconnection request

On the same section of the docs it explains how to update the query params before reconnection. You just need to do something like this.

socket.on('reconnect_attempt', () => {
  socket.io.opts.query = {
    socketId: existingSocketId || ''
  }
});

Just like that you'll be reusing the same socket id as long as it is sent from the client.

Security concerns

It's probably a bad idea to trust information sent from the client to assign the socket id. I'd recommend sending a cryptographically signed payload, storing that payload in the client, and send it back to the server when connecting and reconnecting. That way the server can check that the payload can be trusted by verifying the signature.

Using the same example above, we would send something like this to the client, maybe .on('connect'):

{
  socketId: 'foo',
  signature: SHA_256('foo' + VERY_SECRET_PASSWORD)
}

The client would store that payload and send it back on connecting or reconnecting, in the same way we were sending socketId before.

Once the server receives the signed payload, inside io.engine.generateId we could check that the signature in the payload matches the hash we produce using the ID and the VERY_SECRET_PASSWORD.


You can't reuse Socket.IO connection IDs since they are created during the client-server handshake, but there are alternative methods. I don't have any examples, but you can modify the Socket.IO client to pass along a query string when the handshake is being performed. Then you can tell the server to handle the client based on the query string, and later fetch all client IDs with a certain query string.

Another method you could use would be to use namespaces. Assuming you have some type of session system, you could create a session-specific namespace, and connect clients with that session ID straight to that namespace.