@sebastianwessel/quickjs
Version:
A typescript package to execute JavaScript and TypeScript code in a WebAssembly QuickJS sandbox
136 lines (135 loc) • 5.23 kB
JavaScript
import { Scope } from 'quickjs-emscripten-core';
import { handleToNative } from '../handleToNative/handleToNative.js';
import { isES2015Class } from './isES2015Class.js';
import { isObject } from './isObject.js';
export const getHandle = (scope, ctx, name, input) => {
// null
if (input === null) {
return ctx.null;
}
// null
if (input === undefined) {
return ctx.undefined;
}
// Array Buffer
if (input instanceof ArrayBuffer) {
return ctx.newArrayBuffer(input);
}
// Promise
if (input instanceof Promise) {
const promise = ctx.newPromise();
promise.settled.then(ctx.runtime.executePendingJobs);
input.then(r => {
const handle = getHandle(scope, ctx, '', r);
promise.resolve(handle);
handle.dispose();
}, e => {
const handle = 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 Scope();
const that = handleToNative(ctx, this, s);
if (this) {
this.dispose();
}
if (isES2015Class(input) && isObject(that)) {
const result = new input(...args);
for (const [key, value] of Object.entries(result)) {
ctx.setProp(this, key, getHandle(s, ctx, key, value));
}
s.dispose();
return this;
}
const rawParam = [];
for (const param of args) {
const p = s.manage(param);
rawParam.push(handleToNative(ctx, p, s));
}
try {
const handle = 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
? 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}`);
};
const setProperties = (ctx, scope,
// biome-ignore lint/complexity/noBannedTypes: <explanation>
input, parent) => {
const descs = ctx.newObject();
const setEntry = (key, desc) => {
const keyHandle = getHandle(scope, ctx, '', key);
const valueHandle = typeof desc.value === 'undefined' ? undefined : getHandle(scope, ctx, '', desc.value);
const getterHandle = typeof desc.get === 'undefined' ? undefined : getHandle(scope, ctx, '', desc.get);
const setterHandle = typeof desc.set === 'undefined' ? undefined : 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(getHandle(scope, ctx, name, value));
ctx.setProp(parent, name, handle);
};
export const expose = (ctx, _parentScope, input, parent) => {
const scope = new Scope();
for (const [key, value] of Object.entries(input)) {
addProp(scope, ctx, parent ?? ctx.global, key, value);
}
scope.dispose();
};