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:
server.auth.strategy()
.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.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:
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 eventserver.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();
}
}
}
},
...
});