Awesome socket.io plugin for hapi (inspired by express.oi and express.io).
npm install hapi-io --save
server.register({
register: require('hapi-io'),
options: {
...
}
});
connectionLabel
(optional)namespaces
(optional) - an array of strings representing namespaces. Namespaces must always begin with a slash'/'
(e.g.'/mynamespace'
. The default'/'
namespace is always available irrespective if explicitly specified, and will be the only namespace available to routes if this option is not set upon plugin initialization.socketio
(optional) - an object which is passed through to socket.ioauth
(optional) - authorization configuration. Socket.io connections will be refused if they fail authorization. If this option is omitted: all socket.io connections will be accepted, but route level authorization will still be enforced. Value can be:- a string with the name of an authentication strategy registered with
server.auth.strategy()
. - an object with:
strategies
- a string array of strategy names in order they should be attempted. If only one strategy is used,strategy
can be used instead with the single string value.
- a string with the name of an authentication strategy registered with
You can get raw access to the socket.io server as follows:
exports.register = function(server, options, next) {
var io = server.plugins['hapi-io'].io;
};
Perfect for exposing HTTP API endpoints over websockets!
socket.io events can be mapped to hapi routes; reusing the same authentication, validation, plugins and handler logic.
exports.register = function(server, options, next) {
server.route([
{
method: 'GET',
path: '/users/{id}',
config: {
plugins: {
'hapi-io': 'get-user'
}
},
handler: function(request, reply) {
db.users.get(request.params.id, function(err, user) {
reply(err, user);
});
}
},
{
method: 'POST',
path: '/users',
config: {
plugins: {
'hapi-io': {
event: 'create-user',
mapping: {
headers: ['accept'],
query: ['returnType']
}
}
}
},
handler: function(request, reply) {
db.users.create(request.payload, function(err, user) {
if (err) {
return reply(err).code(201);
}
if (request.headers.accept === 'application/hal+json') {
addMeta(user);
}
if (request.query.returnType !== 'full') {
delete user.favoriteColor;
}
reply(err, user);
});
}
},
// '/admin' namespace
{
method: 'GET',
path: '/users/{id}',
config: {
plugins: {
'hapi-io': {
event: 'create-user',
namespace: '/admin'
}
}
},
handler: function(request, reply) {
db.adminUsers.get(request.params.id, function(err, user) {
reply(err, user);
});
}
},
]);
};
Reference socket.io as per https://socket.io/docs/
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
socket.emit('get-user', { id: 'sibartlett'}, function(res) {
// res is the result from the hapi route
});
socket.emit('create-user', {
name: 'Bill Smith',
email: 'blsmith@smithswidgets.com',
location: 'remote',
favoriteColor: 'green',
returnType: 'full'
}, function (res) {
// do something with new user
});
// '/admin' namespace
var socketA = io('/admin');
socketA.emit('get-admin-user', { id: 'mmemon'}, function(res) {
// res is the result from the hapi route
});
</script>
Each time an event is received, a fake HTTP request is created and injected into the hapi server.
The fake HTTP request is constructed as follows:
- The headers and querystring parameters from the socket.io handshake are added to the fake request.
This allows you to use the route's auth stategy - to authenticate the socket.io event.
-
Each field in the event payload is mapped to one of the following hapi param types: headers, path, query or payload. The mapping is determined on a per field basis:
-
If the field is a parameter in the route's path, it's mapped as a path parameter.
-
If the hapi-io config is an object and has a
mapping
property, then the field is checked against the mapping. Allowed mappings are headers, query, and payload. -
If the field exists in the route's validate object, the value is mapped to the corresponding param type.
-
If the route is a 'GET' method, the field is mapped as a query param.
-
Otherwise it's mapped as a payload field.
-
Maps "Authorization" attribute from query or data object if possible and not already mapped.
You can access both the socket.io server and socket within the hapi route.
exports.register = function(server, options, next) {
server.route({
method: 'GET',
path: '/users/{id}',
config: {
plugins: {
'hapi-io': 'get-user'
}
},
handler: function(request, reply) {
var io = request.plugins['hapi-io'].io;
var socket = request.plugins['hapi-io'].socket;
reply({ success: true });
if (socket) {
// socket is only defined during a hapi-io/socket.io request
}
}
});
};
You can do further processing on a socket.io event, after it has been processed by hapi.
You can use the post
option to specify a function, with two parameters: ctx
and next
. ctx
has the following properties:
io
- the socket.io Server objectsocket
- the socket.io Socket objectevent
- the socket.io eventdata
- the event's data objectreq
- the request object that was injected into hapires
- the result object that was returned by hapiresult
- the res.resulttrigger
- a method that allows you to trigger another socket.io event
server.route({
method: 'POST',
path: '/rooms/{roomId}/join',
config: {
plugins: {
'hapi-io': {
event: 'join-room',
post: function(ctx, next) {
ctx.socket.join(ctx.data.roomId);
next();
}
}
}
},
...
});