/nes

WebSocket adapter plugin for hapi routes

Primary LanguageJavaScriptOtherNOASSERTION

nes adds native WebSocket support to hapi-based application servers. Instead of treating the WebSocket connections as a separate platform with its own security and application context, nes builds on top of the existing hapi architecture to provide a flexible and organic extension.

Build Status

Lead Maintainer - Eran Hammer

API

The full client and server API is available in the API documentation.

Examples

Route invocation

Server

var Hapi = require('hapi');
var Nes = require('nes');

var server = new Hapi.Server();
server.connection();

server.register(Nes, function (err) {

    server.route({
        method: 'GET',
        path: '/h',
        config: {
            id: 'hello',
            handler: function (request, reply) {

                return reply('world!');
            }
        }
    });

    server.start(function (err) { /* ... */ });
});

Client

var Nes = require('nes');

var client = new Nes.Client('ws://localhost');
client.connect(function (err) {

    client.request('hello', function (err, payload) {   // Can also request '/h'

        // payload -> 'world!'
    });
});

Subscriptions

Server

var Hapi = require('hapi');
var Nes = require('nes');

var server = new Hapi.Server();
server.connection();

server.register(Nes, function (err) {

    server.subscription('/item/{id}');

    server.start(function (err) {
    
        server.publish('/item/5', { id: 5, status: 'complete' });
        server.publish('/item/6', { id: 6, status: 'initial' });
    });
});

Client

var Nes = require('nes');

var client = new Nes.Client('ws://localhost');
client.connect(function (err) {

    client.subscribe('/item/5', function (err, update) {

        // update -> { id: 5, status: 'complete' }
        // Second publish is not received (doesn't match)
    });
});

Broadcast

Server

var Hapi = require('hapi');
var Nes = require('nes');

var server = new Hapi.Server();
server.connection();

server.register(Nes, function (err) {

    server.start(function (err) {
    
        server.broadcast('welcome!');
    });
});

Client

var Nes = require('nes');

var client = new Nes.Client('ws://localhost');
client.connect(function (err) {

    client.onBroadcast = function (update) {

        // update -> 'welcome!'
    });
});

Route authentication

Server

var Hapi = require('hapi');
var Basic = require('hapi-auth-basic');
var Bcrypt = require('bcrypt');
var Nes = require('nes');

var server = new Hapi.Server();
server.connection();

server.register([Basic, Nes], function (err) {

    // Set up HTTP Basic authentication

    var users = {
        john: {
            username: 'john',
            password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',   // 'secret'
            name: 'John Doe',
            id: '2133d32a'
        }
    };

    var validate = function (request, username, password, callback) {

        var user = users[username];
        if (!user) {
            return callback(null, false);
        }

        Bcrypt.compare(password, user.password, function (err, isValid) {

            callback(err, isValid, { id: user.id, name: user.name });
        });
    };
    
    server.auth.strategy('simple', 'basic', 'required', { validateFunc: validate });
    
    // Configure route with authentication
    
    server.route({
        method: 'GET',
        path: '/h',
        config: {
            id: 'hello',
            handler: function (request, reply) {

                return reply('Hello ' + request.auth.credentials.name);
            }
        }
    });

    server.start(function (err) { /* ... */ });
});

Client

var Nes = require('nes');

var client = new Nes.Client('ws://localhost');
client.connect({ headers: { authorization: 'Basic am9objpzZWNyZXQ=' } }, function (err) {

    client.request('hello', function (err, payload) {   // Can also request '/h'

        // payload -> 'Hello John Doe'
    });
});

Subscription filter

Server

var Hapi = require('hapi');
var Basic = require('hapi-auth-basic');
var Bcrypt = require('bcrypt');
var Nes = require('nes');

var server = new Hapi.Server();
server.connection();

server.register([Basic, Nes], function (err) {

    // Set up HTTP Basic authentication

    var users = {
        john: {
            username: 'john',
            password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',   // 'secret'
            name: 'John Doe',
            id: '2133d32a'
        }
    };

    var validate = function (request, username, password, callback) {

        var user = users[username];
        if (!user) {
            return callback(null, false);
        }

        Bcrypt.compare(password, user.password, function (err, isValid) {

            callback(err, isValid, { id: user.id, name: user.name, username: user.username });
        });
    };
    
    server.auth.strategy('simple', 'basic', 'required', { validateFunc: validate });

    // Set up subscription

    server.subscription('/items', {
        filter: function (path, message, options, next) {

            return next(message.updater !== options.credentials.username);
        }
    });

    server.start(function (err) {
    
        server.publish('/items', { id: 5, status: 'complete', updater: 'john' });
        server.publish('/items', { id: 6, status: 'initial', updater: 'steve' });
    });
});

var removeUpdated = ;

Client

var Nes = require('nes');

var client = new Nes.Client('ws://localhost');

// Authenticate as 'john'

client.connect({ headers: { authorization: 'Basic am9objpzZWNyZXQ=' } }, function (err) {

    client.subscribe('/items', function (err, update) {

        // First publish is not received (filtered due to updater key)
        // update -> { id: 6, status: 'initial', updater: 'steve' }
    });
});