Integrating socket.io with keystone

2,006 views
Skip to first unread message

Nathan Quinn

unread,
Apr 6, 2014, 7:08:40 PM4/6/14
to keyst...@googlegroups.com
I've been trying to integrate socket.io with keystone and can't figure out exactly how. I've gone to the index.js file inside keystone and tried to add it there adding the listener in the createServer function inside that file, but I get EADDRINUSE. I tried listening to keystone.httpServer in my route files but that doesn't work i get the socket error saying i need it to listen to a http.server instance.

Gregor Elke

unread,
Apr 7, 2014, 5:00:34 AM4/7/14
to keyst...@googlegroups.com
in your web.js this should work, i think, after keystone.start() call:
io = require('socket.io').listen(keystone.server)

j...@keystonejs.com

unread,
Apr 7, 2014, 10:40:29 AM4/7/14
to keyst...@googlegroups.com
We just added support for events in keystone.start() to make it play nicely with socket.io.


Short answer:

keystone.start({
    onHttpServerCreated
: function() {
       
require('socket.io').listen(keystone.httpServer);
   
}
});

Cheers,
Jed.

Nathan Quinn

unread,
Apr 7, 2014, 11:02:35 AM4/7/14
to keyst...@googlegroups.com
Jed, hey I'm putting that call in my index.js inside routes folder but am getting an error saying "The onStart argument must be a function or undefined". 

Nathan Quinn

unread,
Apr 7, 2014, 11:04:22 AM4/7/14
to keyst...@googlegroups.com
Is there a way to update my keystone with this new fix?

j...@keystonejs.com

unread,
Apr 7, 2014, 11:04:48 AM4/7/14
to keyst...@googlegroups.com
Hey Nathan,

Did you update?

keystone.start() only accepted a single onStart function until 0.2.13, when the update to support an object with the additional events was added.

j...@keystonejs.com

unread,
Apr 7, 2014, 11:13:08 AM4/7/14
to keyst...@googlegroups.com
To update keystone to the latest version, run

npm update

... in your project folder.

Also check your package.json says something like

"keystone": "~0.2.13"

... which will automatically update to any release greater than 0.2.13, but less than 0.3.0

When we release 0.3.x you'll need to update your package.json to support it, which is something best handled manually (in case there are changes you need to take into account)

Hope this sorts it out for you!
- Jed.
Message has been deleted

Nathan Quinn

unread,
Apr 7, 2014, 1:27:17 PM4/7/14
to keyst...@googlegroups.com
Ok so I got all that but now It's throwing a mongo error when I used the .start(). Says  {[Error: Trying to open unclosed connection.] stats: 2} . This only throws when I use the start call.

Vadi

unread,
Apr 12, 2014, 9:38:56 AM4/12/14
to keyst...@googlegroups.com
var io = require('socket.io');

keystone.start({
onHttpServerCreated: function() {
        io = require('socket.io').listen(keystone.httpServer);
        io.set('log level', 2); 
        io.sockets.on('connection', function (socket) {
         socket.emit('news', { hello: 'world' });
         socket.on('my other event', function (data) {
           console.log(data);
         });
        });
            }
});



If we are instantiating socket.io in onHttpServerCreated event, how do we get references to io.sockets.on() handler outside start() scope? I assume, we instantiate socket.io in keystone.js start() function.



Could you please suggest a way for this?

kille...@gmail.com

unread,
Apr 14, 2014, 12:13:46 PM4/14/14
to keyst...@googlegroups.com
Something like this would work:

var sock = require('socket.io');

keystone
.start(
{
    onHttpServerCreated
: function()
   
{
        keystone
.set('io', sock.listen(keystone.httpServer));
       
       
var io = keystone.get('io');

kille...@gmail.com

unread,
Apr 29, 2014, 8:31:09 AM4/29/14
to keyst...@googlegroups.com
(UPDATE) For anyone looking for an easy way to integrate socket.io into keystone, this is what I recommend. First, install 'express.io' for your site. This will also install socket.io as one of its dependencies:

npm install express.io

Next, make your 'keystone.start' look like this:
keystone.start(
{
    onHttpServerCreated
: function()
   
{
       
// Instantiate an express.io app object, tack it on to keystone
        keystone
.socketioapp = require('express.io')();
       
// The 'server' property is used internally by express.io as the express http or https object, so copy it from keystone
        keystone
.socketioapp.server=keystone.httpServer;
       
// Since the http(s) object has already been created by keystone just before this callback, we call express.io's socket.io instantiator rather than creating another server with: keystone.socketioapp.http().io(); This allows socket.io to use the same port as keystone.
        keystone
.socketioapp.io();
       
       
// Setup your routes before 'listen' is called. This example 'ready' route will emit a 'talk' event back to the client.
        keystone
.socketioapp.io.route('ready', function(req)
       
{
            req
.io.emit('talk', { message: 'io event from an io route on the server' })
       
});
       
       
// A little sugar to make our example play a bit nicer
       
var port = keystone.get('port');
        // 'listen' will share the port with keystone
        keystone
.socketioapp.server.listen(port?port:3000);
   
}
});

The socket.io object in our example above is available anywhere in keystone as 'keystone.socketioapp.io'.

For the client, add the socket.io library to your javascript folder: /your site/public/js/lib/socket-io/socket.io.js. This file will be found in your socket.io module folder. If you installed socket.io before installing express.io, you can find it in: /your site/node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js, otherwise it's in: /your site/node_modules/express.io/node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js

To test the example route above, add this to the bottom of your default JADE template in /your site/templates/layouts/default.jade, right after the comment "//- Add scripts that are globally required by your site here.":

        script(src='/js/lib/socket-io/socket.io.js')
       
        script
.
            io
= io.connect()
            io
.emit('ready')
            io
.on('talk', function(data) { alert(data.message) })

After you sign in, the alert message should pop up on every page in your regular views (but not the admin pages).

Express.io has some nice abstraction for handling socket.io, so look over their documentation for examples.

JED - would you consider integrating express.io into keystone? I've attempted this, but there's something in the way keystone handles cookies that causes express.io to crash in the socket.io module when doing the handshake. There's two basic steps to add it in. First, change 'express = require('express'),' to: 'express = require('express.io'),' then change 'keystone.httpServer = http.createServer(app);' and 'keystone.httpsServer = https.createServer(sslOpts, app);' to 'keystone.httpServer = app.http().io().server;' and 'keystone.httpsServer = app.https(sslOpts).io().server;' respectively.

SHAH ZAMAN

unread,
Apr 29, 2014, 8:51:51 AM4/29/14
to keyst...@googlegroups.com
@Jed
What MVC and Web Framework are you using for Keystonejs ??

kille...@gmail.com

unread,
Apr 29, 2014, 7:06:10 PM4/29/14
to keyst...@googlegroups.com
Well, after a nice nap, I finally figured out what was causing the crash when express.io is integrated directly into Keystone. It's not in Keystone but in express.io. I'll submit a bug report to the project.

In the meantime, if anyone wants to try out the fix, in /express.io/compiled/index.js, change the line "sessionConfig = options;" to "_.extend(options, sessionConfig);" and in /express.io/lib/index.coffee change "sessionConfig = options" to "_.extend options, sessionConfig". Now you can have express.io integrate directly into Keystone. To integrate without touching any of Keystone's code:

1. In the /node_modules/keystone folder, enter this command:

npm install express.io

2. Delete the "express" folder.
3. Rename the "express.io" folder to "express"
4. Do the fix described above to the two files (index.js and index.coffee).
5. Now make your 'keystone.start' look like this:

keystone.start(
{
   
// http:
    onHttpServerCreated
: function()
   
{
        keystone
.app.server=keystone.httpServer;
        keystone
.app.io();
       
       
// Setup your routes here. 'listen' is called right after this function returns.
        keystone
.app.io.route('ready', function(req)

       
{
            req
.io.emit('talk', { message: 'io event from an io route on the server' })
       
});

   
},
   
// https:
    onHttpsServerCreated
: function()
   
{
        keystone
.app.server=keystone.httpsServer;
        keystone
.app.io();
       
       
// Setup your routes here. 'listen' is called right after this function returns.
        keystone
.app.io.route('ready', function(req)

       
{
            req
.io.emit('talk', { message: 'io event from an io route on the server' })
       
});
   
}    
});

Your socket.io object is now part of Keystone's 'app', i.e. 'keystone.app.io'. Of course, if you want to use a different port than what Keystone is using, the method in my previous posting should be used instead of this.

j...@keystonejs.com

unread,
Apr 30, 2014, 11:34:09 AM4/30/14
to keyst...@googlegroups.com
@killerbobjr nice work figuring this out. It sounds like it would be good to add proper support for socket.io in Keystone itself, to make this simpler.

I've added an issue for it here: https://github.com/JedWatson/keystone/issues/307

Can you let me know (maybe comment on that issue) when the issue you reported gets fixed so we can integrate it without patching the express.io package?

Or would it be better to use the other method (without express.io) if it's supported as an option?

I haven't actually worked with socket.io on a project yet so if someone with more experience could make a PR (or just provide a test case in the form of a simple keystone project implementing socket.io on github) that'd be really helpful.

@SHAH what exactly do you mean? Keystone effectively is an mvc framework for web apps (server-side, at least). It's built on Express, which is also a web framework.

The Admin UI doesn't use a client-side framework, just hand-coded javascript w/ jQuery and a few other libs. We're looking into implementing web components with polymer for the forms, and possibly backbone in a few places.

For the front end you can use any framework (backbone, angular, etc) you like and create REST APIs in the back-end to power it - the choice is up to you.

kille...@gmail.com

unread,
May 1, 2014, 1:18:21 PM5/1/14
to keyst...@googlegroups.com
I've forked express.io, put in the proper fixes, and sent a pull request to the maintainer.

In the mean time I've also forked Keystone, added in the express.io package in place of express, made the two line change for Keystone to support socket.io (as per my earlier post), set package.json to retrieve from my fork of express.io, added in a socket.io client library line to base.jade (including the libraries in the public folder), and sent you a pull request.

I'll try to work out some sort of test bed, possibly by making a package that does some example socket.io tasks that one could use as a project base in keystone.

Marty Hirsch

unread,
Aug 6, 2014, 5:37:50 PM8/6/14
to keyst...@googlegroups.com
Hi, this is very helpful, it allow socket.io to run under Keystone without a lot of kludges - the only way I've found so far to get real-time communications working with Keystone - thank you!

I am wondering what this means:

"The socket.io object in our example above is available anywhere in keystone as 'keystone.socketioapp.io'."

Can you give an example of how to use this outside the context of the keystone.start() function?

I found that I can use it inside that function - (which may be sufficient) - but not elsewhere. 

Many thanks.

Edward Knowles

unread,
Oct 9, 2014, 11:28:58 AM10/9/14
to keyst...@googlegroups.com
I need help with this also

Marty Hirsch

unread,
Oct 9, 2014, 2:16:35 PM10/9/14
to keyst...@googlegroups.com
First, add these (express.io and Simple Java Web Tokens) to your dependencies in package.json:

"express.io": "^1.1.X",
"jwt-simple": "^0.2.X",  

In keystone.js, use this code:


keystone.set('jwtTokenSecret', 'YOUR_XXXXXXXX_TOKEN_XXXXXXXX_SECRET'); // put in something hard to guess

// Start Keystone to connect to your database and initialise the web server
keystone.start(
{
onHttpServerCreated: function() 
{
// Instantiate an express.io app object, tack it on to keystone
keystone.socketioapp = require('express.io')();
// The 'server' property is used internally by express.io as the express http or https object, so copy it from keystone
keystone.socketioapp.server=keystone.httpServer;
// Since the http(s) object has already been created by keystone just before this callback, we call express.io's socket.io instantiator rather than creating another server with: keystone.socketioapp.http().io(); This allows socket.io to use the same port as keystone.
var socketio = keystone.socketioapp.io;
keystone.set('socketio', socketio);

var port = keystone.get('port');
// 'listen' will share the port with keystone
keystone.socketioapp.server.listen(port?port:3000);
socketio.set('heartbeat timeout', 20);
socketio.set('heartbeat interval', 10);
socketio.enable('heartbeats');
socketio.on('connection', function(socket) {

socket.on('connected', function(token) {
user = checkToken(token);
  if (typeof user == 'object') {
  socket.user = user;
}

});
});
}
});


In your client (this example uses jQuery), do this:

var token, socket = io.connect();

$.ajax({ url: "/token", dataType: "json" }).done( function( data ) {
token = data;
socket.emit('connected', token);
} );

Finally, you need the token handlers.  First, establish a restricted route to the client token handler in routes/index.js.

// jwt token authentication for socket.io traffic
app.all('/token*', middleware.requireUser);
app.all('/token', routes.socketio.token);

Here is the code for token.js.

var keystone = require('keystone'),
jwt = require('jwt-simple'),
moment = require('moment'),
async = require('async');

exports = module.exports = function(req, res) {
if (req.user) {
console.log('[token] - fname [' + req.user.name.first + '], lname [' + req.user.name.last + '], id [' + req.user.id + '].');
var jwtTokenSecret = keystone.get('jwtTokenSecret');
var expires = moment().add(7, 'days').valueOf();
var token = jwt.encode({ iss: req.user.id, exp: expires }, jwtTokenSecret);
res.json({ token : token, expires: expires, user: req.user.toJSON() });
}
}


And lastly, here is the code for the checkToken function, called by the server to validate the user during socket.on('connected');

var keystone = require('keystone'),
async = require('async'), 
jwt = require('jwt-simple'),
jwtTokenSecret = keystone.get('jwtTokenSecret');
 
checkToken = function(token) {
var user;
if (token) {
try {
var decoded = jwt.decode(token.token, jwtTokenSecret);
if (decoded.exp > Date.now() && decoded.iss == token.user._id) {
user = token.user;
}
catch (err) { }
}  

return user;
};

 One more thing to know. The server can forcibly disconnect a socket by calling:

socket.disconnect();

Okay, that is the "whole enchilada" for securely using socket.io under keystone.



Aaron Sherrill

unread,
Nov 16, 2017, 2:39:45 PM11/16/17
to Keystone JS
"Here is the code for token.js." Where does token.js go.
"And lastly, here is the code for the checkToken function" What folder/file does this go inside of?

Marty Hirsch

unread,
Nov 16, 2017, 9:01:03 PM11/16/17
to Keystone JS
1) Note that your client uses ajax to load a page at /token.  This is the route to token.js.  

So the file token.js goes on your server (typically somewhere under "routes"), and a route to it has to be set in "index.js" in the directory "routes". For example:

app.all('/token', middleware.requireUser, routes.token); // add this code to routes/index.js //

2) checkToken is a function called by keystone.start().  So the code for checkToken can go in keystone.js, or in any file that keystone "requires" (loads) before starting.
Reply all
Reply to author
Forward
0 new messages