sibartlett / hapi-io

Awesome socket.io plugin for hapi
MIT License
105 stars 18 forks source link

hapi-io

Greenkeeper badge npm Build Status Dependency Status devDependency Status

Awesome socket.io plugin for hapi (inspired by express.oi and express.io).

Table of Contents

Installation and Configuration

npm install hapi-io --save
server.register({
  register: require('hapi-io'),
  options: {
    ...
  }
});
Options

Raw access to socket.io

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;

};

Forward events to hapi routes

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.

Example
Server
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);
        });
      }
     },

  ]);
};
Client

Reference socket.io as per https://socket.io/docs/

<script src="https://github.com/sibartlett/hapi-io/raw/master/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>
How it works

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:

  1. 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.

  2. 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:

    1. If the field is a parameter in the route's path, it's mapped as a path parameter.

    2. 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.

    3. If the field exists in the route's validate object, the value is mapped to the corresponding param type.

    4. If the route is a 'GET' method, the field is mapped as a query param.

    5. Otherwise it's mapped as a payload field.

  3. Maps "Authorization" attribute from query or data object if possible and not already mapped.

Access socket during hapi request

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
      }
    }
  });
};
Post event hook

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:

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();
        }
      }
    }
  },
  ...
});