UNPKG

postman-sandbox

Version:

Sandbox for Postman Scripts to run in Node.js or browser

171 lines (144 loc) 6.31 kB
const _ = require('lodash'), { minify } = require('terser'), bundlingOptions = require('./bundling-options'), PREFER_BUILTIN = 'preferBuiltin', /** * To unite components of path in holy matrimony! * * @return {String} */ pathJoin = function () { return Array.prototype.join.call(arguments, '/').replace(/\/{1,}/g, '/'); }; let browserify, // loaded inside try-catch browserifyBuiltins; // loaded inside try-catch // The modules for browserification should only be required during development of this module and as such a production // installation should not even trigger require of this module. But in case it does, let's make the error message a bit // more consumable. try { browserify = require('browserify'); browserifyBuiltins = require('browserify/lib/builtins'); } catch (e) { console && console.error('sandbox: bundling triggered in production module installation mode'); throw e; } class Bundle { /** * Create a bundle from an options template * * @param {Object} options - * @param {Object} options.files - * @param {Object.<Object>} options.require - * @param {Boolean} options.require.global - * @param {Boolean} options.require.preferBuiltin - * @param {String} options.require.resolve - * @param {String} options.require.expose - * @param {Boolean|Object} options.compress - * @param {Array.<String>} options.ignore - * @param {Object=} [options.bundler] - */ constructor (options) { /** * @private * @type {Browserify} */ this.bundler = browserify({ ...bundlingOptions, ...options.bundler }); // merge with user options /** * @private * @type {Boolean} */ this.compress = options.compress; this.preferBrowserResolver = options.preferBrowserResolver; // process any list of modules externally required and also accommodate the use of built-ins if needed _.forEach(options.require, (options, resolve) => { // allow resolution override where the required module is resolved // from a different name than the one provided in options if (this.preferBrowserResolver && options.resolveBrowser) { resolve = options.resolveBrowser; } else if (options.resolve) { resolve = options.resolve; } // set the name using which the module is exported to the one marked as the module name (only in case when // one is not explicitly provided in options.) !options.expose && (options.expose = resolve); if (_.get(options, PREFER_BUILTIN) && _.has(browserifyBuiltins, resolve)) { // @todo: add tests this.bundler.require(browserifyBuiltins[resolve], options); } else { this.bundler.require(require.resolve(resolve), options); // @todo: add tests for resolve failures } }); // ignore the items mentioned in ignore list _.forEach(options.ignore, this.bundler.ignore.bind(this.bundler)); // add files that are needed _.forEach(options.files, (options, file) => { this.bundler.add(pathJoin(__dirname, file), options); }); } compile (done) { this.bundler.bundle((err, bundle) => { if (err) { return done(err); } // Scrub process object to prevent access to native backdoors const scrubProcess = ` if (typeof process !== 'undefined') { const propsToDel = ['binding', '_linkedBinding', 'dlopen', 'getBuiltinModule']; for (const key of propsToDel) { delete process[key]; } } `, // Expose only the required node built-ins to be used in vendor modules // These will be cleared out by sandbox when the bundle is loaded safeExposeBuiltIns = ` if (typeof require === 'function') { globalThis._nodeRequires = { buffer: require('buffer') }; // prevent access to node's require function require = null; } `, bundleString = scrubProcess + safeExposeBuiltIns + bundle.toString(); // bail out if compression is disabled if (!this.compress) { return done(null, bundleString); } minify(bundleString, { compress: { drop_console: true // discard calls to console.* functions }, mangle: true, // Mangle names safari10: true, // Work around the Safari 10/11 await bug (bugs.webkit.org/show_bug.cgi?id=176685) keep_fnames: /Postman.*/, // Prevent discarding or mangling of function names like "Postman*" keep_classnames: /Postman.*/, // Prevent discarding or mangling of class names like "Postman*" format: { comments: false // Omit comments in the output // @note ascii_only is disabled since postman-sandbox v4 // ascii_only: true, // Unicode characters in strings and regexps } }).then(({ code }) => { done(null, code); }).catch(done); }); } /** * Allows one to fetch a list of dependencies required by the bundle * * @param {Function} done - receives err, dependencies:Array */ listDependencies (done) { const dependencies = [], addPackageToDependencies = function (pkg) { dependencies.push(pkg.name); }; this.bundler.on('package', addPackageToDependencies); this.compile((err) => { this.bundler.removeListener('package', addPackageToDependencies); return done(err, _.uniq(dependencies).sort()); }); } static load (options) { return new Bundle(options); } } module.exports = Bundle;