diff --git a/.gitignore b/.gitignore index 2622890..a2fa3a4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /tmp node_modules oclif.manifest.json +.npmrc diff --git a/package.json b/package.json index 7f82096..f656625 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ } }, "scripts": { - "build": "shx rm -rf dist && tsc -b", + "build": "shx rm -rf dist && tsc -b && shx cp src/lib/phatjs/phatjs_bg.*.wasm dist/lib/phatjs/", "lint": "tsc --noEmit", "test": "mocha --forbid-only \"test/**/*.test.ts\"" }, diff --git a/src/commands/run.ts b/src/commands/run.ts index ad1e364..6648753 100644 --- a/src/commands/run.ts +++ b/src/commands/run.ts @@ -27,7 +27,7 @@ export default class Run extends Command { }), } - public async run(): Promise<{ output: string }> { + public async run() { const { flags: { scriptArgs = [], experimentalAsync }, args: { script }, @@ -39,8 +39,6 @@ export default class Run extends Command { isAsync: experimentalAsync, }) this.log(JSON.stringify({ output })) - return { - output, - } + process.exit(0) } } diff --git a/src/lib/phatjs/phatjs.d.ts b/src/lib/phatjs/phatjs.d.ts new file mode 100644 index 0000000..9b77ff0 --- /dev/null +++ b/src/lib/phatjs/phatjs.d.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +*/ +export function start(): void; +/** +* Get the version of the runtime. +* @returns {Promise} +*/ +export function version(): Promise; +/** +* Run a script. +* +* # Arguments +* - `args` - a list of arguments to pass to the runtime, including the script name and arguments. +* +* # Example +* +* ```js +* const result = await run(["phatjs", "-c", "console.log(scriptArgs)", "--", "Hello, world!"]); +* console.log(result); +* ``` +* @param {(string)[]} args +* @returns {Promise} +*/ +export function run(args: (string)[]): Promise; +/** +* Set a hook for the runtime. +* +* # Available hooks +* - `fetch` - a function that takes a `Request` object and returns a `Response` object. +* @param {string} hook_name +* @param {any} hook_value +*/ +export function setHook(hook_name: string, hook_value: any): void; +export function init(): void; diff --git a/src/lib/phatjs/phatjs.js b/src/lib/phatjs/phatjs.js new file mode 100644 index 0000000..1164c8b --- /dev/null +++ b/src/lib/phatjs/phatjs.js @@ -0,0 +1,647 @@ +let imports = {}; +imports['__wbindgen_placeholder__'] = module.exports; +let wasm; +const { TextDecoder, TextEncoder } = require(`util`); + +const heap = new Array(128).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = null; + +function getUint8Memory0() { + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +let cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedInt32Memory0 = null; + +function getInt32Memory0() { + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_2.get(state.dtor)(a, state.b); + + } else { + state.a = a; + } + } + }; + real.original = state; + + return real; +} +function __wbg_adapter_24(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures__invoke1_mut__hdb3d18d70eb2a205(arg0, arg1, addHeapObject(arg2)); +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store_command_export(addHeapObject(e)); + } +} +function __wbg_adapter_43(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures__invoke2_mut__h129b1a7fb2e72e4c(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); +} + +function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; } +/** +*/ +module.exports.start = function() { + wasm.start(); +}; + +/** +* Get the version of the runtime. +* @returns {Promise} +*/ +module.exports.version = function() { + const ret = wasm.version(); + return takeObject(ret); +}; + +let cachedUint32Memory0 = null; + +function getUint32Memory0() { + if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) { + cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32Memory0; +} + +function passArrayJsValueToWasm0(array, malloc) { + const ptr = malloc(array.length * 4, 4) >>> 0; + const mem = getUint32Memory0(); + for (let i = 0; i < array.length; i++) { + mem[ptr / 4 + i] = addHeapObject(array[i]); + } + WASM_VECTOR_LEN = array.length; + return ptr; +} +/** +* Run a script. +* +* # Arguments +* - `args` - a list of arguments to pass to the runtime, including the script name and arguments. +* +* # Example +* +* ```js +* const result = await run(["phatjs", "-c", "console.log(scriptArgs)", "--", "Hello, world!"]); +* console.log(result); +* ``` +* @param {(string)[]} args +* @returns {Promise} +*/ +module.exports.run = function(args) { + const ptr0 = passArrayJsValueToWasm0(args, wasm.__wbindgen_malloc_command_export); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.run(ptr0, len0); + return takeObject(ret); +}; + +/** +* Set a hook for the runtime. +* +* # Available hooks +* - `fetch` - a function that takes a `Request` object and returns a `Response` object. +* @param {string} hook_name +* @param {any} hook_value +*/ +module.exports.setHook = function(hook_name, hook_value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0(hook_name, wasm.__wbindgen_malloc_command_export, wasm.__wbindgen_realloc_command_export); + const len0 = WASM_VECTOR_LEN; + wasm.setHook(retptr, ptr0, len0, addHeapObject(hook_value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +}; + +module.exports.__wbg_self_05040bd9523805b9 = function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_window_adc720039f2cb14f = function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); +}; + +module.exports.__wbg_globalThis_622105db80c1457d = function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_global_f56b013ed9bcf359 = function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; +}; + +module.exports.__wbg_newnoargs_cfecb3965268594c = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); +}; + +module.exports.__wbg_call_3f093dd26d5569f8 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_new_70828a4353259d4b = function(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_43(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return addHeapObject(ret); + } finally { + state0.a = state0.b = 0; + } +}; + +module.exports.__wbindgen_object_clone_ref = function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); +}; + +module.exports.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); +}; + +module.exports.__wbg_set_961700853a212a39 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + return ret; +}, arguments) }; + +module.exports.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc_command_export, wasm.__wbindgen_realloc_command_export); + var len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; +}; + +module.exports.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); +}; + +module.exports.__wbg_buffer_b914fb8b50ebbc3e = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); +}; + +module.exports.__wbg_newwithbyteoffsetandlength_0de9ee56e9f6ee6e = function(arg0, arg1, arg2) { + const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); +}; + +module.exports.__wbg_new_b1f2d6842d615181 = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); +}; + +module.exports.__wbg_call_67f2111acd2dfdb6 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_abort_510372063dd66b29 = function(arg0) { + getObject(arg0).abort(); +}; + +module.exports.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + return ret; +}; + +module.exports.__wbg_setTimeout_cb9faa432fbf8d02 = function() { return handleError(function (arg0, arg1) { + const ret = setTimeout(getObject(arg0), arg1); + return ret; +}, arguments) }; + +module.exports.__wbg_error_b834525fe62708f5 = function(arg0) { + console.error(getObject(arg0)); +}; + +module.exports.__wbg_warn_2a68e3ab54e55f28 = function(arg0) { + console.warn(getObject(arg0)); +}; + +module.exports.__wbg_info_12174227444ccc71 = function(arg0) { + console.info(getObject(arg0)); +}; + +module.exports.__wbg_debug_7d82cf3cd21e00b0 = function(arg0) { + // console.debug(getObject(arg0)); +}; + +module.exports.__wbg_trace_0758f8b7ad9dd839 = function(arg0) { + console.trace(getObject(arg0)); +}; + +module.exports.__wbg_log_71d60d16f278bc00 = function(arg0, arg1) { + console.log(getObject(arg0), getObject(arg1)); +}; + +module.exports.__wbg_error_a2c41f67d42ff592 = function(arg0, arg1) { + console.error(getObject(arg0), getObject(arg1)); +}; + +module.exports.__wbg_new_632630b5cec17f21 = function() { + const ret = new Object(); + return addHeapObject(ret); +}; + +module.exports.__wbg_new_4db22fd5d40c5665 = function() { return handleError(function () { + const ret = new Headers(); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_new_92cc7d259297256c = function() { return handleError(function () { + const ret = new AbortController(); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_signal_8fbb4942ce477464 = function(arg0) { + const ret = getObject(arg0).signal; + return addHeapObject(ret); +}; + +module.exports.__wbg_newwithstrandinit_11fbc38beb4c26b0 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2)); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_has_ad45eb020184f624 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.has(getObject(arg0), getObject(arg1)); + return ret; +}, arguments) }; + +module.exports.__wbg_fetch_6a2624d7f767e331 = function(arg0) { + const ret = fetch(getObject(arg0)); + return addHeapObject(ret); +}; + +module.exports.__wbg_phatjsFetch_c1ae792b817bf366 = function(arg0) { + const ret = phatjsFetch(getObject(arg0)); + return addHeapObject(ret); +}; + +module.exports.__wbg_fetch_10edd7d7da150227 = function(arg0, arg1) { + const ret = getObject(arg0).fetch(getObject(arg1)); + return addHeapObject(ret); +}; + +module.exports.__wbg_append_b2e8ed692fc5eb6e = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); +}, arguments) }; + +module.exports.__wbg_instanceof_Response_b5451a06784a2404 = function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Response; + } catch (_) { + result = false; + } + const ret = result; + return ret; +}; + +module.exports.__wbg_status_bea567d1049f0b6a = function(arg0) { + const ret = getObject(arg0).status; + return ret; +}; + +module.exports.__wbg_url_e319aee56d26ddf1 = function(arg0, arg1) { + const ret = getObject(arg1).url; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc_command_export, wasm.__wbindgen_realloc_command_export); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; +}; + +module.exports.__wbg_headers_96d9457941f08a33 = function(arg0) { + const ret = getObject(arg0).headers; + return addHeapObject(ret); +}; + +module.exports.__wbg_iterator_40027cdd598da26b = function() { + const ret = Symbol.iterator; + return addHeapObject(ret); +}; + +module.exports.__wbg_get_3fddfed2c83f434c = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbindgen_is_function = function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; +}; + +module.exports.__wbindgen_is_object = function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; +}; + +module.exports.__wbg_next_586204376d2ed373 = function(arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); +}; + +module.exports.__wbg_next_b2d3366343a208b3 = function() { return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_done_90b14d6f6eacc42f = function(arg0) { + const ret = getObject(arg0).done; + return ret; +}; + +module.exports.__wbg_value_3158be908c80a75e = function(arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); +}; + +module.exports.__wbg_stringify_865daa6fb8c83d5a = function() { return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_arrayBuffer_eb2005809be09726 = function() { return handleError(function (arg0) { + const ret = getObject(arg0).arrayBuffer(); + return addHeapObject(ret); +}, arguments) }; + +module.exports.__wbg_length_21c4b0ae73cba59d = function(arg0) { + const ret = getObject(arg0).length; + return ret; +}; + +module.exports.__wbg_set_7d988c98e6ced92d = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); +}; + +module.exports.__wbg_random_1385edd75e02760c = typeof Math.random == 'function' ? Math.random : notDefined('Math.random'); + +module.exports.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc_command_export, wasm.__wbindgen_realloc_command_export); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; +}; + +module.exports.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + +module.exports.__wbg_then_20a5920e447d1cb1 = function(arg0, arg1, arg2) { + const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); +}; + +module.exports.__wbg_then_f9e58f5a50f43eae = function(arg0, arg1) { + const ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); +}; + +module.exports.__wbg_queueMicrotask_f61ee94ee663068b = function(arg0) { + queueMicrotask(getObject(arg0)); +}; + +module.exports.__wbg_queueMicrotask_f82fc5d1e8f816ae = function(arg0) { + const ret = getObject(arg0).queueMicrotask; + return addHeapObject(ret); +}; + +module.exports.__wbg_resolve_5da6faf2c96fd1d5 = function(arg0) { + const ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); +}; + +module.exports.__wbindgen_closure_wrapper1483 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 123, __wbg_adapter_24); + return addHeapObject(ret); +}; + +module.exports.init = function() { + const path = require('upath').join(__dirname, 'phatjs_bg.0.10.1.wasm'); + const bytes = require('fs').readFileSync(path); + const wasmModule = new WebAssembly.Module(bytes); + const wasmInstance = new WebAssembly.Instance(wasmModule, imports); + wasm = wasmInstance.exports; + wasm.__wbindgen_start(); +} diff --git a/src/lib/phatjs/phatjs_bg.0.10.1.wasm b/src/lib/phatjs/phatjs_bg.0.10.1.wasm new file mode 100644 index 0000000..eebf594 Binary files /dev/null and b/src/lib/phatjs/phatjs_bg.0.10.1.wasm differ diff --git a/src/lib/runQuickJs.ts b/src/lib/runQuickJs.ts index 25b5803..11f2c26 100644 --- a/src/lib/runQuickJs.ts +++ b/src/lib/runQuickJs.ts @@ -7,8 +7,7 @@ import { } from '@polkadot/util-crypto' import request, { type HttpMethod } from './sync-request' -import createSandbox from './sandbox' -import { rejectOpenPromises } from './sandbox-wrappers' +import { init, run } from '../lib/phatjs/phatjs.js' function isHexString(str: string): boolean { const regex = /^0x[0-9a-f]+$/ @@ -199,26 +198,12 @@ export async function runQuickJs( args: string[] = [], options = { silent: false, isAsync: false } ) { - const QuickJS = await getQuickJS() if (options.isAsync) { - const { vm, run, isAsyncProcessRunning } = await createSandbox( - QuickJS, - {}, - { scriptArgs: args }, - { silent: options.silent } - ) - await run(code) - while(isAsyncProcessRunning()) { - await new Promise((resolve) => setTimeout(resolve, 100)) - } - await new Promise((resolve) => setTimeout(resolve, 100)) - rejectOpenPromises(vm) - const output = vm - .getProp(vm.global, 'scriptOutput') - .consume(vm.dump) - vm.dispose() + init() + const output = run(['phatjs', '-c', code, '--', ...args]) return output } + const QuickJS = await getQuickJS() const runtime = QuickJS.newRuntime() const context = runtime.newContext() const arena = new Arena(context, { isMarshalable: true }) diff --git a/src/lib/sandbox-wrappers.ts b/src/lib/sandbox-wrappers.ts deleted file mode 100644 index 706dac5..0000000 --- a/src/lib/sandbox-wrappers.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { QuickJSDeferredPromise, QuickJSContext } from 'quickjs-emscripten' - -let promiseId = 0 -const openPromises: Record = {} - -export function wrapPromise( - vm: QuickJSContext, - promise: Promise, - beginAsyncProcess: () => void, - endAsyncProcess: () => void -): any { - promiseId += 1 - - const vmPromise = vm.newPromise() as QuickJSDeferredPromise & { _promiseId: number } - vmPromise._promiseId = promiseId - beginAsyncProcess() - promise - .then((...args) => { - endAsyncProcess() - const wrappedArgs = args.map((arg) => - wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess) - ) - openPromises[vmPromise._promiseId] = null - vmPromise.resolve(...wrappedArgs) - wrappedArgs.forEach((arg) => arg.dispose()) - }) - .catch((...args) => { - const wrappedArgs = args.map((arg) => - wrap(vm, arg, undefined, beginAsyncProcess, endAsyncProcess) - ) - vmPromise.reject(...wrappedArgs) - wrappedArgs.forEach((arg) => arg.dispose()) - openPromises[vmPromise._promiseId] = null - }) - vmPromise.settled.then(() => { - endAsyncProcess() - vm.runtime.executePendingJobs() - }) - - openPromises[promiseId] = vmPromise - - return vmPromise.handle -} - -export function rejectOpenPromises(vm: QuickJSContext) { - for (const [id, promise] of Object.entries(openPromises)) { - if (promise !== null) { - (promise as QuickJSDeferredPromise).reject(vm.newError('Shutting down')) - openPromises[id] = null - } - } - - vm.runtime.executePendingJobs() -} - -export function wrap( - vm: QuickJSContext, - value: any, - context: Record | undefined, - beginAsyncProcess: () => void, - endAsyncProcess: () => void -): any { - if (value === null) { - return null - } else if (typeof value === 'object' && typeof value.then === 'function') { - return wrapPromise(vm, value, beginAsyncProcess, endAsyncProcess) - } else if (typeof value === 'function') { - return wrapGenericFunction( - vm, - value, - context, - beginAsyncProcess, - endAsyncProcess - ) - } else if (Array.isArray(value)) { - return wrapArray(vm, value, beginAsyncProcess, endAsyncProcess) - } else if (typeof value === 'object') { - return wrapObject(vm, value, beginAsyncProcess, endAsyncProcess) - } else if (typeof value === 'string') { - return vm.newString(value) - } else if (typeof value === 'number') { - return vm.newNumber(value) - } else if (typeof value === 'bigint') { - return vm.newBigInt(value) - } else if (typeof value === 'boolean') { - return vm.unwrapResult(vm.evalCode(value ? 'true' : 'false')) - } else if (typeof value === 'undefined') { - return vm.undefined - } else { - return null - } -} - -export function wrapObject( - vm: QuickJSContext, - obj: any, - beginAsyncProcess: () => void, - endAsyncProcess: () => void -): any { - const vmObject = vm.newObject() - - for (const key in obj) { - const value = obj[key] - const wrappedValue = wrap(vm, value, obj, beginAsyncProcess, endAsyncProcess) - - if (wrappedValue !== null) { - vm.setProp(vmObject, key, wrappedValue) - if ( - wrappedValue != vm.undefined && - wrappedValue != vm.null && - wrappedValue.dispose - ) { - wrappedValue.dispose() - } - } - } - - return vmObject -} - -export function wrapArray( - vm: QuickJSContext, - arr: any[], - beginAsyncProcess: () => void, - endAsyncProcess: () => void -): any { - const vmArray = vm.newArray() - - for (let i = 0; i < arr.length; i++) { - const wrappedValue = wrap(vm, arr[i], arr, beginAsyncProcess, endAsyncProcess) - vm.setProp(vmArray, i, wrappedValue) - if (wrappedValue != vm.undefined && wrappedValue != vm.null) { - wrappedValue.dispose() - } - } - - return vmArray -} - -export function wrapGenericFunction( - vm: QuickJSContext, - fn: any, - context: any = undefined, - beginAsyncProcess: () => void, - endAsyncProcess: () => void -): any { - const vmFn = vm.newFunction(fn.name, (...args: any) => { - const unwrappedArgs: any[] = args.map(vm.dump) - try { - const result = fn.call(context, ...unwrappedArgs) - const wrappedResult = wrap( - vm, - result, - undefined, - beginAsyncProcess, - endAsyncProcess - ) - return wrappedResult - } catch (e) { - console.log('Error', e) - } - }) - return vmFn -} - -export function injectTimingFunctions( - vm: QuickJSContext, - cbAddedTimer: any, - cbRemovedTimer: any, - maxTimeout = 600000 -): void { - const timeoutFunctionHandles: any = {} - const _setTimeout = vm.newFunction( - 'setTimeout', - (vmFnHandle: any, timeoutHandle: any) => { - // Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout - const vmFnHandleCopy = vmFnHandle.dup() - let timeout = vm.dump(timeoutHandle) - - // cap timeout at max timeout - if (timeout > maxTimeout) { - timeout = maxTimeout - } - - cbAddedTimer() - const timeoutID = setTimeout(() => { - timeoutFunctionHandles[timeoutID.toString()] = null - // callFunction(vmFnHandleCopy) will call the vm function - // in the context of the vm - // we pass vm.undefined because we need to pass something for the "this" argument - cbRemovedTimer() - vm.callFunction(vmFnHandleCopy, vm.undefined) - vmFnHandleCopy.dispose() - vm.runtime.executePendingJobs() - }, timeout) - timeoutFunctionHandles[timeoutID.toString()] = vmFnHandleCopy - - return vm.newNumber((timeoutID as unknown) as number) - } - ) - - vm.setProp(vm.global, 'setTimeout', _setTimeout) - _setTimeout.dispose() - const intervalFunctionHandles: Record = {} - const _setInterval = vm.newFunction( - 'setInterval', - (vmFnHandle, timeoutHandle) => { - // Make a copy because otherwise vmFnHandle does not live long enough to call after the timeout - const vmFnHandleCopy = vmFnHandle.dup() - let timeout = vm.dump(timeoutHandle) - - // cap timeout at max timeout - if (timeout > maxTimeout) { - timeout = maxTimeout - } - - const maxRepetitions = 99 - let repetitions = 0 - cbAddedTimer() - const intervalId = setInterval(() => { - repetitions += 1 - intervalFunctionHandles[intervalId.toString()] = null - - // callFunction(vmFnHandleCopy) will call the vm function - // in the context of the vm - // we pass vm.undefined because we need to pass something for the "this" argument - vm.callFunction(vmFnHandleCopy, vm.undefined) - vm.runtime.executePendingJobs() - - if (repetitions > maxRepetitions) { - console.log('Sandbox interval exceeded max repetitions') - clearInterval(intervalId) - cbRemovedTimer() - return - } - }, timeout) - intervalFunctionHandles[intervalId.toString()] = vmFnHandleCopy - - return vm.newNumber((intervalId as unknown) as number) - } - ) - vm.setProp(vm.global, 'setInterval', _setInterval) - _setInterval.dispose() - - const _clearTimeout = vm.newFunction( - 'clearTimeout', - (timeoutIdHandle: any) => { - const timeoutId = vm.dump(timeoutIdHandle) - const timeoutHandle = timeoutFunctionHandles[timeoutId.toString()] - if (timeoutHandle) { - cbRemovedTimer() - timeoutHandle.dispose() - clearTimeout(timeoutId) - } - } - ) - vm.setProp(vm.global, 'clearTimeout', _clearTimeout) - _clearTimeout.dispose() - - const _clearInterval = vm.newFunction( - 'clearInterval', - (intervalIdHandle: any) => { - const intervalId = vm.dump(intervalIdHandle) - const intervalHandle = intervalFunctionHandles[intervalId.toString()] - if (intervalId) { - cbRemovedTimer() - intervalHandle.dispose() - clearInterval(intervalId) - } - } - ) - vm.setProp(vm.global, 'clearInterval', _clearInterval) - _clearInterval.dispose() -} diff --git a/src/lib/sandbox.ts b/src/lib/sandbox.ts deleted file mode 100644 index d126b59..0000000 --- a/src/lib/sandbox.ts +++ /dev/null @@ -1,169 +0,0 @@ -import upath from 'upath' -import { readFileSync } from 'node:fs' -import { QuickJSWASMModule } from 'quickjs-emscripten' - -import { injectTimingFunctions, wrap, wrapObject } from './sandbox-wrappers' - -export default async function createSandbox( - QuickJS: QuickJSWASMModule, - requireLookup: Record = {}, - globals: Record = {}, - options: Record = {} -) { - const vm = QuickJS.newContext() - vm.evalCode( - readFileSync( - require.resolve(upath.join(__dirname, 'sidevm-quickjs-bootcode.js')), - 'utf8' - ) - ) - - let errorState = false - let lastError = '' - - let asyncProcessesRunning = 0 - - const beginAsyncProcess = () => { - asyncProcessesRunning++ - } - - const endAsyncProcess = () => { - if (asyncProcessesRunning > 0) { - asyncProcessesRunning-- - } - } - - const isAsyncProcessRunning = () => { - return asyncProcessesRunning > 0 - } - - const consoleHandle = vm.newObject() - const exportsHandle = vm.newObject() - - const fetchHandle = vm.newFunction('fetch', (...args) => { - const nativeArgs = args.map(vm.dump) - const result = fetch(nativeArgs[0], nativeArgs[1]) - return wrap(vm, result, {}, beginAsyncProcess, endAsyncProcess) - }) - - const logHandle = vm.newFunction('log', (...args) => { - const nativeArgs = args.map(vm.dump) - console.log(...nativeArgs) - }) - - const infoHandle = vm.newFunction('info', (...args) => { - const nativeArgs = args.map(vm.dump) - console.info(...nativeArgs) - }) - - const warnHandle = vm.newFunction('warn', (...args) => { - const nativeArgs = args.map(vm.dump) - console.warn(...nativeArgs) - }) - - const debugHandle = vm.newFunction('debug', (...args) => { - const nativeArgs = args.map(vm.dump) - console.debug(...nativeArgs) - }) - - const errorHandle = vm.newFunction('error', (...args) => { - try { - const nativeArgs = args.map(vm.dump) - console.error(...nativeArgs) - lastError = JSON.stringify(nativeArgs) - errorState = true - } catch (e) { - console.error('Error in error:', e) - } - }) - - const globalNames = Object.getOwnPropertyNames(globals) - for (let i = 0; i < globalNames.length; i++) { - const globalValue: any = globals[globalNames[i]] - const globalObj = wrap( - vm, - globalValue, - undefined, - beginAsyncProcess, - endAsyncProcess - ) - - vm.setProp(vm.global, globalNames[i], globalObj) - globalObj.dispose() - } - - injectTimingFunctions(vm, beginAsyncProcess, endAsyncProcess) - - const requireHandle = vm.newFunction('require', (...args: any) => { - const nativeArgs = args.map(vm.dump) - if (requireLookup[nativeArgs[0] as string]) { - const returnObj = wrapObject( - vm, - requireLookup[nativeArgs[0] as string], - beginAsyncProcess, - endAsyncProcess - ) - return returnObj - } else { - return vm.undefined - } - }) - - if (options.silent) { - const handle = vm.newFunction('log', () => {}) - vm.setProp(consoleHandle, 'log', handle) - vm.setProp(consoleHandle, 'info', handle) - vm.setProp(consoleHandle, 'warn', handle) - vm.setProp(consoleHandle, 'error', handle) - vm.setProp(consoleHandle, 'debug', handle) - handle.dispose() - } else { - vm.setProp(consoleHandle, 'log', logHandle) - vm.setProp(consoleHandle, 'info', infoHandle) - vm.setProp(consoleHandle, 'warn', warnHandle) - vm.setProp(consoleHandle, 'debug', debugHandle) - vm.setProp(consoleHandle, 'error', errorHandle) - logHandle.dispose() - infoHandle.dispose() - warnHandle.dispose() - debugHandle.dispose() - errorHandle.dispose() - } - - vm.setProp(vm.global, 'fetch', fetchHandle) - vm.setProp(vm.global, 'console', consoleHandle) - vm.setProp(vm.global, 'require', requireHandle) - vm.setProp(vm.global, 'exports', exportsHandle) - - fetchHandle.dispose() - consoleHandle.dispose() - requireHandle.dispose() - exportsHandle.dispose() - - return { - vm, - getLastError: () => { - return lastError - }, - isAsyncProcessRunning, - run: (code: string): boolean => { - try { - errorState = false - const result = vm.evalCode(code) - - if (result.error) { - console.error('Execution failed:', vm.dump(result.error)) - result.error.dispose() - return false - } else { - result.value.dispose() - return !errorState - } - } catch (e) { - console.error(e) - } - - return false - }, - } -}