socket.io-client
socket.io-client copied to clipboard
Manager.socket() re-uses a manually disconnected socket without reconnecting it
Describe the bug When calling Manager.socket('/someNamespace') a second time after already having called it before, it will re-use the same socket. If that socket has been manually disconnected using Socket.disconnect(), it will be returned in a disconnected state.
To Reproduce
Please fill the following code example:
Socket.IO server version: 3.1.1
Server
const { Server } = require('socket.io');
const io = new Server(3000, {});
io.of('/someNamespace').on('connection', (socket) => {
console.log(`connect ${socket.id}`);
socket.on('disconnect', () => {
console.log(`disconnect ${socket.id}`);
});
});
Socket.IO client version: 3.1.1
Client
const io = require('socket.io-client');
const manager = new io.Manager('ws://localhost:3000');
const firstSocket = manager.socket('/someNamespace');
firstSocket.on('connect', () => {
console.log('Connected first time');
firstSocket.disconnect();
console.log(firstSocket.connected); // false
const secondSocket = manager.socket('/someNamespace');
secondSocket.on('connect', () => {
console.log('Connected second time'); // Never happens
});
});
Expected behavior I expected Manager.socket('/someNamespace') to return a socket that connects to the server. Either the old socket that tries to connect again, or a fresh socket.
Additional context The context is a single page application where not all namespaces are relevant to all "pages" in the application, so we disconnect from a page-specific namespace when leaving a page. The reason for explicitly creating a Manager instance is to avoid creating a new underlying connection when we reconnect to the namespace.
Our current workaround is to manually call Socket.open() after Manager.socket().
Thanks, I could indeed reproduce.
Since we cannot have two (or more) active Socket instances for the same namespace and the same Manager, the most reasonable behavior would be to call Socket.connect() on the cached Socket instance, depending on the autoConnect option.
What do you think?
Note: this behavior is present since forever: https://github.com/socketio/socket.io-client/blob/1.0.0/lib/manager.js#L278-L289
I agree, that sounds good.
@darrachequesne is this an easy fix?
This should be fixed by https://github.com/socketio/socket.io-client/commit/b7dd891e890461d33a104ca9187d5cd30d6f76af, included in version 4.6.0.
Please reopen if needed!
@darrachequesne I've noticed that the fix in b7dd891e890461d33a104ca9187d5cd30d6f76af causes another bug. If the socket uses multiplexing, the socket will emit two connection packets to the server. For example:
// Server receives the following connection packet *once*:
// 0/one
let one = io.connect('/one');
// Server receives the connection packet below *twice*:
// 0/two
let two = io.connect('/two');
This effectively creates two connected sockets to the /two namespace on the server. This leads to problems - which I encountered - when doing stuff on the server like
// Server
let nsp = io.of('/two');
nsp.on('connection', socket => {
socket.join('room');
});
// Somewhere later
nsp.to('room').emit('foo', 'bar');
the two socket that was created on the client receives two foo events beause there are actually two connected sockets on the server, but they are the same on the client. I will see if I can create a test case and perhaps a fix for it next week.
The issue is quite critical for me because it means that chat messages in a chat system of mine get delivered twice.
I've been thinking a bit, and I think that the most elegant fix would be to only call socket.connect() again if
- the socket was cached
- the socket is in a disconnected state
So
let socket = this.nsps[nsp];
if (!socket) {
socket = new Socket(this, nsp, opts);
this.nsps[nsp] = socket;
} else if (socket.disconnected && this._autoConnect) {
socket.connect();
}
return socket;
instead of how it's done now, being
let socket = this.nsps[nsp];
if (!socket) {
socket = new Socket(this, nsp, opts);
this.nsps[nsp] = socket;
}
if (this._autoConnect) {
socket.connect();
}
return socket;
@sebamarynissen you are absolutely right, the fix did indeed contain a bug. This should be finally fixed by https://github.com/socketio/socket.io-client/commit/46213a647ea0d4453b00bca09268f69ffd259509, included in version 4.6.1. Could you please check?
@darrachequesne Checked and confirmed that 4.6.1 fixes the bug. Thanks!