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 });
});
Related
I have a nodejs cluster server that is using mongo changestream listener, to emit data to clients over socket.io. I'm using Redis to store a userId and the socketId of all the connected users in a hash.
{ userId: 'aaa', socketId: 'bbb' }
The redis clients for storing this data is initialized in the master process.
The mongo changestream is created in the master process.
When the changestream sees a new document, it will send the document to a child process as a message. When the child process receives the message, it can retrieve the userId from the document. With the userId, the socketId for the client connection can be retrieved from redis.
The issue I am having is in trying to emit a message using the socketId after it is retrieved from redis.
I am creating a sockethandler object that contains the socketId. When I use this socketId to emit a socket message, like so:
io.sockets.to(userSocketId)
.emit("confirmOrder", "Your order is being processed!")
I receive an error:
(node:31804) UnhandledPromiseRejectionWarning: Error: The client is closed
at new ClientClosedError (/Users/a999999999/code/*/node_modules/#node-redis/client/dist/lib/errors.js:24:9)
The error is from redis, and originated on the socket emit line written above. ^^
Here is more code from the worker process:
const pubClient = createClient({ host: "127.0.0.1", port: 6379 }),
subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
setupWorker(io);
io.on("connection", (socket) => {
const socketId = socket.id;
socket.emit("connection", "SERVER: you are connected");
socket.on("userConnect", (user) => {
let { userId } = user;
userConnectClient
.HSET(userId, { userId, socketId })
.catch((err) => console.log("ERROR: ", err));
});
});
process.on("message", async ({ type, data }) => {
switch (type) {
case "dispatch:order":
let { order } = JSON.parse(data);
const socketsHandler = await createSocketsHandler(order);
const userSocketId = socketsHandler.user.socketId;
io.sockets
.to(userSocketId)
.emit("confirmOrder", "Your order is being processed!");
break;
}
});
async function createSocketsHandler(order) {
let { userId } = order;
let user = await userConnectClient
.HGETALL(userId)
.catch((err) => console.log(err));
return {
user: user,
};
}
I am temporarily stumped at this point. Currently experimenting with the io object, and trying to find better tools to monitor redis. Any help/questions is appreciated! Thank you!
I've since realized why the redis client was not working properly. I'm making use of publisher and subscriber clients with redis. The problem was, I was creating the redis clients in the worker process of the server. So, whenever the server is making a command to redis, it is not able to do it properly because there is a pair of clients for each worker process, which is not the proper implementation, I believe.
This was solved by creating the redisClient outside of my cluster server code. ;P My server can now properly subscribe to redis client in master and worker process!
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();
It's been a while since I've worked with Node and Websockets. Basically how do I get socket.send() to work from another function is what I'm stuck on.
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', socket => {
socket.on('message', message => {
console.log(`received from a client: ${message}`);
});
socket.send('yo world!');
});
function onMessageHandler (target, context, msg, self) {
client.say(target, response);
server.socket.send(response);
console.log(response);
}
}
How do I get my onMessageHandler to trigger a socket send, this is fail... server.socket.send(response);
Seeing your question i think there is a lack of understanding on how Websockets work. I am assuming you're using https://github.com/websockets/ws
There are two things. First is the WebSocketerver which you've named as server and then an Individual Socket which you've named as socket
Now the thing to understand is socket is not accessible outside server.on() callback The reason for this is there could be 1000 of sockets connected at a given instance and there would be no way to uniquely identify a particular socket you want to send message to.
So ask yourself the question that your application wants to send message to an individual socket to send to everyone who is connected to your server (basically broadcast)
If you want to send to an individual, you will have to uniquely identify the user
this._wss = new WebSocket.Server({
port: ENV_APP_PORT_WS
});
this._wss.on("connection", async (ws: AppWebSocket, req: IncomingMessage) => {
// const ipAddress = req.connection.remoteAddress; // IP Address of User
logger.info(req);
const queryParams = url.parse(req.url, true).query;
let authUser: User;
try {
authUser = await this._authenticateWebSocket(queryParams);
} catch (e) {
// Terminate connection and return...
}
// WS User INIT
ws.isAlive = true;
ws.userId = authUser.id;
ws.uuid = Helpers.generateUUIDV4();
ws.send(JSON.stringify({
type: "connected",
env: ENV
}));
});
The above code will add a property to each socket object that will enable it to uniquely identify a particular user/socket.
While sending =>
onMessageHandler(targetUserId: number, message: string) {
const allSockets = <AppWebSocket[]>Array.from(this._wss.clients.values());
const targetSocket = allSockets.find(w => w.userId === targetUserId);
targetSocket.send(message);
}
If You want to send to all connect users, it's quite easy:
https://github.com/websockets/ws#server-broadcast
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?
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