diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index 60bdbf5..6817b09 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,3 +1,7 @@ +unreleased: + new features: + - GH-640 Added option to allow blocking globals in specific executions + 2.1.0: date: 2024-02-28 new features: diff --git a/README.md b/README.md index 7dfead0..f9bd68c 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ An asynchronous script will require an explicit call of a global function `__exi ```javascript myscope.set('setTimeout', global.setTimeout); // inject setTimeout -// note the 2nd parameter is set to `true` for async -myscope.exec('setTimeout(function () { __exitscope(null); }, 1000)', true, function (err) { +// note the 2nd parameter is set to `{ async: true }` +myscope.exec('setTimeout(function () { __exitscope(null); }, 1000)', { async: true }, function (err) { err ? console.error(err.stack || err) : console.log('execution complete'); }); ``` diff --git a/lib/index.js b/lib/index.js index 9b82857..9776ddc 100644 --- a/lib/index.js +++ b/lib/index.js @@ -162,17 +162,25 @@ class Uniscope { * Executes a string within a protected Uniscope of controlled set of globals. * * @param {String} code A string representing a JavaScript expression, statement, or sequence of statements - * @param {Boolean} [async=false] When set to true, callback will be called when `__exitscope` is triggered + * @param {Object} [options] An object of options for the exec + * @param {Boolean} [options.async=false] When set to true, callback will be called when `__exitscope` is triggered + * @param {String[]} [options.block] Specify a set of global variables that will not be allowed to trickle in * @param {Function} callback Callback function to be called at the end of execution */ - exec (code, async, callback) { + exec (code, options, callback) { // allow polymorphic parameter to enable async functions // and validate the primary code parameter - if (async && !callback) { - callback = async; - async = false; + if (options && !callback) { + callback = options; + options = {}; } + if (typeof options === 'boolean') { + options = { async: options }; + } + + const async = Boolean(options && options.async); + if (!util.isFunction(callback)) { throw new Error(ERROR_CALLBACK_MISSING); } if (!util.isString(code)) { return callback(new TypeError(ERROR_CODE_MUST_BE_STRING)); } @@ -233,7 +241,7 @@ class Uniscope { }); // based on Uniscope configuration, the globals that we block are specifically set to undefined - util.forEach(blocked, (key) => { + util.forEach(blocked.concat(options.block || []), (key) => { let position = globals.indexOf(key); (position === -1) && (position = globals.length); diff --git a/test/unit/scope-exec.test.js b/test/unit/scope-exec.test.js index c063874..43b5dd9 100644 --- a/test/unit/scope-exec.test.js +++ b/test/unit/scope-exec.test.js @@ -100,4 +100,37 @@ describe('scope module exec', function () { expect(d).to.equal(4); `, done); }); + + it('should allow blocking globals in specific executions', function (done) { + scope.set('myGlobal', 'my_global'); + + scope.set('blockedExecution', function () { + scope.exec(` + expect(myGlobal).to.equal(undefined); + unblockedExecution(); // call execution from inside the blocked scope + try { + Function('return myGlobal')(); + throw new Error('myGlobal is not blocked'); + } catch (e) { + expect(e).to.be.an('error'); + expect(e.message).to.equal('myGlobal is not defined'); + } + `, { block: ['myGlobal'] }, function (err) { + expect(err, 'error in blockedExecution').to.be.undefined; + }); + }); + + scope.set('unblockedExecution', function () { + scope.exec(` + expect(myGlobal).to.equal('my_global'); + `, function (err) { + expect(err, 'error in unblockedExecution').to.be.undefined; + }); + }); + + scope.exec(` + blockedExecution(); + unblockedExecution(); + `, done); + }); });