Node Express design for avoiding multiple clients in Socket.io - node.js

Building a simple app with Posts and Comments.
User hits a Post and adds a comment, everyone who's viewing that same Post will see the new comments added to the post. All done via Socket.io.
This is how I instantiate it:
// app.js
var io = require('socket.io').listen(app.listen(app.get('port')));
var chat = io.of('/p/' + id).on('connection', function(socket) {
socket.on('addComment', function(data) {
var content = data.comment;
var comment = new commentModel({
user: req.user._id,
post_id: id,
content: content
});
comment.save(function(err, doc) {
if (err) {
return next(err);
}
commentModel.findById(doc._id).populate('user').exec(function(err, comment) {
chat.emit('newComment', {
comment: comment
});
});
});
});
});
// client-side JS
self.socket_io = io.connect(window.location.href);
self.socket_io.on('newComment', function(data) {
// do stuff with the new comment
};
The first time you hit the page, the comment is submitted once (one socket connection), however, if I refresh the page and submit a new comment, two are created (one for each connected client).
Any ideas where I'm going wrong?

does
commentModel.findById
nede to be
comment.findByID
?

You could try a few of these:
Manage the socket session on the server: socketio_session[user_id].push(session).
Will give you access to all the sessions connected for a particular user. You probably don't need to store a list of sessions per user, just the latest session, and force disconnect on the existing session, before storing a new session.
--
This list a few things, including changing a config for forcing new connection
https://github.com/LearnBoost/socket.io/issues/474
--
This might also be of use, explains how to extract session id in sockets using express:
http://www.danielbaulig.de/socket-ioexpress/

I think this one might help you out creating new socket connection each time user refresh
https://github.com/wcamarao/session.socket.io/

Related

Websockets & NodeJS - Changing Browser Tabs & Sessions

I've started writing a node.js websocket solution using socket.io.
The browsers connects to the node server successfully and I get see the socket.id and all config associated with console.log(socket). I also pass a userid back with the initial connection and can see this on the server side to.
Question: I'm not sure the best way to associate a user with a connection. I can see the socket.id changes every page change and when a tab is opened up. How can I track a user and send 'a message' to all required sockets. (Could be one page or could be 3 tabs etc).
I tried to have a look at 'express-socket.io-session' but I'm unsure how to code for it and this situation.
Question: I have 'io' and 'app' variables below. Is it possible to use the 2 together? app.use(io);
Essentially I want to be able to track users (I guess by session - but unsure of how to handle different socket id's for tabs etc) and know how to reply to user or one or more sockets.
thankyou
The best way to handle the situation is rely on SocketIO's rooms. Name the room after the user's unique ID. This will support multiple connections out of the box. Then, whenever you need to communicate with a particular user, simply call the message function and pass in their id, the event, and any relevant data. You don't need to worry about explicitly leaving a room, SocketIO does that for you whenever their session times out or they close their browser tab. (We do explicitly leave a room whenever they log out though obviously)
On the server:
var express = require('express');
var socketio = require('socket.io');
var app = express();
var server = http.createServer(app);
var io = socketio(server);
io.on('connect', function (socket) {
socket.on('userConnected', socket.join); // Client sends userId
socket.on('userDisconnected', socket.leave); // Cliend sends userId
});
// Export this function to be used throughout the server
function message (userId, event, data) {
io.sockets.to(userId).emit(event, data);
}
On the client:
var socket = io('http://localhost:9000'); // Server endpoint
socket.on('connect', connectUser);
socket.on('message', function (data) {
console.log(data);
});
// Call whenever a user logs in or is already authenticated
function connectUser () {
var userId = ... // Retrieve userId somehow
if (!userId) return;
socket.emit('userConnected', userId);
}
// Call whenever a user disconnects
function disconnectUser () {
var userId = ... // Retrieve userId somehow
if (!userId) return;
socket.emit('userDisconnected', userId);
}

How can I get (Express's) sessionID for a SockJS connection

I am using SockJS on Express server. Is there any way to get the associate HTTP session ID of the client?
I see there is a way to do it for raw web socket and Socket.io, but I am struggling to find how to do it for SockJS.
This is how my server looks like. I want a similar handler to fetch session ID:
var sockjs_echo = sockjs.createServer(sockjs_opts);
sockjs_echo.on('connection', function(conn) {
conn.on('data', function(message) {
conn.write(message);
});
});
This is a "hack", but it works for me:
sockjs_echo.on('connection', function(conn) {
var cookieHeader = conn._session.recv.ws._stream._readableState.pipes._driver._request.headers.cookie
var cookies = {}
cookieHeader.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
cookies[parts.shift().trim()] = decodeURI(parts.join('='));
});
conn.on('data', function(message) {
conn.write(message);
});
});
'cookies' variable (example):
{
"dev_cookie": "1mimec6rbcolp0ujkcbqq9pdq4uoa5v0p8a284v32tmd4q3k0qi9p4vjteoifdn9b0lsm238fghf974o9jfehfuhvm3ltrgq02ad6k0",
"session_cookie": "s%3AjkKYPKFFT8r60rXUsVYISoOF17o49GUl.pbpu6T1%2BcdrIu5uQPRxZUYOrl5GnC179GaI5pWyR7SA",
"other_cookie": "s%3AzRMiC3fjo4gxTXX1p2XSi_C_EydIa358.KAdP1gwtZBVfcbkmwi%2B3pa0L1pbOCzQ3lHnNEyFvvHc"
}
Thanks so much for asking this question, #darwinbaisa, and for the answer, c-toesca. This came after days of searching.
For XHR streaming, the cookies are at: conn._session.recv.request.headers.cookie.
The only other way I could think of doing this was to make the express session cookie httpOnly: false, thus exposing it to javascript and, of course, the possibility of hacking, then pass it back as a prefix to any message from the SockJS javascript client to the node server.
Or to assign the ID to a javascript variable as I dynamically wrote a web page response, so that javascript would have access to the variable, and again could return it to the server. But again, this would have exposed the ID, and even if the ID was hashed or encrypted, it could still be used in a malicious call to the server from javascript.
Things like this are made a lot easier in the node WS library, but I need a fallback from that for websocket-challenged browsers.

Save Data on Socket in Socket.IO

I want to save some data on the socket, server side, so whenever the client emits any data to the server, I want that data to be available!
One use case can be storing a token on the socket. When the client is connecting for the first time, it will emit the token, if it has one, or it will show the login page and then the login data will be sent to the server. Whichever one it is, I want to store the token on the server, so that every request after that doesn't need to specify the token.
Later, I'll use RedisStore, so all the data will be accessible all the servers running the app.
My only question is, where do I store the data on the socket so it's associated with that client?
on http://socket.io/#how-to-use
scroll to: Storing data associated to a client
use socket.set and socket.get to set and get data asynchronously
I'm suffering from the same question and guessing what's going on with an example code from socket.io on version 4.x
In the example, They use middleware(use function to register a middleware)
namespace.use((socket, next) => {
// get data from client
const sessionID = socket.handshake.auth.sessionID;
const {userId, username} = yourFunction();
// set socket specific data
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
next();
});
Middlewares are executed when a socket is connected with a server.
and you can use the data afterward
note - Socket.IO reference tells use socket.data for this purpose
namespace.on('connection', socket => {
socket.emit("join", `${socket.username} has been joined`);
})
If you use multiple servers, then you have to keep in mind that the data is only valid for the server
On multiple server environment, You need a single source of data which will be used by socket servers.
namespace.use(async (socket: Socket & { sessionID?: string, userID?: string, username?: string }, next) => {
const sessionID = socket.handshake.auth.sessionID; // [socket.handshake][4]
// or other [socket related attributes][4]
if (sessionID) {
// you have to implement a function to save and retrive session info
const session = await someFunctionToRetrieveSession(sessionID);
if (session) {
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
return next();
}
}
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid username"));
}
socket.sessionID = randomId();
socket.userID = randomId();
socket.username = username;
next();
});
and one more thing as I understood the namespace.use function is called only for the namespace if your client use other namespace then default then default('/') use function will not be called.
//client side
io("/chat");
...
//server side
io.use() // == io.of('/').use() will not be called
io.of('/chat').use() // only will be called
Thanksfully the author of the example implemented a sessionStorage using redis
refer to this example code
with this info, I guess socket.io server saves sockets' info in memory and set a property of a socket will be saved and when the socket comes later the server retrives the socket and it's related data. but because it happens on memory so you can't share the info among other servers that's why you have to find a way to share the data with other servers(eg. redis)
You can save the data on the global variables when you dont want to use any database
var globalVariable = {};
io.sockets.on("connection", function (socket) {
socket.on("save-client-data", function (clientData) {
var clientId = clientData.clientId;
globalVariable[clientId] = JSON.parse(clientHandshakeData);
});
socket.on("get-client-data", function (clientId) {
var clientData = globalVariable[clientId];
socket.emit("get-client-data", JSON.stringify(clientData));
});
});
This worked for my scenario, however I'm not aware of the performance implications.

Get SESSIONID in nodeJS

Now, after some hours of playing around with nodejs and socket.io, I'm getting a couple more problems - one being, that I need to get the sessionID inside node.js, whitout using app.get('/' ... - since that event doesnt seem to fire when socket.io connects, it only fires .on('connection', function( ...
var express = require('express')()
express.set('port', process.env.PORT || 8080)
var server = require('http').createServer(express)
var socket = require('socket.io').listen(server)
server.listen(express.get('port'))
// this event is fired, get('/', ... isnt!
server.on('connection', function(stream) {
// ??
} )
The Session is initially created by the PHP application, as the user logs in. Session data is stored in the database, and the key I need to access that data is the SESSION ID. What's the easiest way to get to it? Like mentioned, I found a couple examples that used app.get('/' ... but I couldnt get that event to fire at all.
Thanks.
If the session data is being stored as a cookie (most likely), then you should be able to re-parse that data during the socket handshake. I posted code for that on this answer, but copied the code here as well:
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
var cookie = handshakeData.headers.cookie;
// parse the cookie to get user data...
// second argument to the callback decides whether to authorize the client
callback(null, true);
});
});
If the session data is being propagated in the URL, then you may be able to gather this information from handshakeData.url or handshakeData.query. You'll probably have to do your own experimentation.

Destroying a handshake after logout. socket.io

Hello I am trying to build chat into an application. What I am wondering is when the user logs out of the website how do I also destroy the socket.io handshake associated with that session so the user cannot send messages from say another tab when he is logged out.
I am using expressjs if that is any help.
Well in case anyone ever find this and wants to know I did figure it out.
You can access the sockets disconnect function. I had object of users ids and their socket id so when someone logged out I called
app.get("/logout", function(req,res){
//do other logging out stuff
sockets.disconnectUser(req.session.user_id);
}
// Disconnect User function
sockets.disconnectUser = function(user_id){
sockets.socket(users[user_id]).disconnect();
}
The socket.io object contains information about all connected sockets and the sessionID of each socket. Thus, it is possible to iterate through the connected sockets and disconnect those which are associated with the sessionID that is logging out. There is no need to manually track user and socket ids in this approach.
Example code tested with socket.io#2.2.0, express#4.17.1 and express-session#1.16.2.
const SocketIO = require('socket.io');
let sio = new SocketIO;
app.get('/logout', function(req, res) {
//do other logging out stuff
logoutSocketsIO(req.sessionID);
});
// Iterate through all connected sockets and close those which are associated
// with the given sessionID
// Note: One sessionID can have multiple sockets (e.g. many browser tabs)
function logoutSocketsIO(sessionID) {
let connections = sio.sockets.connected;
for(let c in connections) {
let socketSessionID = connections[c].conn.request.sessionID;
if(sessionID === socketSessionID) {
connections[c].disconnect();
}
}
}

Resources