UNPKG

@sebastianwessel/quickjs

Version:

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

154 lines (153 loc) 6.18 kB
import { Scope, } from 'quickjs-emscripten-core'; import { getHandle } from '../expose/expose.js'; import { call } from '../helper.js'; import { getSerializer } from './serializer/index.js'; /** * Serialize data from guest to host * * @param ctx The sandbox context * @param handle The QuickJS handle to serialize * @returns */ export const handleToNative = (ctx, handle, rootScope) => { const ty = ctx.typeof(handle); if (ty === 'undefined') { return undefined; } if (ty === 'null') { return null; } if (ty === 'number' || ty === 'string' || ty === 'boolean' || ty === 'bigint') { return ctx.dump(handle); } if (ty === 'symbol') { const desc = ctx.getString(ctx.getProp(handle, 'description')); return Symbol(desc); } const asPromiseState = ctx.getPromiseState(handle); if (asPromiseState.type && !asPromiseState.notAPromise) { return ctx.resolvePromise(handle).then(val => { if (val.error) { const error = handleToNative(ctx, val.error, rootScope); val.error.dispose(); return Promise.reject(error); } const value = handleToNative(ctx, val.value, rootScope); val.value.dispose(); return Promise.resolve(value); }); } // biome-ignore lint/complexity/noBannedTypes: <explanation> const setProperties = (obj, h) => { ctx .newFunction('', (key, value) => { const keyName = handleToNative(ctx, key, rootScope); if (typeof keyName !== 'string' && typeof keyName !== 'number' && typeof keyName !== 'symbol') return; const desc = [ ['value', true], ['get', true], ['set', true], ['configurable', false], ['enumerable', false], ['writable', false], ].reduce((desc, [key, unmarshable]) => { const h = ctx.getProp(value, key); const t = ctx.typeof(h); if (t === 'undefined') return desc; if (!unmarshable && t === 'boolean') { desc[key] = ctx.dump(h); return desc; } desc[key] = handleToNative(ctx, h, rootScope); h.dispose(); return desc; }, {}); Object.defineProperty(obj, keyName, desc); }) .consume(f => { call(ctx, 'internal/serializer/setProperties.js', `(o, fn) => { const descs = Object.getOwnPropertyDescriptors(o); Object.entries(descs).forEach(([k, v]) => fn(k, v)); Object.getOwnPropertySymbols(descs).forEach(k => fn(k, descs[k])); }`, undefined, h, f).dispose(); }); }; if (ty === 'function') { if (!rootScope) throw new Error('Missing root scope'); const cpHandle = rootScope.manage(handle.dup()); const f = function (...args) { const scope = new Scope(); const thisHandle = getHandle(scope, ctx, '', this); const argHandles = args.map(a => getHandle(scope, ctx, '', a)); if (new.target) { const instance = handleToNative(ctx, call(ctx, 'internal/serializer/newClass.js', '(Cls, ...args) => new Cls(...args)', thisHandle, cpHandle, ...argHandles), rootScope); Object.defineProperties(this, Object.getOwnPropertyDescriptors(instance)); scope.dispose(); return this; } try { const resultHandle = scope.manage(ctx.unwrapResult(ctx.callFunction(cpHandle, thisHandle, ...argHandles))); const res = handleToNative(ctx, resultHandle, rootScope); return res; } finally { scope.dispose(); } }; setProperties(f, handle); return f; } if (ty === 'object') { const isNull = call(ctx, 'internal/serializer/isNull.js', 'a => a === null', undefined, handle).consume(r => ctx.dump(r)); if (isNull) return null; // Check for Error instances const errorType = call(ctx, 'internal/serializer/detectErrorType.js', `(o) => { try { const tag = Object.prototype.toString.call(o) if (tag.startsWith('[object ') && tag.endsWith(']')) { const type = tag.slice(8, -1) if (type.endsWith('Error')) return type } } catch {} if (typeof o?.name === 'string' && typeof o?.message === 'string') return 'Error' return undefined }`, undefined, handle).consume(r => ctx.dump(r)); if (typeof errorType === 'string') { const s = getSerializer(errorType); if (s) { const ret = s(ctx, handle); if (ret) { return ret; } } const messageHandle = ctx.getProp(handle, 'message'); const stackHandle = ctx.getProp(handle, 'stack'); const message = ctx.dump(messageHandle) ?? ''; const stack = ctx.dump(stackHandle); messageHandle.dispose(); stackHandle.dispose(); const e = new Error(message); e.name = errorType; if (typeof stack === 'string') e.stack = stack; return e; } const isArray = call(ctx, 'internal/serializer/isArray.js', 'Array.isArray', undefined, handle).consume(r => ctx.dump(r)); const obj = isArray ? [] : {}; const constructorName = call(ctx, 'internal/serializer/getConstructorName.js', `o => typeof o?.constructor?.name === 'string' ? o.constructor.name : undefined`, undefined, handle).consume(r => ctx.dump(r)); const serializer = getSerializer(constructorName); if (serializer) { const ret = serializer(ctx, handle); if (ret) { return ret; } } setProperties(obj, handle); return obj; } throw new TypeError(`Failed to serialize ${ty}`); };