11

is it possible to reuse a socket.id or use it multiple times?

Let's assume a user views multiple pages of the same site in different browser tabs. I want to use a single socket.id, socket to handle them all.

If a user receives a notification it should popup on all tabs with a single socket.emit.

user2429266
  • 390
  • 1
  • 3
  • 19

3 Answers3

14

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.

Daniel Reina
  • 5,764
  • 1
  • 37
  • 50
  • 2
    Thanks for the thoroughness of this answer! – Tyler Collier Sep 13 '20 at 20:58
  • my case is that i don't reconnect manually, socket.io on browser side reconnects by itself when connection is lost, possible to let it be back to the same socket.io id? – Dee Dec 17 '20 at 07:30
  • 1
    @datdinhquoc I thought in that case it would automatically re-use the id. Anyways, you can manually force it to reuse it by implementing points 1 and 3. You'd need to save the given socketId on the client to be able to send it in point 3. I'd suggest something like sessionStorage for that – Daniel Reina Dec 17 '20 at 15:30
  • @Daniel Reina , I am trying to follow your example and overwriting generateId(). But it doesnt work, because the code after searchparams never get reached for some reason. I inserted a log-statement before and after. The second one doesnt print. Also on the front end I get 400 bad request as error. – Atr0x Dec 27 '20 at 02:13
  • @Atr0x I'd try returning a hardcoded string. If that works, I'd look at the `req` argument received by `io.engine.generateId`. It sounds like you're not receiving what you expect to receive as the argument there. – Daniel Reina Dec 27 '20 at 16:17
  • @Daniel Reina Well, I solved the Problem. I did get an error inside `generateId` and that returned the 400 Bad Request. I replaced `parsedUrl.searchParams.get('socketId')` by `(new URLSearchParams(parsedUrl.search)).get('socketId')` to remove the error. But now I facing another Issue. `generateId` gets executed, but if I try to print it with `console.log` inside `io.on('connection')`, it prints a different one. – Atr0x Dec 28 '20 at 00:20
  • @Daniel Reina I posted now a [question](https://stackoverflow.com/questions/65471836/socket-io-overwritten-generateid-gets-executed-but-the-returned-id-seems-to) about my problem – Atr0x Dec 28 '20 at 00:39
6

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.

hexacyanide
  • 88,222
  • 31
  • 159
  • 162
0

Multiple sites?

No, that's not possible. It would be possible if you open those sites into iframes in your webapp, I guess.

Another option would be to build a browser plugin that opens a socket connection.

fusio
  • 3,595
  • 6
  • 33
  • 47