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.
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, asio.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.