Skip to content

Commit

Permalink
Added initial codebase
Browse files Browse the repository at this point in the history
  • Loading branch information
shamasis committed Nov 30, 2016
1 parent 8007855 commit 09e45ac
Show file tree
Hide file tree
Showing 33 changed files with 10,152 additions and 0 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib/sandbox/vendor/sugar.js
34 changes: 34 additions & 0 deletions .jsdoc-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"tags": {
"allowUnknownTags": true,
"dictionaries": ["jsdoc", "closure"]
},
"source": {
"include": [ ],
"includePattern": ".+\\.js(doc)?$",
"excludePattern": "(^|\\/|\\\\)_"
},

"plugins": [
"plugins/markdown"
],

"templates": {
"cleverLinks": false,
"monospaceLinks": false,
"highlightTutorialCode" : true
},

"opts": {
"template": "./node_modules/postman-jsdoc-theme",
"encoding": "utf8",
"destination": "./out/docs",
"recurse": true,
"readme": "README.md"
},

"markdown": {
"parser": "gfm",
"hardwrap": false
}
}
14 changes: 14 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**!
* @license Copyright 2016 Postdot Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
module.exports = require('./lib');
48 changes: 48 additions & 0 deletions lib/bootcode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
var _ = require('lodash'),
bundle = require('./bundle'),
env = require('./environment'),

cache,
bundler,
cacher;

// we first try and load the pre-bundled file from file-cache. file cache might be absent during development phase and
// as such, we fall back to live bundling.
try {
bundler = require('../.cache/bootcode');
}
catch (e) {
console.info('sandbox: ' + e.message + '\n' +
'bootcode is being live compiled. use `npm run cache` to use cached variant.');
}

// in case bundler is not a valid function, we create a bundler that uses the environment to compile sandbox bootstrap
// code
!_.isFunction(bundler) && (bundler = function (done) {
bundle.load(env).compile(done);
});

cacher = function (done) {
// in case the cache is already populated, we simply forward the cached string to the caller
if (cache) {
return done(null, cache);
}

// since the code is not cached, we fetch the code from the bundler (it could be file cached or live compiled) and
// then cache it before forwarding it to caller.
bundler(function (err, code) {
if (err) { return done(err); }

// ensure buffer is stringified before being cached
(code && !_.isString(code)) && (code = code.toString());
if (code && _.isString(code)) { // before caching we check the code as string once more
cache = code;
cacher.cached = true; // a flag to aid debugging
}
code && _.isString(code) && (cache = code);

return done(null, cache);
});
};

module.exports = cacher;
7 changes: 7 additions & 0 deletions lib/bundle/bundling-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
insertGlobalVars: false,
browserField: false,
bare: true,
builtins: false,
commondir: true
};
19 changes: 19 additions & 0 deletions lib/bundle/index.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Error message to trigger if bundling is accidentally triggered inside a browser
*
* @constant
* @private
* @type {String}
*/
var ERROR_MESSAGE = 'sandbox: code bundling is not supported in browser. use cached templates.',
StubBundle;

StubBundle = function StubBundle () {
throw new Error(ERROR_MESSAGE);
};

StubBundle.load = function () {
throw new Error(ERROR_MESSAGE);
};

module.exports = StubBundle;
72 changes: 72 additions & 0 deletions lib/bundle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
var _ = require('lodash'),
browserify = require('browserify'),
browserifyBuiltins = require('browserify/lib/builtins'),
bundlingOptions = require('./bundling-options'),

PREFER_BUILTIN = 'preferBuiltin',

defaultCompressOptions = {
transformer: 'uglifyify',
options: {
output: {ascii_only: true},
global: true
}
},

Bundle;

/**
* Create a bundle from an options template
* @constructor
*
* @param {Object} options
* @param {Object} options.files
* @param {Object} options.require
* @param {Boolean|Object} options.compress
* @param {Object=} [options.bundler]
*/
Bundle = function (options) {
/**
* @private
* @memberOf Bundler.prototype
* @type {Browserify}
*/
this.bundler = browserify(_.defaults(options.bundler, bundlingOptions)); // merge with user options

// add the transformer for compression
if (options.compress) {
this.bundler.transform(_.defaults(options.compress, defaultCompressOptions.options),
defaultCompressOptions.transformer);
}

// process any list of modules externally required and also accommodate the use of built-ins if needed
_.forEach(options.require, function (options, resolve) {
if (_.get(options, PREFER_BUILTIN) && _.has(browserifyBuiltins, resolve)) { // @todo: add tests
this.bundler.require(browserifyBuiltins[resolve], _.defaults(options, {
expose: resolve
}));
}
else {
this.bundler.require(require.resolve(resolve), options); // @todo: add tests for resolve failures
}
}.bind(this));

// add files that are needed
_.forEach(options.files, function (options, file) {
this.bundler.add(file, options);
}.bind(this));
};

_.assign(Bundle.prototype, /** @lends Bundle.prototype */ {
compile: function (done) {
return this.bundler.bundle(done);
}
});

_.assign(Bundle, /** @lends Bundle */ {
load: function (options) {
return new Bundle(options);
}
});

module.exports = Bundle;
22 changes: 22 additions & 0 deletions lib/environment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
require: {
events: {preferBuiltin: true},
_process: {preferBuiltin: true},
timers: {preferBuiltin: true},
'buffer-browserify': {expose: 'buffer'},
'liquid-json': {expose: 'json'},
lodash3: {expose: 'lodash'},
'crypto-js': true,
atob: true,
btoa: true,
tv4: true,
xml2js: true,
backbone: true,
cheerio: true
},
files: {
'./lib/sandbox/vendor/sugar': true,
'./lib/sandbox/purse': true,
'./lib/sandbox': true
}
};
112 changes: 112 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
var _ = require('lodash'),
inherits = require('inherits'),
uuid = require('uuid'),
UniversalVM = require('uvm'),
bootcode = require('./bootcode'),

PostmanSandbox;


PostmanSandbox = function PostmanSandbox (options, callback) {
this.executing = {};

UniversalVM.call(this, options, function (err, context) {
if (err) { return callback(err); }
context.ping(function (err) {
callback(err, context);
context = null;
});
});
};

inherits(PostmanSandbox, UniversalVM);

_.assign(PostmanSandbox.prototype, {
ping: function (callback) {
var packet = uuid(),
start = Date.now();

this.once('pong', function (echo) {
callback((echo !== packet ? new Error('sandbox: ping packet mismatch') : null), Date.now() - start, packet);
});

this.dispatch('ping', packet);
},

/**
* @param {String} code
* @param {Object} options
* @param {Boolean} options.debug
* @param {Number} options.timeout
* @param {String} options.scriptType - 'test', 'prerequest'
* @param {Object} options.legacy
* @param {Object} options.legacy.environment
* @param {Object} options.legacy.globals
* @param {Object} options.legacy.request
* @param {Object} options.legacy.responseCookies
* @param {Object} options.legacy.responseHeaders
* @param {String} options.legacy.responseBody
* @param {Number} options.legacy.responseTime
* @param {Object} options.legacy.responseCode
* @param {Object} options.legacy.tests
* @param {Number} options.legacy.iteration
* @param {Function} callback
*/
execute: function (code, options, callback) {
if (_.isFunction(options) && !callback) {
callback = options;
options = null;
}
!_.isObject(options) && (options = {});

var id = uuid(),
executionEventName = `execution.${id}`;
this.executing[id] = true;

// @todo decide how the results will return in a more managed fashion
this.once(executionEventName, function (err, result) {
delete this.executing[id];
this.emit('execution', err, id, result);
callback(err, result);
});

// set execution timeout
// @todo add tests
_.isFinite(options.timeout) && setTimeout(this.emit.bind(this, executionEventName,
new Error('sandbox: execution timeout')), options.timeout);

// send the code to the sendbox to be intercepted and executed
this.dispatch('execute', id, code, _.omit(options, 'data'), _.get(options, 'data', {}));
},

dispose: function () {
Object.keys(this.executing[id]).forEach(function (id) {
this.emit(`execution.${id}`, new Error('sandbox: execution interrupted, bridge disconnecting.'));
});
this.disconnect();
}
});

_.assign(PostmanSandbox, {
create: function (options, callback) {
return new PostmanSandbox(options, callback);
}
});

module.exports = {
createContext: function (options, callback) {
if (_.isFunction(options) && !callback) {
callback = options;
options = {};
}

options = _.clone(options);
bootcode(function (err, code) {
if (err) { return callback(err); }
if (!code) { return callback(new Error('sandbox: bootcode missing!')); }

options.bootcode = code; // assign the code in options
PostmanSandbox.create(options, callback);
});
}
};
48 changes: 48 additions & 0 deletions lib/sandbox/execute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
var Scope = require('uniscope'),
postmanLegacyInterface = require('./postman-legacy-interface');
var gg = this;
module.exports = {
listener: function (prefix, ctx) {
/**
* @param {String} id
* @param {Code} code
* @param {Object} options
* @param {String} options.scriptType // @todo make sdk event
* @param {Object} data
* @param {Object~VariableScope} data.globals
* @param {Object~VariableScope} data.environment
* @param {Object} data.cursor
* @param {Object} data.iterationData
* @param {Object~Request} data.request
* @param {Object~Response} data.response
* @param {Object} data.legacy
* @param {Array} data.legacy.responseCookies,
* @param {String} data.legacy.responseBody,
* @param {Object} data.legacy.responseCode,
* @param {Object} data.legacy.responseHeaders,
* @param {Object} data.legacy.responseTime
*/
return function (id, code, options, data) {
var bridge = this,
legacyInterface = postmanLegacyInterface.create(_.pick(options, 'scriptType'), _.get(data, 'legacy')),
scope = Scope.create({
console: options.debug,
eval: true
}, {
postman: legacyInterface
}),
dispatchEventName = prefix + id,
done = (function (dispatchEventName) {
var sealed = false;

return function (err) {
if (sealed) { return; }
sealed = true;
bridge.dispatch(dispatchEventName, err || null);
};
}(dispatchEventName));

scope.exec(code, done);
};
}
};
Loading

0 comments on commit 09e45ac

Please sign in to comment.