Sending a message to a user that is not connected? - node.js

I'm trying to create a user to user chat application - no group chat or anything.
I'm using NodeJS and Socket.io on the backend and React Native on the frontend.
I ended up having a Map that stores a user id and it's corresponding socket id, my problem is that only when a user connects to the server, he will get a socket id.
But what if User A is connect and is trying to send a message to User B, and User B is not connected, so it does not have a socket id, I don't really know what to do then.
This is what I got so far:
io.on("connection", (socket: Socket) => {
//Whenever a user will connect, the user will emit 'initiate' to register itself and it's socket id to the server.
//We will be using this userSocketMap to send messages between users.
socket.on(SocketEvents.INITIATE, (data) => {
const uid = data.uid;
const socketID = socket.id;
userSocketMap.set(uid, socketID);
});
//This function will get called when a user is sending message to another user.
socket.on(SocketEvents.SEND, (data) => {
const to = data.to;
const from = data.from;
const content = data.content;
const payload: MessagePayload = {
to: to,
from: from,
content: content,
};
const dstUser = userSocketMap.get(to); // getting the socket id of the receiver.
// '/#' is the prefix for the socketID, if the socketID already has this prefix, this should be deleted - need to check this.
//MessageEvent.RECEIVE is used to signal the dstUser to receive a message.
io.to("/#" + dstUser).emit(SocketEvents.RECEIVE, { payload: payload });
});
socket.on(SocketEvents.DISCONNECT, (socket: Socket) => {
const userID = getByValue(socket.id);
if (!userID) return;
userSocketMap.delete(userID);
});
});

You should do two things when working with react-native and socket.io in case user lost internet connection. Use socket.io heartbeat mechanism inorder to get the users that lost connection and are not responding and user NetInfo package to inform the mobile user that he has lost internet connection.
Socket.io
var server = app.listen(80);
var io = socketio(server,{'pingInterval': 2000});
io.on("connection", (socket: Socket) => {
socket.on(SocketEvents.INITIATE, (data) => {
const uid = data.uid;
const socketID = socket.id;
userSocketMap.set(uid, socketID);
})
socket.on('heartbeat', (socket: Socket) => {
const userID = getByValue(socket.id)
userSocketMap.MARK_USER_AS_INACTIVE(userID)
})
});
React-Native - use NetInfo - it used to be part of the core but got separated to a community module
import NetInfo from "#react-native-community/netinfo";
NetInfo.fetch().then(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
});
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
});
// Unsubscribe
unsubscribe();

Related

Remembering a client even after they disconnect nodeJS socket.io

I have a chat app and currently, the client is saving its name in a text file, this works fine for windows but mac has some weird directory settings so it makes it harder to read the text file. I'm wondering if it's possible that when a client connects my server saves their IP or some sort of constant data about the client so when the client connects again I can know who it is and assign the name accordingly.
I'm using nodeJS socket.io
First thing that comes to my mind is to make the client responsible for identifying itself by sending a generated UUID to server after connecting
socket.on("connect", () => {
const CLIENT_UUID = 'uuid';
// Get client UUID from local storage
let clientUuid = localStorage.getItem(CLIENT_UUID);
// Check whether if this is a new client and it doesn't have UUID
if (!clientUuid) {
// Then generate random UUID, you'll need to implement `generateRandomUuid`
clientUuid = generateRandomUuid();
// Then save it to local storage
localStorage.setItem(CLIENT_UUID, clientUuid);
}
// Then just emit the connected event
// This will be the actual connection event as once this is emitted and received
socket.emit('connected', {
uuid: clientUuid
});
});
Now on server you can handle the client based on it's UUID
io.on('connection', (socket) => {
// Here you handle the `connected` event
socket.on('connected', (clientUuid) => {
// And now you can handle this clientUuid
const chats = getCurrentClientChats(clientUuid);
const groups = getCurrentClientGroups(clientUuid);
socket.emit('current-chats', { chats });
socket.emit('current-groups', { groups });
});
});
Notice that way, you'r client is now actually known on the connected event
Same approach but probably cleaner is to send the client UUID upon connecting to the server, since it's really easy to do that with socket IO
const socket = io("ws://example.com/my-namespace", {
query: { clientUuid }
});
And just use it upon connection
io.on('connection', (socket) => {
const clientUuid = socket.handshake.query.name;
const chats = getCurrentClientChats(clientUuid);
const groups = getCurrentClientGroups(clientUuid);
socket.emit('current-chats', { chats });
socket.emit('current-groups', { groups });
});

How to discard incoming event in Socket.io-client

Hi I am trying to make a tracker app and I'm using socket.io for both server and client. On my client app, I want to disregard message event whenever my browser is not on focus. my code is like this for the client app :
const socket = io('http://localhost:4000');
const [browserState, setBrowserState] = useState('');
useEffect(() => {
socket.on('connect', () => {
console.log('connected');
socket.on("message", payload => {
//payload need to be passed to Map component
console.log(payload);
});
});
},[]);
useEffect(() => {
document.onvisibilitychange = function(){
setBrowserState(document.visibilityState)
}
if(browserState === 'hidden') socket.volatile.emit("message", payload => payload)
},[browserState]);
and on my server is just simply:
io.on('connection', socket => {
socket.on('message', (payload)=>{
console.log(payload)
io.emit('message', payload)
});
The problem is on the client-side for the code socket.volatile.emit("message", payload => payload). if I use socket.volatile.on it's working. but I still receive message event on the client. if I use socket.volatile.emit the server is crashing.
Additional Question: is it okay if my client side io.protocol = 5 and my server is io.protocol = 4?
I'm really new to this. Please advise. :) Thanks!
It can be discarded easily by not replying to incoming sockets
io.on('connection', socket => {
socket.on('message', (payload)=>{
console.log(payload)
// removed this line io.emit('message', payload)
});

Ionic 4 chat application and how to send message to particular user?

I created application in Ionic 4, and for backend I use Lumen. Application should have chat page and in that purpose I add Redis, Socket.io and nodejs. I successfully created public room, and chat between users in that room works. Problem is how to send private message for user, how to initialize users for their private room.
This is how I created public room:
constructor(private socket: Socket) {
this.getMessages().subscribe(message => {
this.messages.push(message);
});
}
getMessages() {
const observable = new Observable(observer => {
this.socket.on('message', (data) => {
observer.next(data);
});
});
return observable;
}
I send message from Lumen application and Redis:
public function sendMessage()
{
$redis = Redis::Connection();
$sendMessage = json_encode(['user' => 'John Doe', 'text' => 'Some message, text', 'channel' => 'message']);
$redis->publish('add-message', $sendMessage);
}
And my node server is:
let express = require('express');
let app = express();
let http = require('http').Server(app);
let redis = require('redis');
let client = redis.createClient("redis://127.0.0.1:6379");
let io = require('socket.io')(http);
app.use('/', express.static('www'));
http.listen(3000, '192.168.10.10', function(){
console.log('listening on *:3000');
});
client.on('message', function(chan, msg) {
let data = JSON.parse(msg);
io.sockets.emit(data.channel, msg);
});
client.subscribe('add-message');
Bottom line everyone who is subscribed on 'message' channel will get message. Problem is that I subscribe user on channel when came to chat page. I don't know how to subscribe user, and when, on channel where some another user send him message.Also how that sender user to create new room, I suppose to use id of users for room name (per instance user1_user2).
Does anyone know how I can solve this problem? I don't know even I described well.
Thanks in advance

Socket.io keeps multiple connected sockets alive for 1 client

I am using Socket.io to connect a React client to a Node.js server and the query option in socket.io to identify uniquely every new client. However, the server creates multiple sockets for every client and, when I need to send something from the server, I don't know which socket use, because I have more than one, and all of them are connected.
The client code:
import io from "socket.io-client";
...
const socket = io(process.env.REACT_APP_API_URL + '?userID=' + userID, { forceNew: true });
socket.on('connect', () => {
socket.on('new-order', data => {
const { add_notification } = this.props;
add_notification(data);
});
The server code:
....
server = http
.createServer(app)
.listen(8080, () => console.log(env + ' Server listening on port 8080'));
io = socketIo(server);
io.on('connection', socket => {
const userID = socket.handshake.query.userID;
socket.on('disconnect', () => {
socket.removeAllListeners();
});
});
And here the server-side that emits events to the client:
for (const socketID in io.sockets.connected) {
const socket = io.sockets.connected[socketID];
if (socket.handshake.query.userID === userID) {
// Here, I find more than one socket for the same condition, always connected.
socket.emit(event, data)
}
}
Here, it is possible to see all these socket for the same client:
I tried to send events for all socket from a given userID, however, multiple events are triggered to the client, showing duplicated data to the user. I also tried to send events to the last socket, but, sometimes it works, sometimes doesn't.
Someone have a clue how to uniquely identify a socket when there are several clients?

How to send file to a particular user in nodejs

I am creating a rest api on nodejs. I have email id and user id in database. I want to share a file (present on same server) from one user to a particular user. Can anyone tell me how this can be done in a best way ?
Here is the code i have tried yet.
const server = require('./../server/server.js')
const socketIO = require('socket.io');
const io = socketIO(server);
const mongoose = require('mongoose');
const User = require('../models/user.js');
let sockets = [];
io.on('connection', socket=>{
console.log("User connected");
socket.on('online', (data)=>{
socket.name = data._id;
sockets[data._id] = socket.id;
console.log("user is online")
})
socket.on('send_file', (data)=>{
User.find({emailId: data},{emailId:0, password:0})
.exec()
.then(userid => {
if(userid.length<1){
console.log("No such user");
}
else{
console.log(userid[0].id);
socket.to(sockets[userid[0].id]).emit('hello', "HELLO");
}
})
.catch(err =>{
console.log(err);
});
});
socket.on('disconnect', ()=>{
console.log("User disconnected");
})
})
module.exports = io;
server.listen('8080', (err)=>{
if(err) throw err;
console.log("running on port 8080");
});
Assuming that you have already configured the socketio and express sever properly with he mechanism to save the file path and file name in you database.
Try something like this (with socketio)
let sockets = [];
io.on("connection", function(socket) {
socket.on("online", data => {
socket.name = data.username;
sockets[data.username] = socket.id;
});
socket.on("send_file", function(data) {
// your logic to retrieve the file path from you database in to the variable **filedata**
// let filedata = ................
socket.to(sockets[data.user]).emit("file",filedata);
});
socket.on("disconnect", reason => {
sockets.splice(sockets.findIndex(id => id === socket.id), 1);
});
});
in send_file event you will have receive the username from the sender inside the data object. The following code will be one which will help you to send file to selected user.
socket.to(sockets[data.user]).emit("file",filedata);
Replying to your 1st comment.
history.push() will not refresh the client since its a single page application.
But when you refresh(from user A side) a new socket session will be created then the other user(user B) will still be referring the old socket session(which is already being disconnected by the refresh). So to handle this use the following lines
socket.on("online", data => {
socket.name = data.username;
sockets[data.username] = socket.id;
});
where you will be keeping a pool(an array) of sockets with the usernames so when ever a user refresh their client the newly created socket will be referring to the same user. Since you will be updating the the socket.id to the same person.
For example assume that you the user who refresh the client and im the other user. so when you refresh a new socket session will be created an it will be sent to the back end along with the user name. When the data comes to the server it will get your session object from the array(sockets[data.username]) and update it with the new socketio sent from your front-end sockets[data.username] = socket.id;.
for this to happen you will have to send the user name along with the socket message. like this
socket.emit("online", {
username: "username"
});
Replying to your 2nd comment
To send data in real time the users should be online. if not you can just create an array of notifications with the following information (sender and receiver). So when the receiver logs in or clicks on the notification panel the list of shared files notification can be shown. This is just a suggestion you can come up with you own idea. Hope this helps.
In case of Non-real time data:
Server Side-:
use res.sendFile() is a way to solve this problem. In addition to file send the receiver and sender id in headers
Client Side-:
Increase the notification count if the receiver id matches the logged in user.
res.sendFile(path.join("path to file"),
{headers:{
receiverid:result[0]._id,
senderid:result[1]._id
}
});
In case of real time data:
follow the answer posted by TRomesh

Resources