Express websockets on message get cookie info - node.js

I have an express web socket application.
In the onmessage function, I would like to access the cookies of the client that sent the message.
The reason for this is that I'm making a game and I have the user login. I need to check what to name cookie is so that I control the correct player.
This is what I've got so far:
var express = require('express');
var expressWs = require('express-ws');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var app = express();
app.use(cookieParser('secretkey123'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}))
expressWs = expressWs(app);
app.get('/', function(req, res) {
// stuff for logging in
})
app.post('/', function(req, res) {
// stuff for logging in
})
app.get('/logout', function(req, res) {
res.clearCookie('name');
res.redirect('/');
// more stuff for logging in
})
app.ws('/ws', function(ws, req) {
ws.on('open', function() {
// how do I check when a connection is opened?
})
ws.on('message', function(msg) {
// who sent the message? how do I get the cookie info to check the user who send it?
})
ws.on('close', function() {
// the've disconnected
})
})
var server = app.listen(8000, function () {
var host = server.address().address
var port = server.address().port
})
Is this possible?
Also, where do I check when a websocket connection is opened?
I tried the 'open' event but it doesn't seem to be working.
Thanks for the help in advance!

I figured out how to do it!
I forgot that the req argument can be accessed inside the other functions.
This means in the on message function you can just do this:
ws.on('message', function(msg) {
req.cookies.username //do stuff
});
The connection open code can be done before you setup any of the events:
app.ws('/ws', function(ws, req) {
// connection open code here
ws.on('message', function(msg) {
// connection message code here
})
})

Related

Prevent closing connection on bad request

I have an express server.
I set socket.setKeepAlive(true, 60000); in order to maintain persistent connection for at least 1min.
Here is the code:
var express = require("express");
var app = express();
var server = app.listen(8080);
app.get("/", (req, res) => {
res.write("Hello Riko");
});
// server.listen(3000);
server.on("connection", function(socket) {
console.log("A new connection was made by a client.");
socket.setKeepAlive(true, 60000);
socket.on("data", data => {
console.log(data);
});
// 30 second timeout. Change this as you see fit.
});
When the client send invalid request, it receives 400 Bad Request
How to prevent connection close on invalid request?
Yes the suggestion i made in the comments works.
server.on('clientError',cb) prevents the default behavior of the stack.
I encountered one problem though. It registers event listener for error event every time clientError is fired. Therefore I changed the code litle bit and ended up with a solution that works for me:
var express = require("express");
var app = express();
var server = app.listen(8080);
app.get("/", (req, res) => {
res.send("Hello Riko");
});
onSocketError = err => {
console.log("Socket Error: " + err);
};
server.on("connection", function(socket) {
socket.on("data", data => {
console.log(data.toString());
});
console.log("A new connection was made by a client.");
});
server.on("clientError", (err, socket) => {
socket.removeAllListeners("error");
});
Hope this would help someone with similar problem.

'Sharing' object between NodeJS modules

I'm new to NodeJS development and I'm doing some tests with the socket.io library. Basically, what I want to do is to stablish a socket.io connection between the clients (Angular 6 web app) and the server and broadcast a message when a new user connects.
Right now, the code is quite simple, and this is what I have:
app.js
var express = require('express');
var http = require('http');
var socketIO = require('socket.io');
// Routes
var twitterRoutes = require('./routes/user');
var app = express();
var server = http.Server(app);
var io = socketIO(server); // <== THIS OBJECT IS WHAT I WANT TO USE FROM THE ROUTES
[ ... ]
io.on('connect', (socket) => {
console.log('New user connected');
socket.on('disconnect', (reason) => {
console.log('User disconnected:', reason);
});
socket.on('error', (err) => {
console.log('Error in connection: ', err);
});
});
I want to use the io object inside the user route, but I don't know how to do it:
routes/user.js
var express = require('express');
var config = require('../config/config');
var router = express.Router();
router.post('/login', (req, res, next) => {
// DO ROUTE LOGIC
// I WANT TO BROADCAST THE NEW LOGGED USER USING io.broadcast.emit, BUT DON'T KNOW HOW
// <=====
});
How could I do it? Thanks in advance,
Not sure if it is the best way but you could share things between request handlers using middleware
// define and use a middleware
app.use(function shareIO(req, res, next) {
req.io = io;
next();
})
Then you could use req.io inside request handlers.
router.post('/login', (req, res, next) => {
// DO ROUTE LOGIC
req.io.emit('event')
});
You could do what you want by injecting your IO var in a function
// app.js
var app = express();
var server = http.Server(app);
var io = socketIO(server);
server.use(require('./router')(io))
...
// router.js
module.exports = function makeRouter(io) {
var router = express.Router();
router.post('/login', (req, res, next) => {
// do something with io
}
return router
}
I don't know if it's the best practice, but I've assigned the io object to a property of the global object, and I can access it from everywhere all across the application. So this is what I did:
app.js
var io = socketIO(server);
global.ioObj = io;
routes/user.js
router.post('/login', (req, res, next) => {
// DO ROUTE LOGIC
if (global.ioObj) {
global.ioObj.sockets.clients().emit('new-message', { type: 'message', text: 'New user has logged in' });
}
});

How to send message to frontend in case of MongoDb connection Failed

Is there any way to send error to frontend on mongoDb connection error.I had tried in a different different way but I didnt get a solution.
var express = require('express');
var session = require('express-session');
var MongoDBStore = require('connect-mongodb-session')(session);
var store = new MongoDBStore(
{
uri: config.connectionString,
collection: 'tbl_session'
});
// Catch errors
store.on('error', function(error) {
app.get('/',function(req,res){
res.send('NOT Connected....')
});
});
You can use web sockets to push this information to the UI.
const express = require('express');
const app = express();
const path = require('path');
const server = require('http').createServer(app);
const io = require('../..')(server);
const port = process.env.PORT || 3000;
var session = require('express-session');
var MongoDBStore = require('connect-mongodb-session')(session);
var store = new MongoDBStore(
{
uri: config.connectionString,
collection: 'tbl_session'
});
// Catch errors
store.on('error', function(error) {
socket.emit('mongodb-failed', error)
});
});
server.listen(port, () => {
console.log('Server listening at port %d', port);
});
// Routing
app.use(express.static(path.join(__dirname, 'public')));
io.on('connection', (socket) => {
// when socket emits 'mongodb-connection-failed', this listens and executes
socket.on('mongodb-failed', (data) => {
// we tell the client to execute 'new message'
socket.broadcast.emit('mongodb-connection-failed', {
errorDetails: data
});
});
});
now at client side:
var socket = io();
socket.on('mongodb-connection-failed', () => {
console.log('you have been disconnected');
//do more whatever you want to.
});
This above example is using socket.io.
You can use any web socket library, see more here

Make two socket connections talk to each other

Here is the scenario...
I am working on an app I had an idea for, I'm building it in ember with an express backend. I am using the express-ws so I can run the ws websocket package inside express better. I was not able to get just ws to work with express.
My app will have two people connecting to two different url's that are socket connections, so that they can send and receive information to the server without the other getting it. At least that's the way I've come up in my mind to do it.
What I want is when one user does an interaction over the socket, for that socket to send a message to the other socket to perform an action and send it's information to the user connected on it.
I hope that makes sense. With express-ws here is what I have done so far which works at a basic level.
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
app.use(function (req, res, next) {
console.log('middleware');
req.testing = 'testing';
return next();
});
app.get('/', function(req, res, next){
console.log('browser connected');
res.send('welcome to the api browser');
});
app.ws('/', function(ws, req) {
console.log('socket connected');
var object = {
message: 'welcome to the socket api',
time: Date.now().toString()
}
ws.send(JSON.stringify(object));
});
app.listen(1337);
I haven't made the other connection yet but for the time being it will be the same, but when the user on one connection sends a certain message to their socket, I want that socket to perform something and then pass some data to the other socket so it can send some information to it's user.
This might give you an idea of how to store the references for later use:
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
// array to hold the connections
var openChannels = [];
app.use(function(req, res, next) {
console.log('middleware');
req.testing = 'testing';
return next();
});
app.get('/', function(req, res, next) {
console.log('browser connected');
res.send('welcome to the api browser');
});
app.ws('/', function(ws, req) {
console.log('socket connected');
// store connection for later reference
openChannels.push(ws);
// #todo: remove from array on disconnect
// set broadcast callback
ws.onmessage = function(msg) {
openChannels.forEach(function(index, item) {
if (item !== ws) { // make sure we're not sending to ourselves
item.send(msg);
}
});
};
var object = {
message: 'welcome to the socket api',
time: Date.now().toString()
}
ws.send(JSON.stringify(object));
});
app.listen(1337);

ExpressJS & Websocket & session sharing

I'm trying to make a chat application based on Node.js. I'd like to force websocket server (ws library) to using ExpressJS session system. Unfortunately, I've got stuck. MemoryStore hashes used to get sessions' data are different than session IDs in cookies. Could somebody explain me what I'm doing wrong?
Websocket server code part:
module.exports = function(server, clients, express, store) {
server.on('connection', function(websocket) {
var username;
function broadcast(msg, from) {...}
function handleMessage(msg) {...}
express.cookieParser()(websocket.upgradeReq, null, function(err) {
var sessionID = websocket.upgradeReq.cookies['sid'];
//I see same value in Firebug
console.log(sessionID);
//Shows all hashes in store
//They're shorter than sessionID! Why?
for(var i in store.sessions)
console.log(i);
store.get(sessionID, function(err, session) {
websocket.on('message', handleMessage);
//other code - won't be executed until sessionID in store
websocket.on('close', function() {...});
});
});
});
}
store object definition:
var store = new express.session.MemoryStore({
reapInterval: 60000 * 10
});
app configuration:
app.configure(function() {
app.use(express.static(app.get("staticPath")));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
store: store,
secret: "dO_ob",
key: "sid"
}));
});
Part of main code:
var app = express();
var httpServer = http.createServer(app);
var websocketServer = new websocket.Server({server: httpServer});
httpServer.listen(80);
Sample debugging output:
- websocket.upgradeReq.headers.cookie "sid=s%3A64a%2F6DZ4Mab8H5Q9MTKujmcw.U8PJJIR%2BOgONY57mZ1KtSPx6XSfcn%2FQPZ%2FfkGwELkmM"
- websocket.upgradeReq.cookies["sid"] "s:64a/6DZ4Mab8H5Q9MTKujmcw.U8PJJIR+OgONY57mZ1KtSPx6XSfcn/QPZ/fkGwELkmM"
- i "64a/6DZ4Mab8H5Q9MTKujmcw"
I found this works for me. Not sure it's the best way to do this though. First, initialize your express application:
// whatever your express app is using here...
var session = require("express-session");
var sessionParser = session({
store: session_store,
cookie: {secure: true, maxAge: null, httpOnly: true}
});
app.use(sessionParser);
Now, explicitly call the session middleware from the WS connection. If you're using the express-session module, the middleware will parse the cookies by itself. Otherwise, you might need to send it through your cookie-parsing middleware first.
If you're using the websocket module:
ws.on("request", function(req){
sessionParser(req.httpRequest, {}, function(){
console.log(req.httpRequest.session);
// do stuff with the session here
});
});
If you're using the ws module:
ws.on("connection", function(req){
sessionParser(req.upgradeReq, {}, function(){
console.log(req.upgradeReq.session);
// do stuff with the session here
});
});
For your convenience, here is a fully working example, using express, express-session, and ws:
var app = require('express')();
var server = require("http").createServer(app);
var sessionParser = require('express-session')({
secret:"secret",
resave: true,
saveUninitialized: true
});
app.use(sessionParser);
app.get("*", function(req, res, next) {
req.session.working = "yes!";
res.send("<script>var ws = new WebSocket('ws://localhost:3000');</script>");
});
var ws = new require("ws").Server({server: server});
ws.on("connection", function connection(req) {
sessionParser(req.upgradeReq, {}, function(){
console.log("New websocket connection:");
var sess = req.upgradeReq.session;
console.log("working = " + sess.working);
});
});
server.listen(3000);
I was able to get this working. I think you need to specify the secret on cookieParser instead of session store.
Example from my app:
var app = express();
var RedisStore = require('connect-redis')(express);
var sessionStore = new RedisStore();
var cookieParser = express.cookieParser('some secret');
app.use(cookieParser);
app.use(express.session({store: sessionStore}));
wss.on('connection', function(rawSocket) {
cookieParser(rawSocket.upgradeReq, null, function(err) {
var sessionID = rawSocket.upgradeReq.signedCookies['connect.sid'];
sessionStore.get(sessionID, function(err, sess) {
console.log(sess);
});
});
});
Feb 2022 update:
verifyClient is now discouraged. New methods of doing this is described in an issue comment.
Consult the example code for session parsing and verification for a full usage example. Sample of the verification function:
server.on('upgrade', function (request, socket, head) {
console.log('Parsing session from request...');
sessionParser(request, {}, () => {
if (!request.session.userId) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
console.log('Session is parsed!');
wss.handleUpgrade(request, socket, head, function (ws) {
wss.emit('connection', ws, request);
});
});
});
Original answer:
In version 3.2.0 of ws you have to do it a bit differently.
There is a full working example of express session parsing in the ws repo, specifically using a new feature verifyClient.
A very brief usage summary:
const sessionParser = session({
saveUninitialized: false,
secret: '$eCuRiTy',
resave: false
})
const server = http.createServer(app)
const wss = new WebSocket.Server({
verifyClient: (info, done) => {
console.log('Parsing session from request...')
sessionParser(info.req, {}, () => {
console.log('Session is parsed!')
done(info.req.session.userId)
})
},
server
})
wss.on('connection', (ws, req) => {
ws.on('message', (message) => {
console.log(`WS message ${message} from user ${req.session.userId}`)
})
})
WS v3.0.0 and above, has changed the behaviour so the given answers won't work out of the box for those versions. For current versions, the signature of the connection method is [function(socket, request)] and the socket no longer contains a reference to the request.
ws.on(
'connection',
function (socket, req)
{
sessionParser(
req,
{},
function()
{
console.log(req.session);
}
);
}
);
Currently, below is my workaround which is working fine. I just don't know it's disadvantages and security. I just prevent the server from listening if it doesn't have a session. (Share session from express-session to ws)
I haven't fully tested this though.
var http = require('http');
var express = require('express');
var expressSession = require('express-session');
var router = express.Router();
var app = express();
const server = http.createServer(app);
router.get('/', function(req, res, next) {
if(req.session.user_id) {
// Socket authenticated
server.listen(8080, function listening(){});
}
});

Resources