providers/core/core.unprivileged.js
/*jslint indent:2,white:true,sloppy:true,node:true */
var EventInterface = require('../../src/proxy/eventInterface');
var Consumer = require('../../src/consumer');
var util = require('../../src/util');
/**
* Core freedom services available to all modules.
* Created by the environment helper in response to a 'core' request.
* @Class Core_unprivileged
* @constructor
* @param {Manager} manager The manager this core is connected with.
* @private
*/
var Core_unprivileged = function(manager, postMessage) {
this.manager = manager.module;
this.debug = this.manager.debug;
};
Core_unprivileged.unboundChannels = {};
Core_unprivileged.contextId = undefined;
Core_unprivileged.moduleInternal = undefined;
/**
* Create a custom channel.
* Returns the structure {channel: Proxy, identifier: Object},
* where the identifier can be 'redeemed' by another module or provider using
* bind channel, at which point the deferred object will resolve with a channel
* between the two endpoints.
* @method createChannel
* @params {Function} continuation Method to call with the cosntructed structure.
*/
Core_unprivileged.prototype.createChannel = function(continuation) {
var proxy = new Consumer(EventInterface, this.manager.debug),
id = util.getId(),
chan = this.getChannel(proxy);
this.manager.setup(proxy);
if (this.isInModule()) {
this.manager.emit(this.manager.delegate, {
type: 'Delegation',
request: 'handle',
flow: 'core',
message: {
type: 'register',
id: id
}
});
}
Core_unprivileged.unboundChannels[id] = {
local: true,
proxy: proxy
};
proxy.once('start', this.getChannel.bind(this, proxy));
continuation({
channel: chan,
identifier: id
});
};
Core_unprivileged.prototype.getChannel = function(proxy) {
var iface = proxy.getProxyInterface(),
chan = iface();
chan.close = iface.close;
chan.onClose = iface.onClose;
iface.onClose(chan, function() {
proxy.doClose();
});
return chan;
};
/**
* Receive a message from another core instance.
* Note: Core_unprivileged is not registered on the hub. it is a provider,
* as it's location and name would indicate. This function is called by
* port-app to relay messages up to higher levels. More generally, the
* messages emitted by the core to 'this.manager.emit(this.mananage.delegate'
* Should be onMessaged to the controlling core.
* @param {String} source The source of the message.
* @param {Object} msg The messsage from an isolated core provider.
*/
Core_unprivileged.prototype.onMessage = function(source, msg) {
if (msg.type === 'register') {
Core_unprivileged.unboundChannels[msg.id] = {
remote: true,
resolve: msg.reply,
source: source
};
} else if (msg.type === 'clear') {
delete Core_unprivileged.unboundChannels[msg.id];
} else if (msg.type === 'bind') {
if (Core_unprivileged.unboundChannels[msg.id]) {
this.bindChannel(msg.id, function() {}, source);
}
} else if (msg.type === 'require') {
source.require(msg.id, msg.manifest);
}
};
/**
* Bind a custom channel.
* Creates a proxy interface to the custom channel, which will be bound to
* the proxy obtained through an earlier createChannel call.
* channel to a proxy.
* @method bindChannel
* @param {Object} identifier An identifier obtained through createChannel.
* @param {Function} continuation A function to be called with the proxy.
*/
Core_unprivileged.prototype.bindChannel = function(identifier, continuation, source) {
var toBind = Core_unprivileged.unboundChannels[identifier],
newSource = !source;
// when bindChannel is called directly, source will be undefined.
// When it is propogated by onMessage, a source for binding will already exist.
if (newSource) {
this.debug.debug('making local proxy for core binding');
source = new Consumer(EventInterface, this.debug);
this.manager.setup(source);
}
// If this is a known identifier and is in the same context, binding is easy.
if (toBind && toBind.local) {
this.debug.debug('Binding a channel to port on this hub:' + source);
this.manager.createLink(source, identifier, toBind.proxy, 'default');
delete Core_unprivileged.unboundChannels[identifier];
if (this.manager.delegate && this.manager.toDelegate.core) {
this.manager.emit(this.manager.delegate, {
type: 'Delegation',
request: 'handle',
flow: 'core',
message: {
type: 'clear',
id: identifier
}
});
}
} else if (toBind && toBind.remote) {
this.debug.debug('Binding a channel into a module.');
this.manager.createLink(
source,
newSource ? 'default' : identifier,
toBind.source,
identifier);
toBind.resolve({
type: 'Bind Channel',
request:'core',
flow: 'core',
message: {
type: 'bind',
id: identifier
}
});
delete Core_unprivileged.unboundChannels[identifier];
} else if (this.isInModule()) {
this.debug.info('delegating channel bind for an unknown ID:' + identifier);
this.manager.emit(this.manager.delegate, {
type: 'Delegation',
request: 'handle',
flow: 'core',
message: {
type: 'bind',
id: identifier
}
});
source.once('start', function(p, cb) {
cb(this.getChannel(p));
}.bind(this, source, continuation));
this.manager.createLink(source,
'default',
this.manager.hub.getDestination(this.manager.delegate),
identifier);
delete Core_unprivileged.unboundChannels[identifier];
return;
} else {
this.debug.warn('Asked to bind unknown channel: ' + identifier);
this.debug.log(Core_unprivileged.unboundChannels);
continuation();
return;
}
if (source.getInterface) {
continuation(this.getChannel(source));
} else {
continuation();
}
};
/**
* @method isInModule
* @private
* @returns {Boolean} Whether this class is running in a module.
*/
Core_unprivileged.prototype.isInModule = function () {
return (this.manager.delegate && this.manager.toDelegate.core);
};
/**
* Require a dynamic dependency for your freedom module.
* If new permissions are needed beyond what are already available to the
* freedom context, the user will need to approve of the requested permissions.
* @method require
* @param {String} manifest The URL of the manifest to require.
* @param {String} api The API of the dependency to expose if not default.
* @param {Function} callback The function to call with the dependency.
*/
Core_unprivileged.prototype.require = function (manifest, api, callback) {
if (this.isInModule() && Core_unprivileged.moduleInternal) {
// Register a callback with moduleInternal.
// DependencyName is the name of the channel moduelInternal will allocate
// callback will be called once a link to that channel is seen.
var dependencyName =
Core_unprivileged.moduleInternal.registerId(api, callback);
// Request the dependency be added.
this.manager.emit(this.manager.delegate, {
type: 'Delegation',
request: 'handle',
flow: 'core',
message: {
type: 'require',
manifest: manifest,
id: dependencyName
}
});
} else {
this.debug.error('The require function in external context makes no sense' +
' Instead create a new freedom() context.');
callback(undefined, {
errcode: 'InvalidContext',
message: 'Cannot call require() from this context.'
});
}
};
/**
* Get the ID of the current freedom.js context. Provides an
* array of module URLs, the lineage of the current context.
* When not in an application context, the ID is the lineage
* of the current View.
* @method getId
* @param {Function} callback The function called with ID information.
*/
Core_unprivileged.prototype.getId = function(callback) {
// TODO: make sure contextID is properly frozen.
callback(Core_unprivileged.contextId);
};
/**
* Get a logger for logging to the freedom.js logger. Provides a
* log object with an interface similar to the standard javascript console,
* which logs via debug.
* @method getLogger
* @param {String} name The name of the logger, used as its 'source'
* @param {Function} callback The function to call with the logger.
*/
Core_unprivileged.prototype.getLogger = function(name, callback) {
callback(this.manager.debug.getLogger(name));
};
/**
* Set the ID of the current freedom.js context.
* @method setId
* @private
* @param {String[]} id The lineage of the current context.
* @param {ModuleInternal} moduleInternal The Module environment if one exists.
*/
Core_unprivileged.prototype.setId = function(id, moduleInternal) {
Core_unprivileged.contextId = id;
Core_unprivileged.moduleInternal = moduleInternal;
};
exports.provider = Core_unprivileged;
exports.name = "core";
exports.flags = {module: true};