bids-validator icon indicating copy to clipboard operation
bids-validator copied to clipboard

interface to execute selectors/checks directly as javascript

Open rwblair opened this issue 2 years ago • 5 comments

The generated context will need to be in the namespace of the executed code. Not sure what kind of jailing features browser/deno api have.

rwblair avatar Apr 20 '22 20:04 rwblair

http://blog.namangoel.com/replacing-eval-with-a-web-worker https://stackoverflow.com/questions/69276975/deno-classic-workers-are-not-supported

rwblair avatar Apr 22 '22 15:04 rwblair

Proof of concept:

export function evalCheck(toExec: string, context: any): void{
  let code = `
    self.onmessage = (e) => {
     Object.keys(e.data).map((key) => {
       globalThis[key] = e.data[key]
     }); 
     try {
        let result = ${toExec}
        self.postMessage(result)
      } catch (error) {
        console.log(error) 
        console.log('inner error')
      } 
      self.close();
    };
  `
  const blob = new Blob([code], { type: 'text/javascript' });
  let blobURL = URL.createObjectURL(blob);
  var worker = new Worker(blobURL, {type: "module", deno: true});
  
  worker.addEventListener('message', function(e) {
    console.log(e.data)
  })
  
  worker.addEventListener('error', function(e) {
    console.log("error")
  })
  
  worker.postMessage(context)
}

evalCheck('x + 1', {x: 1})
evalCheck('bad; format --', {x: 3})

When testing with deno had to run with --unstable.

  • updated to expose context values as variables in worker through globalThis. this was undefined in the worker function.

rwblair avatar Apr 22 '22 17:04 rwblair

I think the issue with this is going to be passing the context into the worker will be fairly expensive for how many evaluations this will perform across a large dataset.

Maybe this will do:

const evalConstructor = (src: string): Function =>
  new Function('context', `with (context) { return ${src} }`)
const safeHas = () => true
const safeGet = (target: any, prop: any) =>
  prop === Symbol.unscopables ? undefined : target[prop]

function evalCheck(src: string, context: Record<string, any>) {
  const test = evalConstructor(src)
  const safeContext = new Proxy(context, { has: safeHas, get: safeGet })
  try {
    return test(safeContext)
  } catch (error) {
    console.error(error)
  }
}

Example:

const context = {
  test: 'value',
}

console.log(evalCheck('1 + 1', context))
> 2
console.log(evalCheck('bad; format --', context))
> undefined
console.log(evalCheck('fetch', context))
> undefined
console.log(evalCheck('test === "value"', context))
> true

nellh avatar Apr 22 '22 18:04 nellh

Wow way better. That proxy/with combination is something else.

rwblair avatar Apr 22 '22 18:04 rwblair

Wow way better. That proxy/with combination is something else.

I think the main problem is making sure the proxy behaves with a deeper object but shouldn't be too hard to generalize it.

nellh avatar Apr 22 '22 19:04 nellh