Client not receiving event emitted from server (using socket.io and next.js)

148 views Asked by At

I'm trying to implement a chat app using next.js and socket.io. Both the client and server of socket io are on nextjs (server is using api routes).

I want to create a chat app which allows users to create "Rooms", each room can have a maximum of 2 connected clients (the creator of the room and another user). Each room has a password which the user has to enter before accessing the chat box. I have a page pages/room/[id].tsx, this page verifies the password, and then displays the chatbox if password is valid. In this page itself, I initialize a socket.io client and also emit a joinRoom event, after the password of the user is verified.

(pages/room/[id].tsx):

export default function ChatRoomID({
    id
}: {
    id: string
}) {
    const [socket, setSocket] = useState<Socket | null>(null);
    useEffect(() => {
        const socket = io();
        setSocket(socket);

        return () => {
            socket.disconnect();
        };
    }, []);

    function handleSubmit(password: string) {
        // ...verify password
        if (socket) {
            socket.emit(
                'joinRoom',
                id,
                (error: string | string, message: string) => {
                    if (error) {
                        toast({
                            title: 'Error',
                            description: error,
                        });
                    }
                }
            );
        }
    }

    return (
      <Chatbox id={id} socket={socket} />
    )
}

This is the chat box component (the id and socket in the props of this component are passed from the room/[id].tsx page, where the socket is initialized):

export default function ChatBox({ socket }: { socket: Socket | null }) {

  useEffect(() => {
    if (!socket) return;

    console.log(socket.connected) // true
    
    socket.on('message', (data: { message: string; roomId: string }) => { // this event does not emit
      const { message, roomId } = data;
      console.log('Received message:', message); // does not log
      setMessages((prevMessages) => [...prevMessages, message]);
    });

    return () => {
      socket.off('message');
    };
  }, [socket]);

 // the function below is called when a user clicks the send message button
  const handleMessageSend = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    if (socket) {
      const { message } = Object.fromEntries(new FormData(e.currentTarget));
      socket.emit('sendMessage', id, message); // this event emits properly
      e.currentTarget.reset();
      return;
    }
  };
}

Lastly, this is my api router:

export default function handler(req: NextApiRequest, res: CustomApiResponse) {
  if (!res.socket.server.io) {
    const io = new Server(res.socket.server);
    res.socket.server.io = io;

    io.on('connection', (socket: Socket) => {
      console.log('io connection received');

      socket.on(
        'joinRoom',
        async (roomId: string, callback: Function) => {
          const roomDetails = await kv.get(roomId);

          if (!roomDetails) {
            return callback('Room not found', null);
          }

          const roomClients = io.sockets.adapter.rooms.get(roomId);
          if (roomClients && roomClients.size >= 2) {
            return callback('Room is full', null);
          }

          socket.join(roomId);
          callback(null, 'Successfully joined the room');
        }
      );

      socket.on('sendMessage', (roomId: string, message: string) => {
        console.log(`send message emitted`, roomId, message) // this is logged to console
        socket.to(roomId).emit('message', { message, roomId });
      });

    });
  }
  res.end();
}

This is the flow I want to achieve: password verified -> joinRoom event emitted (from client, received successfully by server) -> sendMessage emitted (when message is sent, from ChatBox component, received by server) -> message event emitted to the room from sendMessage (received by the client, which displays the received message in the chatbox).

However, the issue is, the message event is not emitted. I've checked that the socket is still connected. And these are the debug logs:

  socket.io:socket emitting event ["sendMessage","3aa166ee-9c8c-419d-b213-6413bfef5e6f","a"] +1ms
  socket.io:socket dispatching an event ["sendMessage","3aa166ee-9c8c-419d-b213-6413bfef5e6f","a"] +1ms
Send message emitted 3aa166ee-9c8c-419d-b213-6413bfef5e6f a
  socket.io-parser encoding packet {"type":2,"data":["message",{"message":"a","roomId":"3aa166ee-9c8c-419d-b213-6413bfef5e6f"}],"nsp":"/"} +4ms
  socket.io-parser encoded {"type":2,"data":["message",{"message":"a","roomId":"3aa166ee-9c8c-419d-b213-6413bfef5e6f"}],"nsp":"/"} as 2["message",{"message":"a","roomId":"3aa166ee-9c8c-419d-b213-6413bfef5e6f"}] +1ms
  engine:transport readyState updated from closing to closed (polling) +30s
  engine:socket writing ping packet - expecting pong within 20000ms +659ms
  engine:socket sending packet "ping" (undefined) +0ms
  engine:socket flushing buffer to transport +1ms
  engine:ws writing "2" +662ms
  engine:ws received "3" +2ms
  engine:socket received packet pong +3ms
  engine:socket got pong +1ms

(logs attached are of events after sendMessage has been dispatched)

I can't seem to find the issue, the socket is connected, I've double checked that the room id and the socket id are same. Please help. If you also have suggestions to improve this architecture, please share.

Thank you.

1

There are 1 answers

0
FC5570 On

I've fixed it. It was because I was using socket.to(roomId).emit(...) which emits the event to all clients, except the sender, which is why the event was not emitting.

Replacing socket with io (Server) should work, as io.in/io.to brodcast events to all the connected clients, including the sender.

Check https://socket.io/docs/v4/server-api/#serverinroom for more info.