2
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

Interesting question,

first things first, it does matter where you run the code. In web browsers, this will always be Window, so eval.call( {} , 'console.log(this)' ) will log Window. link

And, of course, any code reviewer worth his salt will tell you that eval is dangerous, can you update your question and tell us why you need eval ?

In general, I would point you to this for Node for possibly safer ways of accomplishing what you need. For web browsers I would look into running JS in it's own frame ?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.