subetha-bridge

0.0.2-alpha • Public • Published

SubEtha-Bridge

Host SubEtha clients and messages

version 0.0.2-alpha by Bemi Faison

Description

SubEtha-Bridge (or Bridge) is a supporting library within the SubEtha messaging architecture. A bridge routes messages between clients on the same bridge, and relays messages between bridges on the same "network" - i.e., the channel and url origin.

Bridges primarily implement the SubEtha protocol for communicating with Clients, and require zero configuration. Some security, monitoring and bootstrapping options are available.

Note: Please see the SubEtha project page, for important background information, plus development, implementation, and security considerations.

Usage

The Bridge module runs in a web page that is separate from your application code. Bridges are loaded in iframes by the SubEtha-Client library, as needed.

Hosting your own Bridge

Implementing your own Bridge is a simple matter of hosting a static web page. The web page should do little more than load the Bridge module, except for however you choose to customize it's behavior.

Below demonstrates a complete bridge implementation that you could host on any server.

<!doctype html>
<html>
<head>
  <script src="path/to/subetha-bridge.min.js"></script> 
  <title>My SubEtha-Bridge</title>
</head>
</html>

Connecting to your Bridge

If the url to your bridge were http://my.site.com/bridge.html, clients could then connect to it in their applications, like so:

// open a channel on your bridge
var myAppClient = new Subetha.Client();
myAppClient.open('some-channel@my.site.com/bridge.html');

If applications plan to use several clients with your bridge, they might set and use a convenient alias, instead.

// define an alias to your bridge url
Subetha.urls.mybridge = 'my.site.com/bridge.html';
 
// open a channel using an alias
var myAppClient = new Subetha.Client();
myAppClient.open('some-channel@mybridge');

Bootstrapping Asynchronous Bridges

The Bridge module has a bootstrap process that involves capturing the first message event - sent via postMessage, from the SubEtha-Client in the parent window. It is strongly recommended that you load the Bridge module synchronously (via a SCRIPT tag, or otherwise, before the DOM is ready). If you do load the Bridge module asynchronously, you must capture the first message event and pass it's data, manually.

Below demonstrates loading the Bridge module with require.js and capturing the first message event. It ensures the bridge is only initialized when both it and the event are available, regardless of load order.

<!doctype html>
<html>
<head>
  <script data-main="scripts/config" src="js/require.js"></script> 
  <script>
  (function () {
    var
      bridgeRef,
      bootData;
 
    window.addEventListener('message', function myCb(evt) {
      // remove our one-time listener
      window.removeEventListener('message', myCb);
      // capture data from first message
      bootData = evt.data;
 
      tryBridgeInit();
    });
 
    requirejs(['subetha-bridge'], function (SBridge) {
      // capture bridge reference
      bridgeRef = SBridge;
 
      tryBridgeInit();
    });
 
    //initialize bridge when both are ready
    function tryBridgeInit() {
      if (bridgeRef && bootData) {
        // start bridge with captured data
        bridgeRef.init(bootData);
      }
    }
  })();
  </script> 
</head>
</html>

Note: The above leaves scripts/config to configure the "subetha-bridge" module path and dependencies.

Handling Client Requests

Bridges handle two types of client requests: authentication requests, and message-relay requests. These requests are accepted by default, such that any client may join a channel and send any message.

To handle requests manually, simply subscribe to the "auth" and/or "relay" events. Callbacks receive a request object, which represents the suspended state of a request. Requests may be answered asynchronously, but can only be answered once.

// handle client authentication requests
SBridge.on('auth', function (req) {
  // perform logic now and/or handle this request later
});
 
// handle message relay requests
SBridge.on('relay', function (req) {
  // perform logic now and/or handle this request later
});

To answer a request invoke it's #allow(), #deny(), or #ignore() method. The first call to any method will return true, but once handled these methods (do nothing and) return false.

SBridge.on('auth', function (req) {
  if (req.client.origin != 'http://example.com') {
    // the first method called, wins
    req.deny(); // returns `true`
  }
  // does nothing if already denied
  req.allow(); // returns `false`
});

Note: The same request object is passed to all callbacks. If using multiple callbacks for an event, ensure only - and, at least - one handles the request object.

To restore the default behavior, remove all subscriptions to the given event type. Invoke #off(), passing only the event type.

// re-enable default handling of authentication requests
SBridge.off('auth');
 
// re-enable default handling of message-relay requests
SBridge.off('relay');

Authenticating Clients

Authentication-requests may be handled in any order, and are stored in the SBridge.pendingAuths hash until processed. If your bridge employs some form of authorization, requests expose any credentials given by the client, in a .credentials array. (An empty array, means no credentials were sent.)

SBridge.on('auth', function (req) {
  if (req.credentials.length) {
    // verify credentials, now or later
  } else {
    req.deny();
  }
});

Relaying Messages

Message-relay requests can only be handled in the order they are received. When a request is suspended, it is passed to subscribers of the "relay" event, and set in the SBridge.pendingRelay namespace.

It's important to consider latency, when processing a relay request. More message-relay requests will pile up as you perform your logic, which can reduce perceived performance of your bridge. Try to handle message-relay requests immediately, instead of asynchronously.

Below, demonstrates logic that prevents broadcast messages - messages without a recipient.

SBridge.on('relay', function (req) {
  // the message has no client id in the "to" field
  if (!req.msg.to) {
    req.deny();
  } else {
    req.allow();
  }
});

Monitoring Bridge Activity

The following events are available for you to observe, on the Bridge module directly.

  • initialize Fired when the Bridge is awaiting client messages.
  • join Fired when a client has joined the network. The arriving client is passed to callbacks.
  • drop Fired when a client has exited the network. The departed client is passed to callbacks.
  • message Fired when a message is relayed. The delivered message is passed to callbacks.

Note: Unlike the SubEtha-Client module, Bridge events are not prefixed.

Below demonstrates logic that observes when a new client has been authenticated by the current bridge.

SBridge.on('join', function (client) {
  if (SBridge.id == client.bid) {
    // do something, now that a client joined via this bridge
  }
});

API

Below is reference documentation for the SubEtha-Bridge module.

SBridge

A singleton, representing the client authorization and message routing logic for the window.

Bridge events

The bridge fires the following events.

  • relay - Triggered before a message is relayed. Subscribing to this event requires responding to the relay-request.
    • request - An object representing the suspended request.
  • auth - Triggered when a client attempts to use this bridge to join the network.
    • request - An object representing the suspended request.
  • join - Triggered when a client joins the network.
    • client - A reference to the client that recently joined the network.
  • drop - Triggered when a client leaves the network.
    • client - A reference to the client that recently left the network.
  • message - Triggered when a client message is relayed.
    • msg - The message relayed on the network.

SBridge::destroy()

End handling network clients and messages. You can not reuse the bridge once destroyed.

SBridge.destroy();

SBridge::fire()

Triggers callbacks, subscribed to this event.

SBridge.fire(event [, args, ... ]);
  • event: (string) The event to trigger.
  • args: (Mix) Remaining arguments that should be passed to all attached callbacks.

SBridge::init()

Begin handling network clients and messages.

SBridge.init(token);
  • token: (string) A serialized string of parameters needed to bootstrap the SBridge singleton.

This method simply exits if the bridge has already been initialized.

SBridge::off()

Unsubscribe callback(s) from an event. When invoked with no arguments, all subscriptions are removed.

SBridge.off([event [, callback [, scope]]]);
  • event: (string) The event to unsubscribe. When omitted, all event subscribers are removed.
  • callback: (function) The callback to detach from this event. When omitted, all callbacks are detached from this event.
  • scope: (object) Specifies detaching the given callback that is also scoped to the given object. Do not use, unless you attached a callback with a particular scope.

SBridge::on()

Subscribe a callback to an event.

SBridge.on(event, callback [, scope]);
  • event: (string) An arbitrary event name.
  • callback: (function) A callback to invoke when the event is fires.
  • scope: (object) An object to scope the callback invocation. By default, this is the EventEmitter instance.

Sbridge::disabled

Indicates when the bridge is incompatible with the runtime environment.

Sbridge::id

A hash to uniquely identify this bridge.

SBridge::network

The name of the network, as prescribed by the bootstrap token.

SBridge::pendingRelay

The last pending message relay request object. If no relay is pending, the value is null.

SBridge::pendingAuths

Hash of pending authorization request objects. Keys are of clients waiting to gain access.

SBridge::protocol

The SemVer compatible version of the SubEtha protocol supported by this module.

SBridge::version

The SemVer compatible version of this module.

Installation

SubEtha-Bridge works within, and is intended for, modern JavaScript browsers. It is available on bower, component and npm as a CommonJS or AMD module.

If SubEtha-Bridge isn't compatible with your favorite runtime, please file an issue or pull-request (preferred).

Dependencies

SubEtha-Bridge depends on the following modules:

SubEtha-Bridge also uses the following ECMAScript 5 and HTML 5 features:

You will need to implement shims for these browser features in unsupported environments. Note however that postMessage and localStorage shims will only allow this module to run without errors, not work as expected.

Web Browsers

Use a <SCRIPT> tag to load the subetha-bridge.min.js file in your web page. The file includes all module dependencies, for your convenience. Doing so, adds SBridge to the global scope.

  <script type="text/javascript" src="path/to/subetha-bridge.min.js"></script> 
  <script type="text/javascript">
    // ... SubEtha-Bridge dependent code ...
  </script> 

Note: The minified file was compressed by Closure Compiler.

Package Managers

  • npm install subetha-bridge
  • component install bemson/subetha-bridge
  • bower install subetha-bridge

AMD

Assuming you have a require.js compatible loader, configure an alias for the SubEtha-Bridge module (the term "subetha-bridge" is recommended, for consistency). The subetha-bridge module exports a module namespace.

require.config({
  paths: {
    'subetha-bridge': 'libs/subetha-bridge'
  }
});

Then require and use the module in your application code:

require(['subetha-bridge'], function (SBridge) {
  // ... SubEtha Bridge dependent code ...
});

Warning: Do not load the minified file via AMD, since it includes modular dependencies that themselves export modules. Use AMD optimizers like r.js, in order to roll-up your dependency tree.

Note: When loaded asynchronously, you must manually bootstrap this module. (See the usage section for more details.)

License

SubEtha-Bridge is available under the terms of the Apache-License.

Copyright 2014, Bemi Faison

Package Sidebar

Install

npm i subetha-bridge

Weekly Downloads

3

Version

0.0.2-alpha

License

none

Last publish

Collaborators

  • bemson