How to leave a socket room with vue-socket and rejoin without duplicate messages?

1.8k views Asked by At

When I join the room, and then leave the route and go back, and then use the chat I've built, I get double messages of * amount of messages as many times I left and rejoined.

This problem goes away when I hard refresh.

I've tried everything I could find thus far, and have been unable to get it to work.

I tried on the client side, during beforeRouteLeave, beforeDestroy and window.onbeforeunload

  • this.$socket.removeListener("insertListener"); --> tried with all
  • this.$socket = null
  • this.$socket.connected = false
  • this.$socket.disconnected = true
  • this.$socket.removeAllListeners()
  • this.$socket.disconnect()

During the same events, I also sent a this.$socket.emit("leaveChat", roomId) and then on the server side tried the following inside the io.on("connection") receiver socket.on("leaveChat", function(roomId) {}):

  • socket.leave(roomId) --> this is what should according to docs work;
  • socket.disconnect()
  • socket.off() -- seems to be deprecated
  • socket.removeAllListeners(roomId)

There were a bunch of other things I tried that I can't remember but will update the post if I do.

Either it somehow disconnects and upon rejoining, previous listeners or something is still remaining, meaning all the messages are received * times rejoin. OR, if I disconnect, I don't seem to be able to reconnect.

On joining, I emit to server the room id and use socket.join(roomId).

All I want to do, is without refresh, when I leave the page, before that happens, the user can leave the room and when they go back, they get to rejoin, with no duplicate messages occurring.

I am currently trying to chew through the source code now.

2

There are 2 answers

0
danefondo On

If you're using Vue-Socket and feel like going slightly mad having tried everything, this may be your solution.

Turns out challenging core assumptions and investigating from the ground up pays off. It is possible that you forgot yourself so deeply in Socket.io, that you forgot you were using Vue-Socket.

The solution in my case was using Vue-Socket's built in unsubscribe function.

With Vue-Socket, one of the ways you can initially subscribe to events is as follows:

this.sockets.subscribe('EVENT_NAME', (data) => {
    this.msg = data.message;
}); 

Because you're using Vue Socket, not the regular one, you also need to use Vue Socket's way for unsubscribing right before you leave the room (unless you were looking for a very custom solution). This is why I suspect many of the other things I tried didn't work and did next to nothing!

The way you do that is as follows:

this.sockets.unsubscribe('EVENT_NAME');

Do that for any events causing you trouble in the form of duplicates. The reason you'd be getting duplicates in the first place, especially upon rejoining post leaving a room, is because the previous event listeners were still running, and now the singular user would be playing the role of as if two or more listeners.

An alternative possibility is that you're emitting the message to everyone, including the original sender, when you should most likely be emitting it to everyone else except the sender (check here for socket.io emit cheatsheet).

If the above doesn't solve it for you, then make sure you're actually leaving the room, and doing so server-side. You can accomplish that through emitting a signal to the server right before leaving the route (in case you're using a reactive single page application), receiving it server side, and calling 'socket.leave(yourRoomName)' inside your io.on("connection", function(socket) {}) instance.

0
proxim0 On

Full disclosure here, I didn't read the full response posed by roberfoenix, but this is a common issue with socket.io and it comes down to calling the 'on' event multiple times.

When you create an .on event for your socket its a bind, and you can bind multiple times to the same event.

My assumption is, when a users hits a page you run something like

socket.on("joinRoom", data)

This in turn will say join the room, pull your messages from Mongo(or something else) and then emit to the room (side note, using .once on can help so you don't emit to every users when a user joings a room)

Now you leave the room, call socket.emit('leaveRoom',room), cool you left the room, then you go back into the room, guess what you now just binded to the same on event again, so when you emit, it emits two times to that user etc etc.

The way we addressed this is to place all our on-events into a function and call the function once. So, a user joins a page this will run the function like socketInit();

The socketInit function will have something like this

function socketInit(){
if (init === false){
//Cool it has not run, we will bind our on events
socket.on("event")
socket.on("otherEvent")

init = true;
}


}

Basically the init is a global variable, if is false, bind your events, otherwise don't rebind.

This can be improved to use a promis or could be done on connect but if a users reconnects it may run again.