-
Notifications
You must be signed in to change notification settings - Fork 355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IPC between sandbox and parent process (Node.js) #269
Comments
I've built this for a game I'm working on. As long as you only need to call out from the interpreter to normal JS, trick is to define functions in the global interpreter scope with Calling back into the interpreter is more difficult, as the only tool we have available is If anyone here knows how to manually manipulate the call stack to add arbitrary function calls, this is a good place to have the native -> pseudo invocation discussion. |
Figured out how to manually append to the stack for pseudo function execution from native code - here's a code snippet that assumes let resolveFunc: InterpreterFunction | undefined;
let rejectFunc: InterpreterFunction | undefined;
const bindingVar = `__${Math.random()}__`.replaceAll(/[\.\-]/g, "");
function binder (
resolve: InterpreterFunction,
reject: InterpreterFunction
) {
resolveFunc = resolve;
rejectFunc = reject;
};
const currentScope = interpreter.stateStack.at(-1)?.scope;
if (!currentScope) throw new Error("Current scope not defined.");
const promiseIdentifier = interpreter.newNode() as Acorn.Identifier;
promiseIdentifier.type = "Identifier";
promiseIdentifier.start = 0;
promiseIdentifier.end = 0;
promiseIdentifier.name = "Promise";
const binderIdentifier = interpreter.newNode() as Acorn.Identifier;
binderIdentifier.type = "Identifier";
binderIdentifier.start = 0;
binderIdentifier.end = 0;
binderIdentifier.name = bindingVar;
const newPromiseExpression = interpreter.newNode() as Acorn.NewExpression;
newPromiseExpression.type = "NewExpression";
newPromiseExpression.start = 0;
newPromiseExpression.end = 0;
newPromiseExpression.callee = promiseIdentifier
newPromiseExpression.arguments = [binderIdentifier];
const newPromiseExScope = interpreter.createScope(newPromiseExpression, interpreter.getGlobalScope());
interpreter.setProperty(newPromiseExScope.object, bindingVar, interpreter.createNativeFunction(binder));
const newPromiseExState = new Interpreter.State(newPromiseExpression, newPromiseExScope);
// Pop the current native function execution state from the stack.
interpreter.stateStack.pop();
// Push the new execution state we prepared onto the stack.
interpreter.stateStack.push(newPromiseExState);
// Build resolve and reject functions that can be called from native code.
const resolve = (value: InterpreterPseudoValue | void) => {
if (!resolveFunc) return;
const expressionNode = interpreter.newNode() as Acorn.CallExpression;
expressionNode.type = "CallExpression";
const task = new Interpreter.Task(
resolveFunc,
value === undefined ? [] : [value],
currentScope,
expressionNode,
-1
);
interpreter.scheduleTask_(task, 0);
};
const reject = (value: InterpreterPseudoValue | void) => {
if (!rejectFunc) return;
const expressionNode = interpreter.newNode() as Acorn.CallExpression;
expressionNode.type = "CallExpression";
const task = new Interpreter.Task(
rejectFunc,
value === undefined ? [] : [value],
currentScope,
expressionNode,
-1
);
interpreter.scheduleTask_(task, 0);
}; This works well as long as you don't need the native code to get a return value from the executed pseudo code. |
There is the .pseudoToNative, which wil provide a "native" way to access said objects/arrays. // VM Code
var log = console.log;
var fQueue = [];
var fQueueInt = setInterval(function () {
var task = fQueue.pop();
var taskName = task.name,
taskArgs = task.args;
if (global[taskName]) global[taskName](taskArgs);
}, 100); runtime.setProperty(runtime.global, "console", runtime.nativeToPseudo(console));
// Outside Code
let queueTask = function(name, ...args) {
let fQueue = runtime.pseudoToNative(runtime.getProperty(runtime.global, "fQueue"));
fQueue.unshift({ name, args});
runtime.setProperty(runtime.global, "fQueue", runtime.nativeToPseudo(fQueue));
};
queueTask("log", "hello people"); This was quickly written code, i know either .apply, or .call can spread out an array to the arguments of a function. |
I'm attempting to implement a sort of IPC system (a bridge to put it bluntly) between the sandboxed code running within the interpreter and the process that is running the interpreter which is a Node.js process.
The reason behind me wanting to do this is because we are implementing a sort system where users ca create custom actions on our site. And when doing these actions they can create things such as tickets and whatnot. We have chosen to go with a more advanced approach using Blockly that will then generate into JavaScript code. For the custom blocks that do these special 'actions' we decided we are going to define a global object with some native functions on it that will be used.
But here's the issue, we need a way to communicate between the sandbox running the generated code and our parent process that is running the sandbox in node. I won't get into the fine details of how the communication works, but just the fact that I need it to happen.
I read through the documentation and previous Issues here on GitHub, but unfortunately didn't find anything helpful regarding what I'm trying to achieve. If this isn't possible with this package, please let me know. As well if you are aware of any other package that can achieve what I am looking for.
Thanks!
The text was updated successfully, but these errors were encountered: