Issues (23)

1
import { NodeVM, VM } from 'vm2';
2
import Promise, { reject, resolve } from 'bluebird';
3
import {
4
    curry,
5
    propOr,
6
    tryCatch,
7
    type
8
} from 'ramda';
9
import { compileHTML } from './compiler';
10
import { upsert } from './db';
11
import { translator } from './i18n';
12
import { evaluateModules } from './module';
13
14
/**
15
 * Safely evaluates a piece of JS code as if it were running in the browser.
16
 *
17
 * @param {Object} sandbox
18
 * @param {String} code
19
 * @return {*}
20
 */
21
export const runInBrowser = curry((sandbox, code) =>
22
    new VM({ sandbox }).run(code));
23
24
/**
25
 * Returns an instance of the Rung virtual machine
26
 *
27
 * @author Marcelo Haskell Camargo
28
 * @param {Object} translator - Map of strings to translate
0 ignored issues
show
The parameter translator does not exist. Did you maybe forget to remove this comment?
Loading history...
29
 * @return {NodeVM}
30
 */
31
export function createVM(strings = []) {
32
    const vm = new NodeVM({
33
        require: {
34
            external: true
35
        }
36
    });
37
38
    vm.freeze(compileHTML, '__render__');
39
    vm.freeze(translator(strings), '_');
40
41
    return vm;
42
}
43
44
/**
45
 * Runs an app on a virtualized environment and returns its result as
46
 * native JS data
47
 *
48
 * @author Marcelo Haskell Camargo
49
 * @param {String} name - The unique identifier to track the app
50
 * @param {String} source - ES6 source to run
51
 * @param {Object} strings - Object containing the strings to translate
52
 * @param {String[][]} modules - Map of modules with [filename, source]
53
 * @return {Promise}
54
 */
55
export function runInSandbox(name, source, strings = {}, modules = []) {
56
    const evaluate = tryCatch(() => {
57
        const vm = createVM(strings);
58
59
        // Pre-evaluate and inject in vm all necessary modules
60
        vm.options.require.mock = evaluateModules(vm, modules);
61
62
        // Run with all modules! :)
63
        const result = vm.run(source, `${name}.js`);
64
        return resolve(propOr(result, 'default', result));
65
    }, reject);
66
67
    return evaluate();
68
}
69
70
/**
71
 * Tries to get the parameter types by running the script to get config.params
72
 *
73
 * @author Marcelo Haskell Camargo
74
 * @param {Object} extension - Must contain name and source
75
 * @param {Object} strings - Object with strings to translate
76
 * @param {String[][]} modules - Object with modules name and source
77
 * @return {Promise}
78
 */
79
export function getProperties(extension, strings, modules) {
80
    return runInSandbox(extension.name, extension.source, strings, modules)
81
        .then(propOr({}, 'config'));
82
}
83
84
/**
85
 * Runs an app with a context (with parameters) and gets the cards.
86
 * The result may be a string, a nullable value, an array...
87
 *
88
 * @author Marcelo Haskell Camargo
89
 * @param {Object} extension - Object containing name and source
90
 * @param {Object} context - Context to pass to the main function
91
 * @param {Object} strings - Object with strings to translate
92
 * @param {String[][]} modules - Modules with [filename, source]
93
 * @return {Promise}
94
 */
95
export function runAndGetAlerts(extension, context, strings, modules) {
96
    return runInSandbox(extension.name, extension.source, strings, modules)
97
        .then(app => {
98
            const runExtension = ~new Promise((resolve, reject) => {
99
                if (type(app.extension) !== 'Function') {
100
                    return reject(new TypeError('Expected default exported expression to be a function'));
101
                }
102
103
                // Async vs sync extension
104
                return app.extension.length > 1
105
                    ? app.extension.call(null, context, resolve)
106
                    : resolve(app.extension.call(null, context));
107
            });
108
109
            return runExtension();
110
        })
111
        .tap(result => upsert(extension.name, result.db));
112
}
113