blob: 714760d1aaad520bc7fcb300dad7a675a553c111 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/**
* @fileoverview Vanadium Runtime
* @private
*/
var EE = require('eventemitter2').EventEmitter2;
var isBrowser = require('is-browser');
var Deferred = require('../lib/deferred');
var inherits = require('inherits');
var Server = require('../rpc/server');
var ServerRouter = require('../rpc/server-router');
var GranterRouter = require('../rpc/granter-router');
var leafDispatcher = require('../rpc/leaf-dispatcher');
var Client = require('../rpc/client');
var Namespace = require('../naming/namespace');
var CaveatValidatorRegistry = require('../security/caveat-validator-registry');
var Principal = require('../security/principal');
var vlog = require('../lib/vlog');
var context = require('../context');
var SharedContextKeys = require('./shared-context-keys');
var vtrace = require('../vtrace');
var Controller =
require('../gen-vdl/v.io/x/ref/services/wspr/internal/app').Controller;
var BlessingsCache = require('../security/blessings-cache');
var BlessingsRouter = require('../security/blessings-router');
module.exports = {
init: init
};
/*
* Initialize a Vanadium Runtime.
* Runtime exposes entry points to create servers, client, blessing and other
* Vanadium functionality.
* @private
*/
function init(options, cb) {
var rt = new Runtime(options);
var promise = Promise.resolve(rt);
if (isBrowser) {
// In the browser, we must create the app instance in browspr. We send
// along the namespaceRoots and proxy, if they have been provided. If they
// are empty, the defaults from the extension options page will be used.
var settings = {
namespaceRoots: options.namespaceRoots || [],
proxy: options.proxy || ''
};
promise = promise.then(function(rt) {
var def = new Deferred();
rt._getProxyConnection().createInstance(settings, function(err) {
if (err) {
return def.reject(err);
}
def.resolve(rt);
});
return def.promise;
});
}
if (cb) {
promise.then(function(rt) {
cb(null, rt);
}, function(err) {
cb(err);
});
}
return promise;
}
/**
* Crash event.
* <p>Emitted when the runtime crashes in an unexpected way. Recovery from
* crash event requires restarting the application.<p>
* @event module:vanadium~Runtime#crash
*/
/**
* @summary
* Runtime exposes entry points to create servers, client, namespace client and
* other Vanadium functionality.
* @Description
* <p>This constructor should not be used directly, instead use
* [vanadium.init]{@link module:vanadium.init}</p>
*
* <p>Runtime is also an EventEmitter and emits
* [crash event]{@link module:vanadium~Runtime#event:crash} when it crashes in
* an unexpected way.</p>
*
* @property {string} accountName The accountName that the user associated to
* this runtime.
* @property {module:vanadium.security~Principal} principal The principal
* associated with this runtime. All operations that come from this
* runtime, including operations that come from
* [Servers]{@link module:vanadium.rpc~Server} and
* [Clients]{@link module:vanadium.rpc~Client}, will use this principal.
* @property {module:vanadium.security~CaveatValidatorRegistry} caveatRegistry
* Used to register custom first party caveat validators.
* @inner
* @memberof module:vanadium
* @constructor
*/
function Runtime(options) {
if (!(this instanceof Runtime)) {
return new Runtime(options);
}
EE.call(this);
this.accountName = options.accountName;
this._wspr = options.wspr;
this._client = new Client(this._getProxyConnection());
this._controller = this._client.bindWithSignature(
'__controller', [Controller.prototype._serviceDescription]);
this.principal = new Principal(this.getContext(), this._controller);
this._name = options.appName;
this._language = options.language;
this.caveatRegistry = new CaveatValidatorRegistry();
this.blessingsCache = new BlessingsCache();
this._blessingsRouter = new BlessingsRouter(this._getProxyConnection(),
this.blessingsCache);
}
inherits(Runtime, EE);
/**
* Closes the runtime, freeing all the related resources and stopping and
* unpublishing all the servers created in the runtime.
*
* @param {function} [cb] Gets called once the runtime is closed.
* @returns {Promise} Promise that will be resolved or rejected when runtime is
* closed.
*/
Runtime.prototype.close = function(cb) {
if (this._crashed) {
// NaCl plugin crashed. Shutting down will not work.
return process.nextTick(function() {
cb(new Error('Runtime crashed, can not shutdown gracefully.'));
});
}
var router = this._getRouter();
var proxy = this._getProxyConnection();
return router.cleanup().then(function() {
return proxy.close(cb);
});
};
/* jshint ignore:start */
/**
* NewServerOptionalArgs is a set of options that are passed to the
* [serve]{@link module:vanadium~Runtime#newServer}.
* @typedef module:vanadium.rpc~Server~NewServerOptionalArgs
* @property {module:vanadium.security.Authorize} authorizer An Authorizer
* that will handle the authorization for the method call. If null, then the
* default strict authorizer will be used.
* @property {module:vanadium.rpc~Server~ServerOption} serverOption Optional
* server configuration such as whether the server is a mount table or
* represents a leaf server. serverOption can be created with the
* [vanadium.rpc.serverOption(opts)]{@link module:vanadium.rpc#serverOption}
* method.
*/
/**
* Callback passed into NewServer and NewDispatchingServer
* @callback module:vanadium.rpc~Server~NewServer-callback
* @param {Error} err An error if one occurred.
* @param {module:vanadium.rpc~Server} server The server object.
*/
// TODO(aghassemi) the serviceObject example needs to point to a "Guides" page
// on the website when we have it. https://github.com/vanadium/issues/issues/444
/**
* <p>Asynchronously creates a server and associates object with name by
* publishing the address of the server with the mount table under the supplied
* name and using authorizer to authorize access to it.</p>
* <p>If name is an empty string, no attempt will made to publish that
* name to a mount table.
* To publish the same object under multiple names,
* {@link module:vanadium.rpc~Server#addName|addName} can be used.</p>
* <p>Simple usage:</p>
* <pre>
* rt.newServer('service/name', serviceObject, {
* authorizer: serviceAuthorizer
* }, function(server) {
* // server is now active and listening for RPC calls.
* });
* </pre>
* <p>
* serviceObject is simply a JavaScript object that implements service methods.
* </p>
* <p>
* <pre>
* var serviceObject = new MyService();
* function MyService() {}
* </pre>
* <p>
* Each service method must take [ctx]{@link module:vanadium.context.Context}
* and [serverCall]{@link module:vanadium.rpc~ServerCall} as the
* first two parameters.
* </p>
* <p>
* The output arguments can be given in several forms - through direct return,
* return of a promise or calling a callback that is optionally the
* last parameter.
* </p>
* <pre>
* // Sync method that echoes the input text immediately.
* MyService.prototype.echo = function(ctx, serverCall, text) {
* return 'Echo: ' + text;
* };
* </pre>
* <pre>
* // Async method that echoes the input text after 1 second, using Promises.
* MyService.prototype.delayedEcho = function(ctx, serverCall, text) {
* return new Promise(function(resolve, reject) {
* setTimeout(function() {
* resolve('Echo: ' + text);
* }, 1000);
* });
* };
*</pre>
*<pre>
* // Async method that echoes the input text after 1 second, using Callbacks.
* MyService.prototype.delayedEcho = function(ctx, serverCall, text, callback) {
* setTimeout(function() {
* // first argument to the callback is error, second argument is results
* callback(null, 'Echo: ' + text);
* }, 1000);
* };
*</pre>
*
* @public
* @param {string} name Name to publish under.
* @param {object} serviceObject The service object that has a set of
* exported methods.
* @param {module:vanadium.rpc~Server~NewServerOptionalArgs} [optionalArgs]
* Optional arguments for newServer such as 'authorizer' or 'serverOptions'.
* @param {module:vanadium.rpc~Server~NewServer-callback} [cb] If provided,
* the function will be called when server is ready and listening for RPC calls.
* @return {Promise<module:vanadium.rpc~Server>} Promise to be called when
* server is ready and listening for RPC calls.
*/
/* jshint ignore:end */
Runtime.prototype.newServer = function(name, serviceObject, optionalArgs, cb) {
if (typeof optionalArgs === 'function') {
cb = optionalArgs;
optionalArgs = undefined;
}
optionalArgs = optionalArgs || {};
var dispatcher = leafDispatcher(serviceObject, optionalArgs.authorizer);
return this.newDispatchingServer(name, dispatcher,
optionalArgs.serverOption, cb);
};
/**
* @typedef module:vanadium.rpc~Server~ServerDispatcherResponse
* @type {object}
* @property {object} service The Invoker that will handle
* method call.
* @property {module:vanadium.security.Authorize} authorizer An Authorizer that
* will handle the authorization for the method call. If null, then the default
* authorizer will be used.
*/
/**
* A function that returns the service implementation for the object identified
* by the given suffix.
* @callback module:vanadium.rpc~Server~ServerDispatcher
* @param {string} suffix The suffix for the call.
* @param {module:vanadium.rpc~Server~ServerDispatcher-callback} cb
* The callback to call when the dispatch is complete.
* @return {Promise<module:vanadium.rpc~Server~ServerDispatcherResponse>}
* Either the DispatcherResponse object to
* handle the method call or a Promise that will be resolved the service
* callback.
*/
/**
* Callback passed into Dispatcher.
* @callback module:vanadium.rpc~Server~ServerDispatcher-callback
* @param {Error} err An error if one occurred.
* @param {object} object The object that will handle the method call.
*/
/**
* <p>Asynchronously creates a server and associates dispatcher with the
* portion of the mount table's name space for which name is a prefix, by
* publishing the address of this dispatcher with the mount table under the
* supplied name.
* RPCs invoked on the supplied name will be delivered to the supplied
* Dispatcher's lookup method which will in turn return the object. </p>
* <p>Simple usage:</p>
* <pre>
* rt.newDispatchingServer('service/name', dispatcher, function(server) {
* // server is now active and listening for RPC calls.
* });
* </pre>
*
* <p>If name is an empty string, no attempt will made to publish that
* name to a mount table.
* To publish the same object under multiple names,
* {@link module:vanadium.rpc~Server#addName|addName} can be used.</p>
*
* @public
* @param {string} name Name to publish under.
* @param {module:vanadium.rpc~Server~ServerDispatcher} dispatcher A function
* that will take in the suffix and the method to be called and return the
* service object for that suffix.
* @property {module:vanadium.rpc~Server~ServerOption} [serverOption] Optional
* server configuration such as whether the server is a mount table or
* represents a leaf server. serverOption can be created with the
* [vanadium.rpc.serverOption(opts)]{@link module:vanadium.rpc#serverOption}
* method.
* @param {module:vanadium.rpc~Server~NewServer-callback} [cb] If provided,
* the function will be called when server is ready and listening for RPC calls.
* @return {Promise<module:vanadium.rpc~Server>} Promise to be called when
* server is ready and listening for RPC calls.
*/
Runtime.prototype.newDispatchingServer = function(name, dispatcher,
serverOption, cb) {
if (typeof serverOption === 'function') {
cb = serverOption;
serverOption = undefined;
}
var def = new Deferred(cb);
var server = new Server(this._getRouter());
server._init(name, dispatcher, serverOption).then(function() {
def.resolve(server);
}).catch(def.reject);
return def.promise;
};
/**
* Returns a [Client]{@link module:vanadium.rpc~Client} instance.<br>
* Client allows one to bind to Vanadium names and call methods on them.
* @return {module:vanadium.rpc~Client} A Client instance.
*/
Runtime.prototype.getClient = function() {
return this._client;
};
/**
* Returns the root runtime [context]{@link module:vanadium.context.Context}<br>
* Context objects provide a number of features such as
* ability to provide configuration for requests such as timeout durations,
* tracing across requests for debugging, etc...<br>
* In order to provide these facilities context objects are required as the
* first parameter for client calls and also for requests that are incoming
* to servers.
* @return {module:vanadium.context.Context} The root runtime context.
*/
Runtime.prototype.getContext = function() {
if (this._rootCtx) {
return this._rootCtx;
}
var ctx = new context.Context();
ctx = ctx.withValue(SharedContextKeys.COMPONENT_NAME, this._name);
if (this._language) {
ctx = ctx.withValue(SharedContextKeys.LANG_KEY, this._language);
}
ctx = ctx.withValue(SharedContextKeys.RUNTIME, this);
ctx = vtrace.withNewStore(ctx);
ctx = vtrace.withNewTrace(ctx);
this._rootCtx = ctx;
return ctx;
};
/**
* <p>Returns a [namespace]{@link module:vanadium.naming~Namespace} client.</p>
* <p>Namespace client enables interactions with the Vanadium namespace such as
* globbing, mounting, setting permissions and other name related operations.
* </p>
* @return {module:vanadium.naming~Namespace} A namespace client instance.
*/
Runtime.prototype.getNamespace = function() {
this._ns = this._ns || new Namespace(this.getClient(),
this.getContext());
return this._ns;
};
/**
* Get or creates a new proxy connection
* @return {ProxyConnection} A proxy connection
* @private
*/
Runtime.prototype._getProxyConnection = function() {
if (this._proxyConnection) {
return this._proxyConnection;
}
var ProxyConnection;
if (this._wspr) {
vlog.logger.info('Using WSPR at: %s', this._wspr);
ProxyConnection = require('../proxy/websocket');
this._proxyConnection = new ProxyConnection(this._wspr);
} else {
vlog.logger.info('Using the Vanadium Extension\'s NaCl WSPR');
ProxyConnection = require('../proxy/nacl');
this._proxyConnection = new ProxyConnection();
}
// relay crash event from proxy
var self = this;
this._proxyConnection.on('crash', function(e) {
self._crashed = true;
self.emit('crash', e);
});
return this._proxyConnection;
};
/**
* Get or creates a router
* @return {ServerRouter} A router
* @private
*/
Runtime.prototype._getRouter = function() {
if (!this._router) {
this._router = new ServerRouter(
this._getProxyConnection(),
this._name,
this.getContext(),
this._controller,
this.caveatRegistry,
this.blessingsCache);
}
return this._router;
};
/**
* Get or creates a granter router
* @return {GranterRouter} A granter router
* @private
*/
Runtime.prototype._getGranterRouter = function() {
if (!this._granterRouter) {
this._granterRouter = new GranterRouter(
this._getProxyConnection(),
this.getContext(),
this.blessingsCache);
}
return this._granterRouter;
};