isolated-vm icon indicating copy to clipboard operation
isolated-vm copied to clipboard

convenient way of wrapping an async function to use within an isolate?

Open suspicious-pineapple opened this issue 11 months ago • 2 comments

Hello, so far i have been successfully using synchronous functions such as this:

const isolate = new ivm.Isolate({ memoryLimit: 1024 });
    const context = isolate.createContextSync();
    const jail = context.global;
    jail.setSync('global', jail.derefInto());

jail.setSync('rainbowGradient', function(x) {
        //return a color value for a given x value
        //return an array of r,g,b values
        //x is a number between 0 and 1
        let r = Math.floor(Math.sin(0.3 * x + 0) * 127 + 128);
        let g = Math.floor(Math.sin(0.3 * x + 2) * 127 + 128);
        let b = Math.floor(Math.sin(0.3 * x + 4) * 127 + 128);
        return [r,g,b];
    }  
)

which has been working well. But now i am trying to use an async function.. which appears to be considerably more difficult. I think applySyncPromise is what i need to use? i however have no idea how to make use of that.

Is there a way to do this, ideally without several pages of code for a seemingly simple task? I would be very thankful for any advice; i have been trying to do this for quite a while. I am aware that there are numerous other issues regarding this topic; i have however not been able to find a single conclusive answer.

suspicious-pineapple avatar Jan 31 '25 17:01 suspicious-pineapple

seemingly simple task

nothing about this is simple

About applySyncPromise this function is used to implement synchronous APIs in the context of asynchronous APIs. For example you would use this to mock something similar to fs.readFileSync but the on the nodejs side it would actually invoke fs.readFile in order to avoid blocking the main loop.

Anyway, here is a plain example of something you might use for async to async invocations. Depending on how many, and what kind of, parameters you want your method to take it would change. Note that resolving a promise is, in effect, a new stack with a new timeout. So if you want to timeout the user's script after a period of time and also support promises then you have to do some bookkeeping for that.

const ivm = require('isolated-vm');
const timer = require('node:timers/promises');

const isolate = new ivm.Isolate();
const context = isolate.createContextSync();

// This is the setup code
const request = (callback, payload) => {
  (async function() {
    console.log("received", payload);
    await timer.setTimeout(500);
    console.log("returning");
    return payload;
  }()).then(
    value => callback.apply(null, [ null, new ivm.ExternalCopy(value).copyInto() ], { timeout: 1000 }),
    error => callback.apply(null, [ new ivm.ExternalCopy(error).copyInto() ], { timeout: 1000 }),
  );
};

context.evalClosureSync(
  `globalThis.request = payload => new Promise((resolve, reject) => {
    const callback = (error, result) => error ? reject(error) : resolve(result);
    $0.apply(null, [ new $1.Reference(callback), new $1.ExternalCopy(payload).copyInto() ]);
  });`,
  [ new ivm.Reference(request), ivm ]);

// This is the client code
const result = context.eval(`request({ note: 'hello' })`, { promise: true, copy: true });
result.then(console.log, console.error);
received { note: 'hello' }
returning
{ note: 'hello' }

laverdet avatar Jan 31 '25 19:01 laverdet

Thanks!

suspicious-pineapple avatar Jan 31 '25 22:01 suspicious-pineapple