'How to create/join chat room using ws (Websocket) package in node js
I am using ws package in server side and I want the client to create/join rooms in server socket. And to remove them from a created room when they are no longer connected. PS: I don't want to use socketIo.
Solution 1:[1]
You could try something like:
const rooms = {};
wss.on("connection", socket => {
const uuid = ...; // create here a uuid for this connection
const leave = room => {
// not present: do nothing
if(! rooms[room][uuid]) return;
// if the one exiting is the last one, destroy the room
if(Object.keys(rooms[room]).length === 1) delete rooms[room];
// otherwise simply leave the room
else delete rooms[room][uuid];
};
socket.on("message", data => {
const { message, meta, room } = data;
if(meta === "join") {
if(! rooms[room]) rooms[room] = {}; // create the room
if(! rooms[room][uuid]) rooms[room][uuid] = socket; // join the room
}
else if(meta === "leave") {
leave(room);
}
else if(! meta) {
// send the message to all in the room
Object.entries(rooms[room]).forEach(([, sock]) => sock.send({ message }));
}
});
socket.on("close", () => {
// for each room, remove the closed socket
Object.keys(rooms).forEach(room => leave(room));
});
});
This is only a sketch: you need to handle leave room, disconnection from client (leave all rooms) and delete room when nobody is longer in.
Solution 2:[2]
You can create user and room in this way using a socket or without socket
const users = [];//It can be collection(noSQL) or table(SQL)
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase();
room = room.trim().toLowerCase();
const existingUser = users.find((user) => user.room === room && user.name === name);
if(!name || !room) return { error: 'Username and room are required.' };
if(existingUser) return { error: 'Username is taken.' };
const user = { id, name, room };
users.push(user);
return { user };
}
Solution 3:[3]
const port = process.env.PORT || 8000
const WebSocket = require('ws');
function noop() {}
function heartbeat() {
this.isAlive = true;
}
const wss = new WebSocket.Server({ port });
var rooms = {};
const paramsExist = (data) =>{
try {
if('meta' in data && 'roomID' in data && 'clientID' in data && 'message' in data){
return true;
}else{
return false;
}
} catch (error) {
return false;
}
}
const roomExist = (roomID) =>{
// check for room is already exist or not
if(roomID in rooms){
return true;
}else{
return false;
}
}
const insideRoomdataExist = (arr,data) =>{
var status = false;
for(var i =0; i<arr.length;i++){
if(data in arr[i]){
status= true;
break;
}
}
return status;
}
const clientExistInRoom = (roomID,ws,clientID) =>{
var status = false;
const data = rooms[roomID];
for(var i =0; i< data.length ;i++){
var temp = data[i];
// if(roomID in temp){
// status=true;
// console.log("hello world");
// }
for(const obj in temp){
// if(ws == temp[obj]){
if(clientID == obj){
status = true;
break;
}
}
}return status;
}
// create room
const createRoom =(data,ws)=>{
try {
var {roomID,clientID} = data;
const status = roomExist(roomID);
if(status){
ws.send(JSON.stringify({
'message':'room already exist',
'status':0
}));
}else{
rooms[roomID] = [];
var obj = {};
obj[clientID] = ws;
rooms[roomID].push(obj);
ws['roomID']=roomID;
ws['clientID']=clientID;
ws['admin']=true;
ws.send(JSON.stringify({
'message':'room created succesfully',
'status':1
}));
}
} catch (error) {
ws.send(JSON.stringify({
'message':'there was some problem in creating a room',
'status':0
}));
}
}
// join room
const joinRoom = (data,ws) => {
try {
var {roomID,clientID} = data;
// check if room exist or not
const roomExist = roomID in rooms;
if(!roomExist){
ws.send(JSON.stringify({
'message':'Check room id',
'status':0
}));
return;
}
// const inRoom = insideRoomdataExist(rooms[roomID],clientID);
const inRoom = clientExistInRoom(roomID,ws,clientID)
if(inRoom){
ws.send(JSON.stringify({
"message":"you are already in a room",
"status":0
}));
}else{
var obj = {};
obj[clientID] = ws;
rooms[roomID].push(obj);
ws['roomID']=roomID
ws['clientID']=clientID;
ws.send(JSON.stringify({
"message":"Joined succesfully",
"status":1
}));
}
} catch (error) {
ws.send(JSON.stringify({
'message':'there was some problem in joining a room',
'status':0
}));
}
}
// send message
const sendMessage = (data,ws,Status=null) => {
try {
var {roomID, message,clientID} = data;
//check whether room exist or not
const roomExist = roomID in rooms;
if(!roomExist){
ws.send(JSON.stringify({
'message':'Check room id',
'status':0
}));
return;
}
// check whether client is in room or not
const clientExist = clientExistInRoom(roomID,ws,clientID);
if(!clientExist){
ws.send(JSON.stringify({
'message':"You are not allowed to send message",
'status':0
}));
return;
}
const obj = rooms[roomID];
for(i=0;i<obj.length;i++){
var temp = obj[i];
for(var innerObject in temp){
var wsClientID = temp[innerObject];
if(ws!==wsClientID){
wsClientID.send(JSON.stringify({
'message':message,
'status':Status?Status:1
}));
}
}
}
} catch (error) {
ws.send(JSON.stringify({
'message':'There was some problem in sending message',
'status':0
}));
}
}
const leaveRoom = (ws,data) => {
try {
const {roomID} = data;
// manual code started------------------------------------------------------------
const roomExist = roomID in rooms;
if(!roomExist){
ws.send(JSON.stringify({
'message':'Check room id',
'status':0
}));
return;
}
if('admin' in ws){
data['message']="Admin left the room.";
sendMessage(data,ws,Status=2);
delete rooms[ws.roomID]
return;
}
else{
// find the index of object
lst_obj = rooms[roomID];
var index = null;
for(let i=0;i<lst_obj.length;i++){
var temp_obj = lst_obj[i];
for(var key in temp_obj){
var temp_inside = temp_obj[key]
if('admin' in temp_inside){
temp_inside.send(JSON.stringify({
'message':'Somebody leave the room',
'status':3
}));
}
if(ws==temp_inside){
index =i;
}
}
}
if(index!=null){
rooms[roomID].splice(index,1);
console.log((rooms[roomID].length));
}
}
} catch (error) {
ws.send(JSON.stringify({
'message':'There was some problem----------------------',
'status':0
}))
}
}
const available_room = (ws) =>{
try {
var available_room_id=[];
for(var i in rooms){
available_room_id.push(parseInt(i));
}
ws.send(JSON.stringify({
"rooms":available_room_id,
"status":4
}))
} catch (error) {
ws.send(JSON.stringify({
'message':'There was some problem----------------------',
'status':0
}))
}
}
wss.on('connection', function connection(ws) {
try {
ws.on('message',(recieveData)=>{
var data = JSON.parse(recieveData);
const error = paramsExist(data);
if(!error){
ws.send(JSON.stringify({
'message':'check params',
'status':0
}));
return;
}
var {roomID,meta} = data;
switch (meta) {
case "create_room":
createRoom(data,ws);
console.log(rooms);
break;
case "join_room":
joinRoom(data,ws);
console.log(rooms);
break;
case "send_message":
sendMessage(data,ws);
console.log(rooms);
break;
case "show_all_rooms":
ws.send(JSON.stringify({
"rooms":[rooms]
}))
break;
default:
ws.send(JSON.stringify({
"message":"Unsupported meta data provided provide valid data",
"status":0
}));
break;
}
})
ws.on('close', function(data) {
leaveRoom(ws,{roomID:ws.roomID,clientID:ws.clientID,message:"Leave request"})
ws.terminate();
});
ws.on('pong', heartbeat);
} catch (error) {
ws.send(JSON.stringify({
"message":"there was some problem",
"status":0
}))
}
});
const interval = setInterval(function ping() {
var a = wss.clients;
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) {
leaveRoom(ws,{roomID:ws.roomID,clientID:ws.clientID});
ws.terminate();
}
ws.isAlive = false;
ws.ping(noop);
});
}, 50000);
const serverFree = setInterval(()=>{
var removeKey = [];
for(const obj in rooms){
if(rooms[obj].length<1){
removeKey.push(obj);
}
}
for(var i =0; i<removeKey.length;i++){
delete rooms[removeKey[i]];
}
},30000)
Hello there above is the code for socket connection using was. All you need to do is provide JSON from the frontend in the meta key. There are 4 params that you need to send from the frontend in JSON using the socket library.
Required keys -meta:"create_room"/"join_room"/"send_message"/"show_all_rooms" -message:"anything" -roomID:"roomid to create or join" -clientID:"unique client id"
package.json file dependies
{
"name": "nodesocket",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/app.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"ws": "^7.4.6"
}
}
Solution 4:[4]
I would suggest using Map that stores a channel and an array of WS instead of the array that registers users' rooms.
If you have a lot of users this would be a better approach for sending messages (I implemented these kinds of WS for my company) that can handles easily 40k to 50k (for each node of WS) and send thousands of messages per second.
You can broadcast messages easily (by just getting a room from your Map).
The Map is like this: Map<String,[]> (room, ws array)
.
And when the user joins a new room just add it into the room's array when he left removes it. (/!\ do not forget to ping users in both directions)
For sending messages to a room just call (avoid calling it without checking if the room exists by using map.has(channel)
).
map.get(room).forEach(ws => ws.send(yourMessage))
It also helped me with linear scaling. This array is external from my wss
and ws
so while a message is published from REDIS (or anything else), I just put a callback that send the message to the corresponding room. So when you broadcast messages it does not check all the users' rooms.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Talha Noyon |
Solution 3 | Rinkesh panwar |
Solution 4 | VLAZ |