I tried to build a solution to dynamically load JS code, but to only expose predefined functions. Is this the correct approach? Is there any Method to execute arbitrary code if I put all my code inside of (function($$) { ... }).call({}, $$)
statements?
// scope.js
// $$ holds all global variables, and is not accessible
// inside an plugin (unless there is a helper method in the API, which exposes the variable)
$$ = {};
// (function($$) { ... }).call({}, $$) is used to set this to an empty object.
// This prevents getting values from this, which should be in another scope
// "use strict"; is used to prevent the creation of global variables
// initialize the global variables
(function($$) {
"use strict";
// read-only property
$$.DEBUG = {
get: function() {
return true;
}
};
Object.defineProperty($$, 'DEBUG', $$.DEBUG);
// execute the plugin code
$$.exec = function (code) {
return (function($$) {
/**/
// get the exposed API Methods
var API = new $$.API(),
$$ = {}; // Hide the global $$ object
// prepare code for execution
code = code.trim();
if (code[code.length - 1] !== ';') {
code += ';';
}
var _code = "(function() { var document = {}, window = {}; " + code + " return this; }).call({})";
// call with eval
try {
// everything set with this will be returned after the eval
return eval(_code);
} catch(e) {
console.error(e);
}
/**/
}).call({}, $$);
};
}).call({}, $$);
// $$.API() returns an object, which holds all exposed API methods
// because $$.API() is an class, every plugin gets an local API oject.
// If the elements of this object are overwritten, it does not interfere with other API objects
(function($$) {
$$.API = function() {
// Default API values
this.ApiVar = 3;
// Expose global variables to the API
this.setGlobalVar = function(value) {
$$.globalVar = value;
};
this.getGlobalVar = function() {
return $$.globalVar;
};
return this;
};
}).call({}, $$);
// setting a global variable
"use strict";
$$.globalVar = 15;
Usage ($$.globalVar
inside the exec
should return undefined
):
// load the scopes
require('./scope.js');
"use strict";
(function($$) {
/**/
// restricted execution of code (only API is accessible)
var ret = $$.exec("this.test = 'test' + ' API.ApiVar: ' + API.ApiVar + '\\n' + '$$.globalVar: ' + $$.globalVar + '\\n' + 'API.getGlobalVar(): ' + API.getGlobalVar(); API.setGlobalVar('test'); API.ApiVar = 16;");
console.log(ret.test);
console.log();
// API Values are only changed locally
console.log('API.ApiVar: ' + $$.API().ApiVar);
// normal usage of $$ variables
console.log('$$.globalVar: ' + $$.globalVar);
/**/
}).call({}, $$);
I used Node.js for testing but it shouldn't matter where it runs.
We are developing a desktop application, which uses HTML and JS as frontend. So it's almost a browser.
The wrapping with (function($$) { ... }).call({}, $$)
is to get our code in its own local scope. that way, we can not expose function, which shouldn't be outside of the file they were used. ($$ is used to expose functions for our code)
The functions which are usable for 3rd party developers, are exposed through API(). So every "Plugin" (if you like to call it that) has its own local copy of API functions and cannot mess with global objects.