PromucFlow_constructor/app/client/src/jsExecution/RealmExecutor.ts

114 lines
3.5 KiB
TypeScript
Raw Normal View History

2020-02-18 10:41:52 +00:00
import {
JSExecutorGlobal,
JSExecutor,
JSExecutorResult,
} from "./JSExecutionManagerSingleton";
import JSONFn from "json-fn";
import log from "loglevel";
2019-11-28 03:56:44 +00:00
declare let Realm: any;
export default class RealmExecutor implements JSExecutor {
rootRealm: any;
createSafeObject: any;
2019-11-28 03:56:44 +00:00
extrinsics: any[] = [];
createSafeFunction: (unsafeFn: Function) => Function;
libraries: Record<string, any> = {};
constructor() {
this.rootRealm = Realm.makeRootRealm();
2020-02-18 10:41:52 +00:00
this.registerLibrary("JSONFn", JSONFn);
2019-11-28 03:56:44 +00:00
this.createSafeFunction = this.rootRealm.evaluate(`
2020-01-17 09:28:26 +00:00
(function createSafeFunction(unsafeFn) {
2019-11-28 03:56:44 +00:00
return function safeFn(...args) {
2020-02-18 10:41:52 +00:00
return unsafeFn(...args);
2019-11-28 03:56:44 +00:00
}
})
`);
2020-02-18 10:41:52 +00:00
// 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)))
}
2020-02-18 10:41:52 +00:00
})
}
return safeObject
2019-11-28 03:56:44 +00:00
})
2020-02-18 10:41:52 +00:00
`,
);
2019-11-28 03:56:44 +00:00
}
2020-02-18 10:41:52 +00:00
2019-11-28 03:56:44 +00:00
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;
}
2020-02-18 10:41:52 +00:00
execute(
sourceText: string,
data: JSExecutorGlobal,
callbackData?: any,
): JSExecutorResult {
const safeCallbackData = this.createSafeObject(callbackData || {});
const safeData = this.createSafeObject(data);
2019-11-28 03:56:44 +00:00
try {
2020-02-18 10:41:52 +00:00
// 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,
};
2019-11-28 03:56:44 +00:00
} catch (e) {
log.debug(`Error: "${e.message}" when evaluating {{${sourceText}}}`);
log.debug(e);
2020-02-18 10:41:52 +00:00
return { result: undefined, triggers: [] };
2019-11-28 03:56:44 +00:00
}
}
}