sandboxjs
Version:
Sandbox node.js code
343 lines (287 loc) • 10.7 kB
JavaScript
var Bluebird = require('bluebird');
var Decode = require('jwt-decode');
var Path = require('path');
var Superagent = require('superagent');
var Url = require('url');
var defaults = require('lodash.defaults');
var defaultsDeep = require('lodash.defaultsdeep');
var forEach = require('lodash.foreach');
var getFrom = require('lodash.get');
/**
* Creates an object representing a Webtask
*
* @constructor
*/
function Webtask (sandbox, token, options) {
if (!options) options = {};
if (sandbox.securityVersion === 'v1') {
try {
/**
* @property claims - The claims embedded in the Webtask's token
*/
this.claims = Decode(token);
/**
* @property token - The token associated with this webtask
*/
this.token = token;
}
catch (_) {
throw new Error('token must be a valid JWT');
}
}
if (sandbox.securityVersion === 'v2') {
if (typeof options.name !== 'string') {
throw new Error('name must be a valid string');
}
this.claims = {
jtn: options.name,
ten: options.container || sandbox.container,
}
}
/**
* @property sandbox - The {@see Sandbox} instance used to create this Webtask instance
*/
this.sandbox = sandbox;
/**
* @property meta - The metadata associated with this webtask
*/
this.meta = options.meta || {};
/**
* @property secrets - The secrets associated with this webtask if `decrypt=true`
*/
this.secrets = options.secrets;
/**
* @property code - The code associated with this webtask if `fetch_code=true`
*/
this.code = options.code;
/**
* @property container - The container name in which the webtask will run
*/
Object.defineProperty(this, 'container', {
enumerable: true,
get: function () {
return options.container || this.sandbox.container;
}
});
/**
* @property url - The public url that can be used to invoke this webtask
*/
Object.defineProperty(this, 'url', {
enumerable: true,
get: function () {
var url = options.webtask_url;
if (!url) {
if (this.claims.host) {
var surl = Url.parse(this.sandbox.url);
url = surl.protocol + '//' + this.claims.host + (surl.port ? (':' + surl.port) : '') + '/' + this.sandbox.container;
}
else {
url = this.sandbox.url + '/api/run/' + this.sandbox.container;
}
if (this.claims.jtn) url += '/' + this.claims.jtn;
else url += '?key=' + this.token;
}
return url;
}
});
}
/**
* Create a stream of logs from the webtask container
*
* Note that the logs will include messages from our infrastructure.
*
* @param {Object} options - Streaming options overrides
* @param {String} [options.container] - The container for which you would like to stream logs. Defaults to the current profile's container.
* @returns {Stream} A stream that will emit 'data' events with container logs
*/
Webtask.prototype.createLogStream = function (options) {
return this.sandbox.createLogStream(options);
};
/**
* Run the webtask and return the result of execution
*
* @param {Object} options - Options used to tweak how the webtask will be invoked
* @param {Function} [cb] - Optional node-style callback that will be invoked upon completion
* @returns {Promise} - A Promise that will be resolved with the response from the server.
*/
Webtask.prototype.run = function (options, cb) {
var methodMap = {
'delete': 'del',
};
var urlData = Url.parse(this.url, true);
var config = defaultsDeep(options || {}, {
method: 'get',
path: '',
query: {},
});
var method = getFrom(methodMap, config.method, config.method);
var query = defaults(urlData.query, config.query);
urlData.pathname = Path.join(urlData.pathname, config.path);
urlData.query = {};
urlData.search = null;
var url = Url.format(urlData);
var request = Superagent[method](url)
.query(query);
if (config.body) request = request.send(config.body);
forEach(config.headers, function (value, header) {
request = request.set(header, value);
});
var promise = Bluebird.resolve(request)
.catch(function (err) {
if (err.response) return err.response;
throw err;
})
.then(function (res) {
res.statusCode = res.status;
return res;
});
return cb ? promise.nodeify(cb, {spread: true}) : promise;
};
/**
* Schedule the webtask to run periodically
*
* @param {Object} options - Options for creating the webtask
* @param {Object} options.schedule - Cron-string-formatted schedule
* @param {Object} [options.name] - The name for the cron job
* @param {Object} [options.tz] - The timezone for the cron job (IANA timezone)
* @param {Function} [cb] - Optional node-style callback that will be invoked upon completion
* @returns {Promise} - A Promise that will be resolved with a {@see CronJob} instance.
*/
Webtask.prototype.createCronJob = function (options, cb) {
if (this.token) {
var parsedUrl = Url.parse(this.claims.url);
var filename = parsedUrl.pathname
? Path.basename(parsedUrl.pathname)
: '';
options = defaultsDeep(options, {
name: this.claims.jtn || filename,
state: 'active',
meta: this.meta,
});
}
else {
if (options.name && options.name !== this.claims.jtn) {
throw new Error('Server does not support specifying CRON name that is different from webtask name.');
}
options = defaultsDeep(options, {
name: this.claims.jtn,
state: 'active',
meta: this.meta,
});
}
if (!options.name) throw new Error('Cron jobs must have a name.');
var promise = this.sandbox.createCronJob({
name: options.name,
token: this.token,
schedule: options.schedule,
state: options.state,
tz: options.tz,
meta: options.meta
});
return cb ? promise.nodeify(cb, {spread: true}) : promise;
};
/**
* Inspect an existing webtask to optionally get code and/or secrets
*
* @param {Object} options - Options for inspecting the webtask.
* @param {Boolean} [options.decrypt] - Decrypt the webtask's secrets.
* @param {Boolean} [options.fetch_code] - Fetch the code associated with the webtask.
* @param {Function} [cb] - Optional callback function for node-style callbacks.
* @returns {Promise} A Promise that will be fulfilled with the result of inspecting the token.
*/
Webtask.prototype.inspect = function (options, cb) {
if (this.sandbox.securityVersion === 'v2') {
throw new Error('The Webtask.inspect function is not supported in the current configuration. Consider using Sandbox.inspectWebtask instead.')
}
options.token = this.token;
var promise = this.sandbox.inspectToken(options);
return cb ? promise.nodeify(cb) : promise;
};
/**
* Remove the named webtask
*
* @param {Function} [cb] - Optional callback function for node-style callbacks.
* @returns {Promise} A Promise that will be fulfilled with the result of inspecting the token.
*/
Webtask.prototype.remove = function (cb) {
var promise;
if (!this.claims.jtn) {
var err = new Error('Unnamed webtasks cannot be removed');
err.statusCode = 400;
promise = Bluebird.reject(err);
return cb ? promise.nodeify(cb) : promise;
} else {
promise = this.sandbox.removeWebtask({
name: this.claims.jtn,
});
}
return cb ? promise.nodeify(cb) : promise;
};
/**
* Revoke the webtask's token
*
* @param {Function} [cb] - Optional callback function for node-style callbacks.
* @returns {Promise} A Promise that will be fulfilled with the result of revoking the token.
*/
Webtask.prototype.revoke = function (cb) {
var promise = this.sandbox.revokeToken(this.token);
return cb ? promise.nodeify(cb) : promise;
};
/**
* Update a webtask
*
* @param {Object} [options] - Options for updating a webtask (@see: Sandbox.updateWebtask)
* @param {Function} [cb] - Optional callback function for node-style callbacks.
* @returns {Promise} A Promise that will be fulfilled with the result of revoking the token.
*/
Webtask.prototype.update = function (options, cb) {
if (typeof options === 'function') {
cb = options;
options = {};
}
if (!options) {
options = {};
}
options.name = this.claims.jtn;
var promise = this.sandbox.updateWebtask(options);
return cb ? promise.nodeify(cb) : promise;
};
Webtask.prototype.toJSON = function () {
var data = {
container: this.container,
token: this.token,
url: this.url,
};
if (this.claims.jtn) data.name = this.claims.jtn;
return data;
};
/**
* Update the storage associated to the a webtask
*
* @param {Object} options - Options
* @param {String} [options.container] - Set the webtask container. Defaults to the profile's container.
* @param {String} options.name - The name of the webtask.
* @param {Object} storage - storage
* @param {Object} storage.data - The data to be stored
* @param {String} storage.etag - Pass in an optional string to be used for optimistic concurrency control to prevent simultaneous updates of the same data.
* @param {Function} [cb] - Optional callback function for node-style callbacks.
* @return {Promise} A Promise that will be fulfilled with an array of Webtasks
*/
Webtask.prototype.updateStorage = function (storage, options, cb) {
var promise = this.sandbox.updateStorage(storage, options);
return cb ? promise.nodeify(cb) : promise;
}
/**
* Read the storage associated to the a webtask
*
* @param {Object} options - Options
* @param {String} [options.container] - Set the webtask container. Defaults to the profile's container.
* @param {String} options.name - The name of the webtask.
* @param {Function} [cb] - Optional callback function for node-style callbacks.
* @return {Promise} A Promise that will be fulfilled with an array of Webtasks
*/
Webtask.prototype.getStorage = function (options, cb) {
var promise = this.sandbox.getStorage(options);
return cb ? promise.nodeify(cb) : promise;
}
module.exports = Webtask;