lively.vm
Version:
Controlled JavaScript code execution and instrumentation.
250 lines (206 loc) • 7.16 kB
JavaScript
/*global System,location*/
import { obj } from "lively.lang";
class EvalStrategy {
async runEval(source, options) {
return Promise.reject(`runEval(source, options) not yet implemented for ${this.constructor.name}`);
}
async keysOfObject(prefix, options) {
return Promise.reject(`keysOfObject(prefix, options) not yet implemented for ${this.constructor.name}`);
}
}
class SimpleEvalStrategy extends EvalStrategy {
async runEval(source, options) {
return Promise.resolve().then(() => {
try {
return Promise.resolve({value: eval(source)});
} catch (err) {
return {isError: true, value: err}
}
});
}
async keysOfObject(prefix, options) {
// for dynamic object completions
var result = await lively.vm.completions.getCompletions(
code => this.runEval(code, options), prefix);
return {completions: result.completions, prefix: result.startLetters};
}
}
class LivelyVmEvalStrategy extends EvalStrategy {
normalizeOptions(options) {
if (!options.targetModule)
throw new Error("runEval called but options.targetModule not specified!");
return Object.assign({
sourceURL: options.targetModule + "_doit_" + Date.now(),
}, options);
}
async runEval(source, options) {
options = this.normalizeOptions(options)
var System = options.System || lively.modules.System;
System.config({meta: {[options.targetModule]: {format: "esm"}}});
return lively.vm.runEval(source, options);
}
async keysOfObject(prefix, options) {
// for dynamic object completions
var result = await lively.vm.completions.getCompletions(
code => lively.vm.runEval(code, options), prefix);
return {completions: result.completions, prefix: result.startLetters}
}
}
export class RemoteEvalStrategy extends LivelyVmEvalStrategy {
sourceForRemote(action, arg, options) {
const contextFetch = obj.isString(options.context) ? options.context : false;
options = obj.dissoc(options, ["systemInterface", "System", "context"]);
return `
(function() {
var arg = ${JSON.stringify(arg)},
options = ${JSON.stringify(options)};
if (typeof lively === "undefined" || !lively.vm) {
return Promise.resolve({
isEvalResult: true,
isError: true,
value: 'lively.vm not available!'
});
}
var hasSystem = typeof System !== "undefined"
options.context = ${contextFetch}
if (!options.context) {
options.context = hasSystem
? System.global
: typeof window !== "undefined"
? window
: typeof global !== "undefined"
? global
: typeof self !== "undefined" ? self : this;
}
async function evalFunction(source, options) {
if (hasSystem) {
var conf = {meta: {}}; conf.meta[options.targetModule] = {format: "esm"};
System.config(conf);
} else {
options = Object.assign({topLevelVarRecorderName: "GLOBAL"}, options);
delete options.targetModule;
}
let res = await lively.vm.runEval(source, options);
try {
JSON.stringify(res.value);
} catch(e) {
res.value = String(res.value);
}
return res;
}
function keysOfObjectFunction(prefix, options) {
return lively.vm.completions.getCompletions(code => evalFunction(code, options), prefix)
.then(result => ({isEvalResult: true, completions: result.completions, prefix: result.startLetters}));
}
return ${action === "eval" ? "evalFunction" : "keysOfObjectFunction"}(arg, options)
.catch(err => ({isEvalResult: true, isError: true, value: String(err.stack || err)}));
})();
`;
}
async runEval(source, options) {
return this.remoteEval(this.sourceForRemote("eval", source, options), options)
}
async keysOfObject(prefix, options) {
return this.remoteEval(this.sourceForRemote("keysOfObject", prefix, options), options)
}
async remoteEval(source, options) {
try {
var result = await this.basicRemoteEval(source, options)
return typeof result === "string" ? JSON.parse(result) : result;
} catch (e) {
return {isError: true, value: `Remote eval failed: ${result || e}`};
}
}
async basicRemoteEval(source, options) {
throw new Error("Not yet implemented");
}
}
class HttpEvalStrategy extends RemoteEvalStrategy {
static get defaultURL() { return "http://localhost:3000/lively" }
constructor(url) {
super();
this.url = url || this.constructor.defaultURL;
}
normalizeOptions(options) {
options = super.normalizeOptions(options);
return Object.assign(
{serverEvalURL: this.url},
options,
{context: null});
}
async basicRemoteEval(source, options) {
options = this.normalizeOptions(options);
var method = "basicRemoteEval_" + (System.get("@system-env").node ? "node" : "web");
return await this[method]({method: "POST", body: source}, options.serverEvalURL);
}
async basicRemoteEval_web(payload, url) {
let [domain] = url.match(/[^:]+:\/\/[^\/]+/) || [url],
loc, crossDomain;
// fixme: this should be replaced by accessing the location
// in a canonical way
if (System.get("@system-env").worker)
loc = window.location;
else {
loc = document.location;
}
crossDomain = loc.origin !== domain;
if (crossDomain) { // use lively.server proxy plugin
payload.headers = {
...payload.headers,
'pragma': 'no-cache',
'cache-control': 'no-cache',
"x-lively-proxy-request": url
}
url = loc.origin;
}
var res;
try {
res = await window.fetch(url, payload);
} catch (e) {
throw new Error(`Cannot reach server at ${url}: ${e.message}`)
}
if (!res.ok) {
throw new Error(`Server at ${url}: ${res.statusText}`)
}
return res.text();
}
async basicRemoteEval_node(payload, url) {
var urlParse = System._nodeRequire("url").parse,
http = System._nodeRequire(url.startsWith("https:") ? "https" : "http"),
opts = Object.assign({method: payload.method || "GET"}, urlParse(url));
return new Promise((resolve, reject) => {
var request = http.request(opts, res => {
res.setEncoding('utf8');
var data = "";
res.on('data', (chunk) => data += chunk);
res.on('end', () => resolve(data));
res.on('error', err => reject(err));
})
request.on('error', err => reject(err));
request.end(payload.body)
});
}
}
class L2LEvalStrategy extends RemoteEvalStrategy {
constructor(l2lClient, targetId) {
super();
this.l2lClient = l2lClient;
this.targetId = targetId;
}
async basicRemoteEval(source, options) {
let {l2lClient, targetId} = this,
{data: evalResult} = await l2lClient.sendToAndWait(targetId, "remote-eval", {source}, {
ackTimeout: options.ackTimeout || 3500
});
if (evalResult && evalResult.value && evalResult.value.isEvalResult)
evalResult = evalResult.value;
return evalResult;
}
}
export {
EvalStrategy,
SimpleEvalStrategy,
LivelyVmEvalStrategy,
HttpEvalStrategy,
L2LEvalStrategy
}