Hey guys! I haven't been updating the series here but I have made quite a lot of progress on the Goodeed app. One of the features of this app includes a private chat and what better technology to use for real time communication than Socket.io.
The idea is that if the user wants to know more about the item they want to borrow or they're not comfortable leaving those details such as where and when to meet in the comments, they can choose to message the other privately. So, basically it is a 1-to-1 chat.
There are a few ways to go about creating socket connections and to make a private messaging feature work but after a few attempts, I've decided to do the Rooms route in which a chat will be held in a room where there can only be a maximum of 2 participants. Check out more about Socket.io rooms here.
Socket.io requires both server and client integration to work. The flow would be to create socket instance on the server and upon connection, the socket will have access to events to either listen or receive them. The client does pretty much the same thing, only that the client will connect to the server's port.
const { Server } = require('socket.io');
const io = new Server(res.socket.server);
io.on('connection', socket => {
console.log(socket.id + ' ==== connected');
// creating a room name that's unique using both user's unique username
socket.on('join', roomName => {
let split = roomName.split('--with--'); // ['username2', 'username1']
let unique = [...new Set(split)].sort((a, b) => (a < b ? -1 : 1)); // ['username1', 'username2']
let updatedRoomName = `${unique[0]}--with--${unique[1]}`; // 'username1--with--username2'
Array.from(socket.rooms)
.filter(it => it !== socket.id)
.forEach(id => {
socket.leave(id);
socket.removeAllListeners(`emitMessage`);
});
socket.join(updatedRoomName);
socket.on(`emitMessage`, message => {
Array.from(socket.rooms)
.filter(it => it !== socket.id)
.forEach(id => {
socket.to(id).emit('onMessage', message);
});
});
});
socket.on('disconnect', () => {
console.log(socket.id + ' ==== diconnected');
socket.removeAllListeners();
});
});
So let's walkthrough this a little. I found that a single connection can indeed join multiple rooms which is not what I want. When the user clicks on a chat, we want to emit join
from the client side with a string that has both participants' unique usernames like username1--with--username2. This shows that username1 and username2 has to join a unique room. Since there are two clients here, the roomName
is sorted in a way that is currentUser--with--whoTheUserWantsToChatWith
. So a sort
needs to happen so we generate a unique room name that the two participants can join the same one.
Socket.io automatically creates a unique id for a connected client and that id itself is a room. For example, logging socket.rooms
when a client (let's take username1
) with a socket.id (123123
) is connected will give us
Array.from(socket.rooms) // ['123123']
Before joining a private chat room with username2
, first we need to make sure there is only 1 room which is the socket.id 123123
(like the example above), and no other rooms. If there are other rooms though, we need to leave that room with socket.leave(roomName)
and remove emitMessage
listener.
Then we'll join the client
socket.join(roomName)
console.log(Array.from(socket.rooms) // ['123123', 'username1--with--username2'])
Now it's time to emit a message from the server to the client.
socket.on(`emitMessage`, message => {
Array.from(socket.rooms)
.filter(it => it !== socket.id)
.forEach(id => {
socket.to(id).emit('onMessage', message);
});
});
});
Here I'm listening an emitMessage
event and a message
parameter that client sent over. Now I only want to send the message IN the only room that client is in but not its own socket.id. Once a filter is done, there I am pointing to the unique room and emit an onMessage
event that the client is listening with the message
.
Also, not forgetting to disconnect the socket and removing all listeners once the socket is no longer in use.
On the client side, we're gonna set up event listeners that match the server's. In order to join a room, we would do something like this
// user: username2, anotherUser: username1
socket.emit('join', `${user}--with--${anotherUser}`);
To send a message to the server, the client will need to emit emitMessage
event with the message.
// send message
socket.emit(`emitMessage`, "Hello world!");
To receive message from the server that's being emitted from onMessage
event:
// receive message
socket.on('onMessage', msg => {
console.log(msg) // "Hello world!"
});
Have fun coding :)
Top comments (2)
Thanks Jiayi for this awesome article, I am experiencing a challenge in my react native app, hope you can help out, in the event that I listen for an 'onMessage', which kind of state or condition do I need to pass down to my useEffect inorder for it to update the recvMessages state when a new message is sent.
useEffect(()=>{
// receive message
socket.on('onMessage', message => {
setRecvMessages(prevState => GiftedChat.append(prevState, message));
});
}, [ ]);
As of now i can receive these messages on the other users ends
Sorry, I meant the other user can receive messages