114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
import {
|
|
JSExecutorGlobal,
|
|
JSExecutor,
|
|
JSExecutorResult,
|
|
} from "./JSExecutionManagerSingleton";
|
|
import JSONFn from "json-fn";
|
|
import log from "loglevel";
|
|
declare let Realm: any;
|
|
|
|
export default class RealmExecutor implements JSExecutor {
|
|
rootRealm: any;
|
|
createSafeObject: any;
|
|
extrinsics: any[] = [];
|
|
createSafeFunction: (unsafeFn: Function) => Function;
|
|
|
|
libraries: Record<string, any> = {};
|
|
constructor() {
|
|
this.rootRealm = Realm.makeRootRealm();
|
|
this.registerLibrary("JSONFn", JSONFn);
|
|
this.createSafeFunction = this.rootRealm.evaluate(`
|
|
(function createSafeFunction(unsafeFn) {
|
|
return function safeFn(...args) {
|
|
return unsafeFn(...args);
|
|
}
|
|
})
|
|
`);
|
|
// After parsing the data we add a triggers list on the global scope to
|
|
// push to it during any script execution
|
|
// We replace all action descriptor functions with our pusher function
|
|
// which has reference to the triggers via binding
|
|
this.createSafeObject = this.rootRealm.evaluate(
|
|
`
|
|
(function createSafeObject(unsafeObject) {
|
|
const safeObject = JSONFn.parse(JSONFn.stringify(unsafeObject));
|
|
if(safeObject.actionPaths) {
|
|
safeObject.triggers = [];
|
|
const pusher = function (action, ...payload) {
|
|
const actionPayload = action(...payload);
|
|
this.triggers.push(actionPayload);
|
|
}
|
|
safeObject.actionPaths.forEach(path => {
|
|
const action = _.get(safeObject, path);
|
|
const entity = _.get(safeObject, path.split(".")[0])
|
|
if(action) {
|
|
_.set(safeObject, path, pusher.bind(safeObject, action.bind(entity)))
|
|
}
|
|
})
|
|
}
|
|
return safeObject
|
|
})
|
|
`,
|
|
);
|
|
}
|
|
|
|
registerLibrary(accessor: string, lib: any) {
|
|
this.rootRealm.global[accessor] = lib;
|
|
}
|
|
unRegisterLibrary(accessor: string) {
|
|
this.rootRealm.global[accessor] = null;
|
|
}
|
|
private convertToMainScope(result: any) {
|
|
if (typeof result === "object") {
|
|
if (Array.isArray(result)) {
|
|
return Object.assign([], result);
|
|
}
|
|
return Object.assign({}, result);
|
|
}
|
|
return result;
|
|
}
|
|
execute(
|
|
sourceText: string,
|
|
data: JSExecutorGlobal,
|
|
callbackData?: any,
|
|
): JSExecutorResult {
|
|
const safeCallbackData = this.createSafeObject(callbackData || {});
|
|
const safeData = this.createSafeObject(data);
|
|
try {
|
|
// We create a closed function and evaluate that
|
|
// This is to send any triggers received during evaluations
|
|
// triggers should already be defined in the safeData
|
|
const scriptToEvaluate = `
|
|
function closedFunction () {
|
|
const result = ${sourceText};
|
|
return { result, triggers }
|
|
}
|
|
closedFunction()
|
|
`;
|
|
|
|
const scriptWithCallback = `
|
|
function callback (script) {
|
|
const userFunction = script;
|
|
const result = userFunction(CALLBACK_DATA);
|
|
return { result, triggers };
|
|
}
|
|
callback(${sourceText});
|
|
`;
|
|
const script = callbackData ? scriptWithCallback : scriptToEvaluate;
|
|
const data = callbackData
|
|
? { ...safeData, CALLBACK_DATA: safeCallbackData }
|
|
: safeData;
|
|
|
|
const { result, triggers } = this.rootRealm.evaluate(script, data);
|
|
return {
|
|
result: this.convertToMainScope(result),
|
|
triggers,
|
|
};
|
|
} catch (e) {
|
|
log.debug(`Error: "${e.message}" when evaluating {{${sourceText}}}`);
|
|
log.debug(e);
|
|
return { result: undefined, triggers: [] };
|
|
}
|
|
}
|
|
}
|