Nodejs, keep socket io connection alive in MongoStore - node.js

In a nodejs-express app, in the server.js file I set the socket-io connection.
It works fine doing something like this
var server = require('http').createServer(app)
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
// how can I save globally this 'socket'?
});
I would like to save this 'socket' globally in the server.js file and so, be able to use it everywhere in the project. Like this:
app.get('/on', function(req, res){
socket.on('test', function (data) {
console.log(data);
});
});
app.get('/emit', function(req, res){
socket.emit('test', "ciao");
});
I red there is a way to do it saving the 'socket' connection in session. My sessions settings:
app.configure(function(){
// ....
app.use(express.cookieParser(SITE_SECRET));
app.use(express.session({
secret :
,store : new MongoStore({
mongoose_connection : mongoose.connection
,db: mongoose.connections[0].db
})
,cookie: { maxAge: new Date(Date.now() + (1000*60*60*24*30*12)) }
}));
...
});
What is a good way to do it?
And also, after have made this saving, how to use socket.on() and socket.emit() loading the first page if the connection is not opened yet?

What you might have heard is not saving a socket into a session, but referencing sockets by their session cookie, which is passed to the server during the socket authorization process. During authorization, this is an example of the type of object that is passed to the server:
{
headers: req.headers, // <Object> the headers of the request
time: (new Date) +'', // <String> date time of the connection
address: socket.address(), // <Object> remoteAddress and remotePort object
xdomain: !!headers.origin, // <Boolean> was it a cross domain request?
secure: socket.secure, // <Boolean> https connection
issued: +date, // <Number> EPOCH of when the handshake was created
url: request.url, // <String> the entrance path of the request
query: data.query // <Object> the result of url.parse().query or a empty object
}
What we're interested in is the headers property, where we can find the session cookies of a connecting socket. We then parse the cookies during authorization:
// pass same objects from Express to Socket.IO so they match
var parseCookie = express.cookieParser(SITE_SECRET);
var store = new MongoStore({
mongoose_connection: mongoose.connection,
db: mongoose.connections[0].db
});
io.configure(function() {
io.set('authorization', function(handshake, callback) {
if (handshake.headers.cookie) {
parseCookie(handshake, null, function(err) {
// we used the signedCookies property since we have a secret
// save the session ID to the socket object, we can access it later
handshake.sessionID = handshake.signedCookies['connect.sid'];
store.get(handshake.sessionID, function(err, session) {
// we have the same Express session, reference it
socket.session = session;
callback(null, true);
});
});
} else {
// they client has no session yet, don't let them connect
callback('No session.', false);
}
});
});
app.use(parseCookie);
app.use(express.session({
secret: SITE_SECRET,
store: store,
cookie: {maxAge: new Date(Date.now() + (1000*60*60*24*30*12))}
}));
Then once we have saved the session ID, we can use the typical connection events:
var server = require('http').createServer(app)
var io = require('socket.io').listen(server);
var clients = {};
io.sockets.on('connection', function (socket) {
// save to a global object
var session = socket.handshake.sessionID;
clients[session] = socket;
socket.on('disconnect', function() {
delete clients[session];
});
});
Then we have a global reference by cookie signature. We can then access the socket like this:
app.get('/path', function(req, res) {
var socket = clients[req.sessionID];
socket.emit('Socket client accessed route.');
});
Keep in mind you might have to add some logic into your global logic for clients with multiple tabs, which would result in two sockets with the same authorization cookie.
As for your question about using socket.on() and socket.emit(), you can't use that before the connection has been established because the socket itself does not exist. If you want to send a message to all connected clients, then you should just use the global io.sockets object. It would then be more like io.sockets.emit().

Related

How can I communicate between Node/Express routes and Socket.io

I would like to use socket.io to keep track of daily active users of an application. My socket connection looks like this:
let visitorData = {};
io.on('connection', (socket) => {
socket.on('user', (data) => {
visitorData[socket.id] = data;
})
socket.on('disconnect', () => {
delete visitorData[socket.id]
})
})
I would like to be able to incorporate existing Node/Express routes and the data received to socket connections. In particular, how would I handle a POST to a /login endpoint to associate the login username to the socket id connection.
For example:
app.get('/login', (req, res, next) => {
const someSocket = /* socket */
const someSocketId = /* socket id */;
visitorData[someSocketId] = req.body.username;
someSocket.emit('user', visitorData);
})
Is there a way I can incorporate socket.io into Node/Express routes to (a) associate the user with a socket.id and (b) emit information from a request/response body?
I set up something just like this using passport socket.io. It creates middleware that allows you to access users from sockets. Here is some of my implementation:
app.js:
import GameSocket from './lib/game-socket';
var app = express();
GameSocket(app, 3700);
lib/game-socket.js:
import Server from 'socket.io';
import passportSocketIo from 'passport.socketio';
import cookieParser from 'cookie-parser';
// Configured redisStore for authentication
import {redisStore} from '../lib/session';
// Configured passport strategy for authentication
import passport from '../lib/passport';
export default function GameSocket(app, port) {
// Setup socket
var server = new Server();
var io = server.listen(app.listen(port));
// Setup authentication
io.use(passportSocketIo.authorize({
key: 'connect.sid',
secret: 'your_secret',
store: redisStore,
passport: passport,
cookieParser: cookieParser,
success: (data, accept) => accept(null, true),
failure: (data, message, err, accept) => {
console.log(err);
if(err) throw new Error(message);
}
}));
// On connection listener
io.sockets.on('connection', function (socket) {
// This is an example of getting user info from the socket
console.log('Success: New connection with: ', socket.request.user.username);
// Add your socket events here
});
return io;
}
In order to use this you need to use a session store. I personally used a redis session store, connect-redis. This is configured in lib/session.js.

Does Express.io has a property authorization to reload a session data??

I have been implementing the socket concept as in the https://github.com/techpines/express.io/tree/master/examples/sessions, I am able to display the session data that is stored once the page is initialised.
But nevertheless , when a session data keeps on changing over an interval , i get that session data emitted as undefined..
But similar concept is well done using socket.io authorization & handshake session provided the link http://www.danielbaulig.de/socket-ioexpress/
Client Side code :
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect();
// Emit ready event.
setInterval(function(){
socket.emit('ready', 'test');
},2000);
// Listen for get-feelings event.
socket.on('get-feelings', function () {
socket.emit('send-feelings', 'Good');
})
// Listen for session event.
socket.on('session', function(data) {
document.getElementById('count').value = data.count;
})
</script>
<input type="text" id="count" />
Server Side Code :
express = require('express.io')
app = express().http().io()
// Setup your sessions, just like normal.
app.use(express.cookieParser())
app.use(express.session({secret: 'monkey'}))
// Session is automatically setup on initial request.
app.get('/', function(req, res) {
var i = 0;
setInterval(function(){
req.session.count = i++;
console.log('Count Incremented as '+ req.session.count);
},1000);
res.sendfile(__dirname + '/client.html')
})
// Setup a route for the ready event, and add session data.
app.io.route('ready', function(req) {
req.session.reload( function () {
// "touch" it (resetting maxAge and lastAccess)
// and save it back again.
req.session.touch().save(function() {
req.io.emit('get-feelings')
});
});
/*
req.session.save(function() {
req.io.emit('get-feelings')
})*/
})
// Send back the session data.
app.io.route('send-feelings', function(req) {
console.log('Count Emitted as '+ req.session.count); // it is undefined in console
req.session.reload( function () {
// "touch" it (resetting maxAge and lastAccess)
// and save it back again.
req.session.save(function() {
req.io.emit('session', req.session)
});
});
})
app.listen(7076);
in console , it is printed as undefined in every emit ... i want to why socket session is not updated as the original user session keeps changing ... ???
Do i need to put any extra configuaration in the server side to handle changing session data ???

Null store object with connect-mongo and express

I'm using Express and Connect-Mongo to manage sessions for my Node.js/Socket.io app.
I looked at the connect-mongo doc and I'm trying to initialize the DB connection but whenever I try to store anything in it the store object is always null and crashes.
Here's a snippet of the code I'm using:
var mongoserver = new mongodb.Server('localhost', mongodb.Connection.DEFAULT_PORT, {safe:true}) ;
var store ;
app.use(express.session({
secret: 'secret',
store: new MongoStore({ db: 'test'}, function(){
db = new mongodb.Db('test', mongoserver, { w: 1 }) ;
db.open(function(err, db){
if(err){console.log('DB error', err) ;}
db.collection('test', function(err, collection) {
console.log('Collection...') ;
});
db.on("close", function(error){
console.log("Connection to the database was closed!");
});
});
})
}));
io.sockets.on('connection', function (socket) {
store.set('test-sid', {foo:'bar'}, function(err, session) {}) ;
)};
All other variables (express, server etc) are properly initalized, the app comes up, I see in the console that the connection to mongo is accepted. Any ideas ??
You are not storing the store in the variable "store". It is being passed into the session configuration and set as an express application setting.
To fix this you can create the store and assign it to the variable before adding it as a setting and then pass it into the session config:
// Create the store
var store = new MongoStore(...);
// Pass into session creation
app.set(expression.session({
store: store
});
// You can now access the store from the variable as you expected
store.set('test-sid', {foo: 'bar'}, function(err, session) {});

How to set 2 users socket.id into 2 variables, user1id and user2id in socket.io/node.js

I've followed this chat tutorial http://cestfait.ch/content/chat-webapp-nodejs on creating a simple chat app, Ive been trying to limit this to just 2 users, and I wanted to create 2 variables called user1id, and user2id, I want them to be the socket.id of the client, like
// you set this value when the user1 connects
var user1id;
// you set this value when the user2 connects
var user2id;
// just for visualization, this is one of the questions I have on how to do this.
user1id = socket.id; user2id = socket.id;
the reason I want just 2 users and have them as user1 and user2 is so I can compare their messages, so I need an array of user1's and users 2's messages like
user1arr = []; user2arr = [];
and then just compare for example the "currentWord" variable of user1 and see if that matches anything in user2msgs[]. An example of user1's case below:
function receivedWord(userid, currentWord) {
// this means current player is 1
if( userid == user1id ) {
// adds the word received to user 1 word array
user1arr.push(currentWord);
// compares received word with each word in user 2 array
$.each(user2arr, function(u2word) {
if(u2word == currentWord) {
wordsMatch();
}
}
}
My code is basically that of the chat app I gave at the start of this post.
Thanks.
The problem that you will be having is that every time the user reloads the page, the socketID from socketIO will change. What you will need to do is attach something like a sessionID to the user so you know that even if he reconnects he is the same user. You will need to look at a webframe like expressJS or even connect which are highly flexible middlewares for nodeJS. I've accomplished such a thing with express, so let me get you started with a bit of code for this
//directories
var application_root = __dirname;
//general require
var express = require('express'),
connect = require('connect'),
http = require('http'),
fs = require('fs');
//create express app
var app = express();
//create sessionStore
var sessionStore = new connect.session.MemoryStore();
//setup sessionKey
var secretKey = 'superdupersecret';
//configure express
app.configure(function() {
//use body parser
app.use(express.bodyParser());
//override methods
app.use(express.methodOverride());
//cookies and sessions
// BE AWARE: this lines have to be written before the router!
app.use(express.cookieParser(secretKey));
app.use(express.session({
store: sessionStore,
secret: secretKey,
key: 'express.sid'
}));
//router
app.use(app.router);
//use static page in public folder
app.use(express.static(path.join(application_root, 'public')));
});
//create the http server link to the new data http object of express 3
server = http.createServer(app);
//make server listen to 8080 and localhost requests
server.listen(8080, 'localhost', function() {
console.log('Express Server running and listening');
});
//bind socket.io to express server by listening to the http server object
var io = require('socket.io').listen(server);
//set the authorization through cookies
io.set('authorization', function(data, accept) {
//check if header data is given from the user
if (!data.headers.cookie) {
//return false if no cookie was given
return accept('Session cookie required!', false);
}
//parse cookie into object using the connect method
//NOTE: this line is a hack, this is due to the fact
//that the connect.utils.parseCookie has been removed
//from the current connect module and moved to an own
//instance called 'cookie'.
data.cookie = connect.utils.parseSignedCookies(require('cookie').parse(decodeURIComponent(data.headers.cookie)), secretKey);
//save session id based on exress.sid
data.sessionID = data.cookie['express.sid'];
//get associated session for this id
sessionStore.get(data.sessionID, function(err, session) {
if (err) {
//there was an error
return accept('Error in session store.', false)
} else if (!session) {
//session has not been found
return accept('Session not found', false);
}
//successfully got the session, authed with known session
data.session = session;
//return the accepted connection
return accept(null, true);
});
});
//connection handler for new io socket connections
io.sockets.on('connection', function(socket) {
//parse handshake from socket to io
var hs = socket.handshake;
//show session information
console.log('New socket connection with sessionID ' + hs.sessionID + ' connected');
});
Just read through the code and the comments to understand what they are doing. Basically what you are doing is the create an express server, make the socketIO listen to it, create a sessionStore to make it globally available, lock it with a secretKey and set it to the authorization method of socketIO. What socket then does is making handshakes with the users and then links it to a express session. That means that the express sessionID stays the same throughout the session while socketIO ids switch all the time.

With socket.io, how to handle authentication with a non-browser client?

I am trying to authenticate a WebSocket connection using socket.io.
My web sockets can receive connections both from a browser, and from a Python or a Node.js client.
In the first case (browser), then the user is authenticated by my Express.js server, and I can just use the session cookie, so no problem.
However, in the second case (random client) I need to handle the authentication myself.
I found from socket.io documentation, that there is a hook for authorizing the connection at the handshake phase. However, there seems to be no way for the client to add custom headers. Looks like the only way to add custom data is in the url query string.
That doesn't seem really secure to me as url is a common thing to be logged, so credentials would be logged as well.
Also, I am wondering if the connection is secure even during the handshake, or if the handshake is not over ssl then it is a major security problem to do it like that.
Well... basically security concerns. Do somebody know what's the right way to do that ? Do I really have to put credentials in the url, and is that secure ?
For such an endeavour you will need to hook the expressID to the sessionID of the socketIO connection. For express 3 that is a whole different story and is usally not that easy. Way back a few months ago I found a way to do so.
//directories
var application_root = __dirname,
//general require
var express = require('express'),
connect = require('connect'),
http = require('http');
//create express app
var app = express();
//create sessionStore
var sessionStore = new connect.session.MemoryStore();
//setup sessionKey
var secretKey = 'superdupersecret';
//configure express
app.configure(function() {
//use body parser
app.use(express.bodyParser());
//override methods
app.use(express.methodOverride());
//cookies and sessions
app.use(express.cookieParser(secretKey));
app.use(express.session({
store: sessionStore,
secret: secretKey,
key: 'express.sid'
}));
//router
app.use(app.router);
});
//create the http server link to the new data http object of express 3
server = http.createServer(app);
//make server listen to 8080 and localhost requests
server.listen(8080, 'localhost', function() {
console.log('Express Server running and listening');
});
//bind socket.io to express server by listening to the http server object
var io = require('socket.io').listen(server);
//set the authorization through cookies
io.set('authorization', function(data, accept) {
//check if header data is given from the user
if (!data.headers.cookie) {
//return false if no cookie was given
return accept('Session cookie required!', false);
}
//parse cookie into object using the connect method
//NOTE: this line is a hack, this is due to the fact
//that the connect.utils.parseCookie has been removed
//from the current connect module and moved to an own
//instance called 'cookie'.
data.cookie = connect.utils.parseSignedCookies(require('cookie').parse(decodeURIComponent(data.headers.cookie)), secretKey);
//save session id based on exress.sid
data.sessionID = data.cookie['express.sid'];
//get associated session for this id
sessionStore.get(data.sessionID, function(err, session) {
if (err) {
//there was an error
return accept('Error in session store.', false)
} else if (!session) {
//session has not been found
return accept('Session not found', false);
}
//successfully got the session, authed with known session
data.session = session;
//return the accepted connection
return accept(null, true);
});
});
//connection handler for new io socket connections
io.sockets.on('connection', function(socket) {
//parse handshake from socket to io
var hs = socket.handshake;
//show session information
console.log('New socket connection with sessionID ' + hs.sessionID + ' connected');
});
//handle user disconnect and clear interval
io.sockets.on('disconnect', function() {
//show disconnect from session
console.log('Socket connection with sessionID ' + hs.sessionID + ' disconnected');
});
So it looks like the only solution is to add your authentication data in the url query string. There's an open ticket for allowing other possibilities, but it looks like the developers are leaning towards using the query strings only.
This works for me: https://gist.github.com/jfromaniello/4087861. I had to change the socket.io-client package.json to use xmlhttprequest version 1.5.0 which allows setting the cookie header. I also had to change both requires to use the socket.io xmlhttprequest:
require('socket.io-client/node_modules/xmlhttprequest')
instead of
require ('xmlhttprequest');

Resources