UNPKG

@sebastianwessel/quickjs

Version:

A typescript package to execute JavaScript and TypeScript code in a WebAssembly QuickJS sandbox

161 lines (160 loc) 6.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.expose = exports.getHandle = void 0; const quickjs_emscripten_core_1 = require("quickjs-emscripten-core"); const fetch_js_1 = require("../../adapter/fetch.js"); const handleToNative_js_1 = require("../handleToNative/handleToNative.js"); const isES2015Class_js_1 = require("./isES2015Class.js"); const isObject_js_1 = require("./isObject.js"); const pendingHostPromises_js_1 = require("./pendingHostPromises.js"); const getHandle = (scope, ctx, name, input) => { // null if (input === null) { return ctx.null; } // null if (input === undefined) { return ctx.undefined; } // Map serialized fetch headers from host to a native Headers instance in guest. if (input && typeof input === 'object' && fetch_js_1.HEADERS_MARKER in input && '_headers' in input) { const headersValue = input._headers; const headersJson = JSON.stringify(headersValue); return ctx.unwrapResult(ctx.evalCode(`new Headers(${headersJson})`)); } // Array Buffer if (input instanceof ArrayBuffer) { return ctx.newArrayBuffer(input); } // Promise if (input instanceof Promise) { const promise = ctx.newPromise(); (0, pendingHostPromises_js_1.registerPendingHostPromise)(ctx, promise); promise.settled.finally(() => { (0, pendingHostPromises_js_1.unregisterPendingHostPromise)(ctx, promise); if (ctx.alive) { ctx.runtime.executePendingJobs(); } }); input.then(r => { if (!ctx.alive || !promise.alive) { return; } const handle = (0, exports.getHandle)(scope, ctx, '', r); promise.resolve(handle); handle.dispose(); }, e => { if (!ctx.alive || !promise.alive) { return; } const handle = (0, exports.getHandle)(scope, ctx, '', e); promise.reject(handle); handle.dispose(); }); return promise.handle; } switch (typeof input) { case 'number': return ctx.newNumber(input); case 'string': return ctx.newString(input); case 'boolean': return input ? ctx.true : ctx.false; case 'bigint': return ctx.newBigInt(input); case 'symbol': return ctx.newSymbolFor(input); case 'function': return ctx.newFunction(name, function (...args) { const s = new quickjs_emscripten_core_1.Scope(); const that = (0, handleToNative_js_1.handleToNative)(ctx, this, s); if (this) { this.dispose(); } if ((0, isES2015Class_js_1.isES2015Class)(input) && (0, isObject_js_1.isObject)(that)) { const result = new input(...args); for (const [key, value] of Object.entries(result)) { ctx.setProp(this, key, (0, exports.getHandle)(s, ctx, key, value)); } s.dispose(); return this; } const rawParam = []; for (const param of args) { const p = s.manage(param); rawParam.push((0, handleToNative_js_1.handleToNative)(ctx, p, s)); } try { const handle = (0, exports.getHandle)(s, ctx, '', input.apply(that, rawParam)); return handle; } finally { s.dispose(); } }); } if (typeof input === 'object' || input === null) { if (input instanceof Date) { const x = ctx.evalCode(`new Date(${input.getTime()})`); return x.unwrap(); } const prototype = Object.getPrototypeOf(input); const prototypeHandle = prototype && prototype !== Object.prototype && prototype !== Array.prototype ? (0, exports.getHandle)(scope, ctx, '', prototype) : undefined; const handle = Array.isArray(input) ? ctx.newArray() : ctx.newObject(prototypeHandle); setProperties(ctx, scope, input, handle); prototypeHandle?.dispose(); return handle; } throw new Error(`unsupported data type in ${name} ${typeof input}`); }; exports.getHandle = getHandle; const setProperties = (ctx, scope, // biome-ignore lint/complexity/noBannedTypes: ok here input, parent) => { const descs = ctx.newObject(); const setEntry = (key, desc) => { const keyHandle = (0, exports.getHandle)(scope, ctx, '', key); const valueHandle = typeof desc.value === 'undefined' ? undefined : (0, exports.getHandle)(scope, ctx, '', desc.value); const getterHandle = typeof desc.get === 'undefined' ? undefined : (0, exports.getHandle)(scope, ctx, '', desc.get); const setterHandle = typeof desc.set === 'undefined' ? undefined : (0, exports.getHandle)(scope, ctx, '', desc.set); const descObj = ctx.newObject(); for (const [k, v] of Object.entries(desc)) { const v2 = k === 'value' ? valueHandle : k === 'get' ? getterHandle : k === 'set' ? setterHandle : v ? ctx.true : ctx.false; if (v2) { ctx.setProp(descObj, k, v2); } } ctx.setProp(descs, keyHandle, descObj); keyHandle.dispose(); valueHandle?.dispose(); getterHandle?.dispose(); setterHandle?.dispose(); descObj.dispose(); }; const desc = Object.getOwnPropertyDescriptors(input); for (const [k, v] of Object.entries(desc)) { setEntry(k, v); } for (const k of Object.getOwnPropertySymbols(desc)) { setEntry(k, desc[k]); } const fnHandle = ctx.unwrapResult(ctx.evalCode('Object.defineProperties')); const callHandle = ctx.unwrapResult(ctx.callFunction(fnHandle, ctx.undefined, parent, descs)); callHandle.dispose(); fnHandle.dispose(); descs.dispose(); }; const addProp = (scope, ctx, parent, name, value) => { const handle = scope.manage((0, exports.getHandle)(scope, ctx, name, value)); ctx.setProp(parent, name, handle); }; const expose = (ctx, _parentScope, input, parent) => { const scope = new quickjs_emscripten_core_1.Scope(); for (const [key, value] of Object.entries(input)) { addProp(scope, ctx, parent ?? ctx.global, key, value); } scope.dispose(); }; exports.expose = expose;