Add a private chat feature in my application (Node js) - node.js

I have recently started learning about Node js and developed a Realtime chat application where whoever is connected to the server they can have a chat but now I want to add a feature of private chat between two users only so how can I achieve it? I have read about the room functionality of socket.io but by that I can get that it's a room and in that room there can be many users and they can have chat but it's not personal means many users can join the same chat. I need to know how to implement chat feature where two people can have a chat no third person can enter that particular chat. My question is different than other questions present here as I want to implement both the functionalities I need group and private chat both in one application.
My idea is in my group chat functionality username and message are displayed so if one user clicks on username then the person can start their personal private chat no third person can join it.
Here I am sharing my code snippets for your reference
Server.js
const io = require('socket.io')(http)
io.on('connection', (socket) => {
console.log('Connected...')
socket.on('message', (msg) => {
socket.broadcast.emit('message', msg)
})
})
Client.js
const socket = io()
let name;
let textarea = document.querySelector('#textarea')
let messageArea = document.querySelector('.message__area')
do {
name = prompt('Please enter your name: ')
} while(!name)
textarea.addEventListener('keyup', (e) => {
if(e.key === 'Enter') {
sendMessage(e.target.value)
}
})
function sendMessage(message) {
let msg = {
user: name,
message: message.trim()
}
// Append
appendMessage(msg, 'outgoing')
textarea.value = ''
scrollToBottom()
// Send to server
socket.emit('message', msg)
}
function appendMessage(msg, type) {
let mainDiv = document.createElement('div')
let className = type
mainDiv.classList.add(className, 'message')
let markup = `
<h4>${msg.user}</h4>
<p>${msg.message}</p>
`
mainDiv.innerHTML = markup
messageArea.appendChild(mainDiv)
}
// Recieve messages
socket.on('message', (msg) => {
appendMessage(msg, 'incoming')
scrollToBottom()
})
function scrollToBottom() {
messageArea.scrollTop = messageArea.scrollHeight
}
Please help me out!

when a new user join keep that socket id, and while sending message send that unique userid and emit message to that socket only,
const io = require('socket.io')(http)
var user={};
io.on('connection', (socket) => {
console.log('Connected...')
socket.on('join', (userid) => {
users[userid]=socket.id;
});
socket.on('privateMessage', (data) => {
io.sockets.socket(users[data.to]).emit('message', data.msg);
});
socket.on('publicMessage', (msg) => {
socket.broadcast.emit('message', msg)
});
});

Related

socket emits does not do anything, no messages are reflected in ReactsJs UI

i am a newbie in socket.io, i apologize for any bad practices or outright errors in code below, any help would be really appreciated.
i am trying to make a simple chats app where two people can join a room and chat with each other using Socket.io and Reactjs + Nodejs
my server file is as below
io.on("connect", (socket) => {
console.log(socket.id, " connected"); //prints
socket.on("disconnect", () => {
console.log(socket.id, " Disconnected"); //prints
});
socket.on("join", ({ room, username }) => {
console.log(`${username} joined room ${room}`) //prints
socket.on("message", (payload) => {
console.log(`${username} says - ${payload.message}`); //prints
socket.emit("message", payload) //does not reflect in front-end
})
socket
.to(room)
.emit("message", {
message: `${username} has joined the Room`,
isToast: true,
}); //does not reflect on front-end
socket.emit("message", {
message: `Hello ${username}, Welcome to Room ${room}`,
isToast: true,
}); //does not reflect on front-end
});
});
my frontend is as following
const Home = ({ socket }) => {
const msgRef = useRef();
const { setSession } = useSessionContext();
const [msg, setMsg] = useState([]);
socket.on("message", (payload) => {
// setMsg(prev => [...prev, payload]); //remains empty for some reason
console.log(payload); //prints correctly only when user himself sends, texts from others dont reflect
});
const handleMsgSend = (e) => {
e.preventDefault();
const newText = msgRef.current.value;
socket.emit("message", { message: newText, isToast: false })
e.target.reset();
};
.
.
.
.
thankyou for your time, i have been banging my head on this since days, rage quitting and trying again in the verge of tears.
Expected behavior :-
user connects - xyz connected (log)
on room join - xyz joined room abc (log)
xyz sends text - xyz says qwerty (log)
text gets emited to other connected user including sender via message
xys has joined room get emited to all users in room except the sender via message
hello xyz welcome to room abc gets emited to only sender via message

How to send message to specific user using socket_io_client flutter package

I am trying to build an chat apps using socket.io and node.js for backend and flutter for frontend... so far I have been trying to send message to all connected user, is there a way to send message to specific user? here is part of my backend code
io.on('connection', (socket) => {
console.log(`id: ${socket.id}`)
socket.on('send_message', (msg) => {
var detail = JSON.parse(msg)
socket.in(detail["receiver"]).emit('to_user', msg)
})
});
in flutter I am using socket_io_client package (https://pub.flutter-io.cn/packages/socket_io_client) but I don't know how to emit message for specific user
here is part of code for frontend
StreamController<String> _data = StreamController<String>();
socket.on('send_message', (x) {
_data.sink.add(x);
});
sendChat(String msg, String sender, String receiver) {
Map map = {"msg": msg, "snd": sender, "rcv": receiver};
var mapbody = json.encode(map);
socket.emit('send_message', mapbody);
}
Stream<String> get sendChat => _data.stream;
you have to have the socket.id and use io.sockets.socket(SocketId).emit(msg) to send message
var express = require("express");
var redis = require("redis");
var sio = require("socket.io");
var client = redis.createClient()
var app = express.createServer();
var io = sio.listen(app);
io.set("store", new sio.RedisStore);
// In this example we have one master client socket
// that receives messages from others.
io.sockets.on('connection', function(socket) {
// Promote this socket as master
socket.on("I'm the master", function() {
// Save the socket id to Redis so that all processes can access it.
client.set("mastersocket", socket.id, function(err) {
if (err) throw err;
console.log("Master socket is now" + socket.id);
});
});
socket.on("message to master", function(msg) {
// Fetch the socket id from Redis
client.get("mastersocket", function(err, socketId) {
if (err) throw err;
io.sockets.socket(socketId).emit(msg);
});
});
});
some options you have here.
first :
you can store your connected clients ids to redis .
use io-redis :
and store your client id in it:
for storing ids in redis:
await redisClient.lpush(`socket_members`, socket.id);
for getting the specefic id:
let client = await redisClient.lrange(
`socket_members`,
0,
socket.id
);
the second option is you can create an authentication middleware
const jwt = async(socket, next) => {
let token = socket.handshake.query.token
let verify = jwt.verify(socket.handshake.query.token, jwt_encryption, async(err, decoded) => {
// find user with decoded token
if (!user) {
return next(new Error(JSON.stringify({ status: 401, message: 'unuthorized' })));
}
socket.user = user._doc
return next()
})
};
and use it in socket io instance :
io.use(jwt);
the authenticated user is in socket.user.
hope this would help

How to listen to socketIO private message in React client?

I have a SocketIO instance in an Express app, that listens to a React client requests. A user can send private messages to a specific person. The server receives the private message, and should dispatch it back to both sender & recipient thanks to the io.to(socketId).emit(content) method.
How to listen to this event in React and update the message array? In order to ease the process, I have created a connectedUsers object, whose keys are mongoDB's user._id, and whose values are the unique socketID generated by socketIO. This way, I can easily address message to specific persons in the client. Once sent, the messages are stored in a MongoDB database.
Here is the back-end. The point of interest is io.on("privateMessage")
const connectedUsers = {};
const socketManager = (io) => {
io.on("identifyUser", (user) => {
if (!([user.id] in connectedUsers)) {
connectedUsers[user.id] = io.id;
}
});
io.on("privateMessage", (data) => {
io.to(connectedUsers[data.recipientId]).emit(data.message);
io.to(connectedUsers[data.senderId]).emit(data.message);
});
io.on("disconnect", () => console.log("user disconnected!"));
};
Here is the listening function in React. Everything works but the "privateMessage" part.
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connect", () => {
socket.emit("identifyUser", { id: res.data._id });
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
});
} catch (err) {
throw new Error(err);
}
}
Thanks for your help!
I think you need to put the socket.on("privateMessage") part outside the socket.on("connect") scope.
React must load all events at the beginning.
The backend side must be responsible for the authorization.
For the client there is connection event, not connect.
Subscription to event privateMessage should be outside connection callback.
This code should work. Hope this helps
import io from 'socket.io-client'
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connection", () => {
socket.emit("identifyUser", { id: res.data._id });
});
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
} catch (err) {
throw new Error(err);
}
}

Socket.io - Cannot emit to a specific room but can emit to everyone

I am using Socket.io 2.0.4 and React.js (CRA)
Background: In my server code (server.js) once someone sucessfully joins a room I want to emit an event that tells all clients in that room that someone joined.
Problem: I don't get an error but nothing get's transmitted to my client if I try to use .to(room) or .in(room) in conjuction with .emit... but .emit will work on it's own.
What Works: I am successfully able to implement to the socket.join() code and in the callback I console.log the IDs of each person that's joined using the showClients function I created. I can see each person join one at a time via console.
Notes: I store the room name in the data variable and access it using data.room but I've also just wrote in the room name manually to no avail.
Client Code (abridged)
import React, {Component} from 'react';
import Card from '../card/card';
import './game.css';
const io = require('socket.io-client');
const socket = io()
class Game extends Component {
constructor(props){
super();
}
componentDidMount(){
this.gameCode();
this.cardSelected();
socket.on("cardDiscarded", this.updateDiscard);
socket.on("playerJoined", () => {
alert("hi!");
console.log("YAY!!!!!!!!!!!!!!!!!!!!");
});
}
//....rest of my code....
}
Server Code (abridged)
Look at the joinRoom function to see the issue
const express = require('express');
const app = express();
const port = process.env.PORT || 5000;
const http = require("http").Server(app);
var io = require("socket.io")(http);
var bodyParser = require('body-parser');
console.log("Hello world!");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
io.on("connection", (socket) => {
socket.on("createNewRoom", (name) =>{
CreateNewRoom();
console.log("NEW ROOM CREATED!");
})
socket.on("joinRoomRequest", (name, room) =>{
// console.log(name, room);
console.log("Request to join room");
var data = {
name: name,
room: room
};
joinRoom(data);
})
function CreateNewRoom() {
//Get unique room number
var thisGameId = (Math.random() * 1000000 ) | 0;
//Send the room number to the browser
socket.emit('newRoomCreated', {roomID: thisGameId, mySocketID: socket.id});
//Tell socket.io this user is joining this room
socket.join(thisGameId.toString());
console.log(thisGameId);
};
function joinRoom(data){
console.log("trying to join room:" + data.room);
data.socketID = socket.id;
console.log(data);
socket.join(data.room, () => {
let rooms = Object.keys(socket.rooms);
//Let the clients know a player has joined
console.log(rooms);
console.log(data.name + " JOINED room " + data.room);
showClients(data.room);
io.to(data.room).emit("playerJoined"); //<----- DOESN't WORK
});
}
function showClients(room){
var roomClients = io.of('/').in(room).clients((err, data)=>{
if (err) throw err;
console.log("The people in room ", room, " are: ", data);
})
}
})
I managed to solve it like this:
does not work:
socket.join (my_room)
work:
socket.join (my_room.toString ())
I believe io.in(room).emit(data) is what you are looking for. I recently ran into this problem as well. According to the documentation, if you want to emit to everyone accept 'socket' (aka the user who joined) you use socket.to(room).emit. If you want to emit to everyone including the user, you use io.in(room).emit

Socket.io: How to correctly join and leave rooms

I'm trying to learn Socket.io by building a set of dynamically created chatrooms that emit 'connected' and 'disconnected' messages when users enter and leave. After looking at a couple of questions I've put together something functional but most of the response linked are from people who admit they've hacked together answers and I've noticed there's a more general - and recent - discussion about the right way to do this on the Socket.io repo (notably here and here)
As I'm such a novice I don't know if the work below is an acceptable way to do things or it just happens to incidentally function but will cause performance issues or result in too many listeners. If there's an ideal - and official - way to join and leave rooms that feels less clunky than this I'd love to learn about it.
Client
var roomId = ChatRoomData._id // comes from a factory
function init() {
// Make sure the Socket is connected
if (!Socket.socket) {
Socket.connect();
}
// Sends roomId to server
Socket.on('connect', function() {
Socket.emit('room', roomId);
});
// Remove the event listener when the controller instance is destroyed
$scope.$on('$destroy', function () {
Socket.removeListener('connect');
});
}
init();
Server
io.sockets.once('connection', function(socket){
socket.on('room', function(room){ // take room variable from client side
socket.join(room) // and join it
io.sockets.in(room).emit('message', { // Emits a status message to the connect room when a socket client is connected
type: 'status',
text: 'Is now connected',
created: Date.now(),
username: socket.request.user.username
});
socket.on('disconnect', function () { // Emits a status message to the connected room when a socket client is disconnected
io.sockets.in(room).emit({
type: 'status',
text: 'disconnected',
created: Date.now(),
username: socket.request.user.username
});
})
});
Socket.IO : recently released v2.0.3
Regarding joining / leaving rooms [read the docs.]
To join a room is as simple as socket.join('roomName')
//:JOIN:Client Supplied Room
socket.on('subscribe',function(room){
try{
console.log('[socket]','join room :',room)
socket.join(room);
socket.to(room).emit('user joined', socket.id);
}catch(e){
console.log('[error]','join room :',e);
socket.emit('error','couldnt perform requested action');
}
})
and to leave a room, simple as socket.leave('roomName'); :
//:LEAVE:Client Supplied Room
socket.on('unsubscribe',function(room){
try{
console.log('[socket]','leave room :', room);
socket.leave(room);
socket.to(room).emit('user left', socket.id);
}catch(e){
console.log('[error]','leave room :', e);
socket.emit('error','couldnt perform requested action');
}
})
Informing the room that a room user is disconnecting
Not able to get the list of rooms the client is currently in on disconnect event
Has been fixed (Add a 'disconnecting' event to access to socket.rooms upon disconnection)
socket.on('disconnect', function(){(
/*
socket.rooms is empty here
leaveAll() has already been called
*/
});
socket.on('disconnecting', function(){
// socket.rooms should isn't empty here
var rooms = socket.rooms.slice();
/*
here you can iterate over the rooms and emit to each
of those rooms where the disconnecting user was.
*/
});
Now to send to a specific room :
// sending to all clients in 'roomName' room except sender
socket.to('roomName').emit('event', 'content');
Socket.IO Emit Cheatsheet
This is how I inform users of a "disconnecting user"
socket.on('disconnecting', function(){
console.log("disconnecting.. ", socket.id)
notifyFriendOfDisconnect(socket)
});
function notifyFriendOfDisconnect(socket){
var rooms = Object.keys(socket.rooms);
rooms.forEach(function(room){
socket.to(room).emit('connection left', socket.id + ' has left');
});
}
Here is a working method.
I am using socketio "socket.io": "^2.3.0" on server side. On Client side is android
// SocketIO
implementation('io.socket:socket.io-client:1.0.0') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
Following is the code that is working for me.
// Join Chat Room
socket.on('ic_join', function(data) {
// Json Parse String To Access Child Elements
let messageJson = JSON.parse(data)
let room1 = messageJson.room1
console.log('======Joined Room========== ')
console.log(room1)
socket.join(room1, function(err) {
console.log(io.sockets.adapter.rooms[room1].length);
console.log(err)
})
})
// Leave Chat Room
socket.on('ic_leave', function(data) {
// Json Parse String To Access Child Elements
let messageJson = JSON.parse(data)
let room1 = messageJson.room1
console.log('======Left Room========== ')
console.log(room1)
socket.leave(room1, function(err) {
if (typeof io.sockets.adapter.rooms[room1] !== 'undefined' && io.sockets.adapter.rooms[room1] != null) {
console.log(io.sockets.adapter.rooms[room1].length);
console.log(err)
} else{
console.log("room is deleted")
}
})
})
For anyone reading this beyond 2/1/2021, using socket.io 3.1.0 and hopefully later, you can reference my example. I have found that the example on how to do this in socket.io's documentation is incorrect. They claim that in the disconnect event that socket.rooms is an object. While is uses the block container and is comma separated, there are no key pairs, meaning their demonstration of const rooms = Object.keys(socket.rooms) returns an empty value. It's creating an array out of an object that is really just an array. To my knowledge {} can only be used for block statements and objects. By some quirk, NodeJS is treating it like a normal array. I assign custom, 4 digit rooms on each connect event. So I have on the disconnect event I have the server skim through all the rooms, and if it encounters a room name with a length of 4, it tells everyone in the room that the size of the room decreased by one. On the client side, I have a socket event listener monitoring for this and when it's detected, updates a innerHTML property so that the clients can display the number of connected users.
//client side code:
socket.on('roomSize', (roomSize) => {
document.getElementById('clientCount').innerHTML = roomSize + ' clients connected';
});
//Server side code:
io.on("connection", (socket) => {
socket.on('createRoom', () => {
let ID = makeID(4)
while (io.sockets.adapter.rooms.has(ID)) {
ID = makeID(4)
}
socket.join(ID);
socket.emit('setID', ID);
});
socket.on("joinRoom", (room) => {
socket.join(room);
let roomSize = io.sockets.adapter.rooms.get(room).size
io.in(room).emit('roomSize', roomSize);
socket.emit('roomJoined', room, roomSize);
});
socket.on('disconnecting', function() {
let rooms = socket.rooms;
rooms.forEach(function(room) {
if (room.length === 4) {
let roomSize = io.sockets.adapter.rooms.get(room).size - 1
io.in(room).emit('roomSize', roomSize);
}
});
});
function makeID(length) {
var result = '';
var characters = '0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
})

Resources