Faye

Simple pub/sub messaging for the web

Node.js server

Extensions

Both the server and client support an extension mechanism that lets you intercept messages as they pass in and out. This lets you modify messages for any purpose you like, including messages on /meta/* channels that are used by the protocol. An extension is just an object that has either an incoming() or outgoing() method (or both). These methods should accept a message and a callback function, and should call the function with the message once they have made any modifications.

Extensions use a callback instead of simply returning the modified message since this allows you to use asynchronous logic to make your modifications.

As an example, suppose we want to authenticate subscription messages by checking an authentication token against a list we’re keeping in a file on disk. Clients subscribe to channels by sending a message to the /meta/subscribe channel with the channel they want to subscribe in the subscription field. Let’s say our authentication file contains a JSON object that maps channels to required tokens:

// tokens.json

{
  "/users/jcoglan/updates": "rt6utrb",
  "/artists/mclusky/news":  "99taaec" 
}

The server can validate subscription messages by checking that they have the right auth token attached. By convention, data added by extensions is stored in the message’s ext field.

var fs = require('fs');

var serverAuth = {
  incoming: function(message, callback) {
    // Let non-subscribe messages through
    if (message.channel !== '/meta/subscribe')
      return callback(message);

    // Get subscribed channel and auth token
    var subscription = message.subscription,
        msgToken     = message.ext && message.ext.authToken;

    // Find the right token for the channel
    fs.readFile('./tokens.json', function(err, content) {
      var registry = JSON.parse(content.toString()),
          token    = registry[subscription];

      // Add an error if the tokens don't match
      if (token !== msgToken)
        message.error = 'Invalid subscription auth token';

      // Call the server back now we're done
      callback(message);
    });
  }
};

bayeux.addExtension(serverAuth);

If you add an error property to a message, the server will not process the message further and will simply return it to the sender, effectively blocking the subscription attempt. You should always make sure your extension calls the callback, as failing to do so could block delivery of other messages in the same request.

When implementing authentication, remember that a message published to channel /foo/bar/qux will be routed to subscriptions to /foo/bar/qux, /foo/bar/*, /foo/bar/**, /foo/** and /**. Take appropriate measures in your extensions to correctly authenticate subscriptions.

On the client side, you’ll need to make sure the client sends the right auth token to satisfy the server. We do this by adding an outgoing extension on the client side.

var clientAuth = {
  outgoing: function(message, callback) {
    // Again, leave non-subscribe messages alone
    if (message.channel !== '/meta/subscribe')
      return callback(message);

    // Add ext field if it's not present
    if (!message.ext) message.ext = {};

    // Set the auth token
    message.ext.authToken = 'rt6utrb';

    // Carry on and send the message to the server
    callback(message);
  }
};

client.addExtension(clientAuth);

If an extension has an added() method, that will be called when the extension is added to its host. To remove an extension, call:

// Calls extension.removed() if defined
hostObject.removeExtension(extension);

To write extensions you’ll need to know what kinds of messages are used by the Bayeux protocol; see the specification for more details.