Search code examples
node.jsreactjssocket.iomern

Socket.io not joining user to given room


I'm in a bit of a pickle. Not sure what I'm doing wrong here.

The abstract logic is like this:

  1. Show users are online only when they are part of a chat. A user can be part of many chats and multiple users can be part of one chat.
  2. When I initially connect, I send all of the chats that the user is part of.

Here is the issue. I have one user create a chat and add the second user to it. When I refresh either of the pages of the users, only one of them gets added to the room. If I refresh the first users page, then the second user gets added to the room, and vice versa.

At no point do both of them get added to the same room.

Here are screenshots of my implementation.

Server:

const sockets = new Map<string, number>();

io.on('connection', (socket) => {
  // console.log({sockets: io.sockets.adapter.sids});
  socket.on('newUserJoined', async (data: { chatIds: number[], user: number }) => {
    if(!data.chatIds || !data.user ) return;
    sockets.set(socket.id, data.user);
    socket.join(data.chatIds.map(t => `room${t}`));
    // console.log('connect', {user: data.user, date: new Date()})
    // console.log({ rooms: data.chatIds.map(t => `room${t}`) });
    console.log('Connecting socket', { id: socket.id, user: data.user, chatIds: data.chatIds });
    io.to(data.chatIds.map(t => `room${t}`)).emit('onlineUsers', [data.user]);
  });

  socket.on('disconnect', () => {
    let rooms = Array.from(io.sockets.adapter.sids.get(socket.id) ?? []);
    for(var room in rooms) {
      socket.leave(room);
    }
    console.log('Disconnecting socket', socket.id);
    sockets.delete(socket.id);
    // console.log({onlineUsers: Array.from(sockets.values())});
    io.to(rooms).emit('onlineUsers', Array.from(sockets.values()));
    // console.log('disconnect', {onlineUsers, date: new Date()})
  });

  socket.on('sendMessage', (message: { text: string, chatId: number, senderId: number }) => {

    io.to(`room${message.chatId}`).emit('getMessage', message);
  });

});

Server side for sending and listening to events

Server explanation:

  1. I'm keeping a Map of all connected sockets. It's a structure of <socket_id, user_id>.
  2. When I disconnect from a socket, I go through all of the rooms that the socket was part of. Then I delete the socket from the Map. And at the end emit "onlineUsers" so other users can see that someone disconnected.

Client:

  useEffect(() => {
    if(!socket || !user?.id || !chatResponse || chatResponse.data.length == 0) return;
    console.log({chatResponse});
    socket.emit("newUserJoined", { chatIds: chatResponse?.data.map((c: ChatFull) => c.id), user: user.id });

    socket?.on('onlineUsers', (ids: number[]) => {
      console.log('Online users', {ids});
      setOnlineUsers([...onlineUsers, ...ids]);
    });
    return () => {
      socket.off('onlineUsers');
    }
  }, [chatResponse]);

Client chat for listening to events

Client explanation:

  1. "chatResponse" is the response from the backend that gives all the chats in which the logged in user is part of.
  2. When all the chats are loaded I emit it as "newUserJoined" so that the other users of that chat can see who is online.
  3. "onlineUsers" receiving emit just receives a list of users that are already online.

Any help on this? Maybe my approach is wrong to the problem. I can't really find a guide when there are multiple chats, that can exist during connection, and multiple users in any given chat.


Solution

  • In the newUserJoined event handler, you are only sending the just logged-in user. You should send all of them. Change this line

    io.to(data.chatIds.map(t =› 'room${t')).emit('onlineUsers', [data.user]);
    

    to

    io.to(data.chatIds.map(t =› 'room${t')).emit('onlineUsers', Array.from(sockets.values()));