src/hub.js
/*jslint indent:2,sloppy:true,node:true */
var util = require('./util');
/**
* Defines fdom.Hub, the core message hub between freedom modules.
* Incomming messages from apps are sent to hub.onMessage()
* @class Hub
* @param {Debug} debug Logger for debugging.
* @constructor
*/
var Hub = function (debug) {
this.debug = debug;
this.config = {};
this.apps = {};
this.routes = {};
util.handleEvents(this);
this.on('config', function (config) {
util.mixin(this.config, config);
}.bind(this));
};
/**
* Handle an incoming message from a freedom app.
* @method onMessage
* @param {String} source The identifiying source of the message.
* @param {Object} message The sent message.
*/
Hub.prototype.onMessage = function (source, message) {
var destination = this.routes[source], type;
if (!destination || !destination.app) {
this.debug.warn("Message dropped from unregistered source " + source);
return;
}
if (!this.apps[destination.app]) {
this.debug.warn("Message dropped to destination " + destination.app);
return;
}
// The firehose tracing all internal freedom.js messages.
if (!message.quiet && !destination.quiet && this.config && this.config.trace) {
type = message.type;
if (message.type === 'message' && message.message &&
message.message.action === 'method') {
type = 'method.' + message.message.type;
} else if (message.type === 'method' && message.message &&
message.message.type === 'method') {
type = 'return.' + message.message.name;
} else if (message.type === 'message' && message.message &&
message.message.type === 'event') {
type = 'event.' + message.message.name;
}
this.debug.debug(this.apps[destination.source].toString() +
" -" + type + "-> " +
this.apps[destination.app].toString() + "." + destination.flow);
}
this.apps[destination.app].onMessage(destination.flow, message);
};
/**
* Get the local destination port of a flow.
* @method getDestination
* @param {String} source The flow to retrieve.
* @return {Port} The destination port.
*/
Hub.prototype.getDestination = function (source) {
var destination = this.routes[source];
if (!destination) {
return null;
}
return this.apps[destination.app];
};
/**
* Get the local source port of a flow.
* @method getSource
* @param {Port} source The flow identifier to retrieve.
* @return {Port} The source port.
*/
Hub.prototype.getSource = function (source) {
if (!source) {
return false;
}
if (!this.apps[source.id]) {
this.debug.warn("No registered source '" + source.id + "'");
return false;
}
return this.apps[source.id];
};
/**
* Register a destination for messages with this hub.
* @method register
* @param {Port} app The Port to register.
* @param {Boolean} [force] Whether to override an existing port.
* @return {Boolean} Whether the app was registered.
*/
Hub.prototype.register = function (app, force) {
if (!this.apps[app.id] || force) {
this.apps[app.id] = app;
return true;
} else {
return false;
}
};
/**
* Deregister a destination for messages with the hub.
* Note: does not remove associated routes. As such, deregistering will
* prevent the installation of new routes, but will not distrupt existing
* hub routes.
* @method deregister
* @param {Port} app The Port to deregister
* @return {Boolean} Whether the app was deregistered.
*/
Hub.prototype.deregister = function (app) {
if (!this.apps[app.id]) {
return false;
}
delete this.apps[app.id];
return true;
};
/**
* Install a new route in the hub.
* @method install
* @param {Port} source The source of the route.
* @param {Port} destination The destination of the route.
* @param {String} flow The flow where the destination will receive messages.
* @param {Boolean} quiet Whether messages on this route should be suppressed.
* @return {String} A routing source identifier for sending messages.
*/
Hub.prototype.install = function (source, destination, flow, quiet) {
source = this.getSource(source);
if (!source) {
return;
}
if (!destination) {
this.debug.warn("Unwilling to generate blackhole flow from " + source.id);
return;
}
var route = this.generateRoute();
this.routes[route] = {
app: destination,
flow: flow,
source: source.id,
quiet: quiet
};
if (typeof source.on === 'function') {
source.on(route, this.onMessage.bind(this, route));
}
return route;
};
/**
* Uninstall a hub route.
* @method uninstall
* @param {Port} source The source of the route.
* @param {String} flow The route to uninstall.
* @return {Boolean} Whether the route was able to be uninstalled.
*/
Hub.prototype.uninstall = function (source, flow) {
source = this.getSource(source);
if (!source) {
return;
}
var route = this.routes[flow];
if (!route) {
return false;
} else if (route.source !== source.id) {
this.debug.warn("Flow " + flow + " does not belong to port " + source.id);
return false;
}
delete this.routes[flow];
if (typeof source.off === 'function') {
source.off(route);
}
return true;
};
/**
* Remove all listeners and notify all connected destinations of their removal.
* @method teardown
*/
Hub.prototype.teardown = function () {
util.eachProp(this.apps, function (source) {
if (typeof source.off === 'function') {
source.off();
}
});
this.apps = {};
this.routes = {};
};
/**
* Generate a unique routing identifier.
* @method generateRoute
* @return {String} a routing source identifier.
* @private
*/
Hub.prototype.generateRoute = function () {
return util.getId();
};
module.exports = Hub;