@sebastianwessel/quickjs
Version:
A typescript package to execute JavaScript and TypeScript code in a WebAssembly QuickJS sandbox
78 lines (77 loc) • 3.73 kB
JavaScript
import { newQuickJSWASMModuleFromVariant, Scope, shouldInterruptAfterDeadline } from 'quickjs-emscripten-core';
import { getTypescriptSupport } from './getTypescriptSupport.js';
import { rejectAndFlushPendingHostPromises } from './sandbox/expose/pendingHostPromises.js';
import { setupFileSystem } from './sandbox/setupFileSystem.js';
import { executeSandboxFunction } from './sandbox/syncVersion/executeSandboxFunction.js';
import { getModuleLoader } from './sandbox/syncVersion/getModuleLoader.js';
import { modulePathNormalizer } from './sandbox/syncVersion/modulePathNormalizer.js';
import { prepareNodeCompatibility } from './sandbox/syncVersion/prepareNodeCompatibility.js';
import { prepareSandbox } from './sandbox/syncVersion/prepareSandbox.js';
/**
* Loads the QuickJS module and returns a sandbox execution function.
* @param variant - Options for loading the QuickJS module. Defaults to '@jitl/quickjs-ng-wasmfile-release-sync'.
* @returns An object containing the runSandboxed function and the loaded module.
*/
export const loadQuickJs = async (variant) => {
const module = await newQuickJSWASMModuleFromVariant(variant);
/**
* Provides a new sandbox and executes the given function.
* When the function has been finished, the sandbox gets disposed and can longer be used.
*
* @param sandboxedFunction
* @param sandboxOptions
* @returns
*/
const runSandboxed = async (sandboxedFunction, sandboxOptions = {}) => {
const scope = new Scope();
let ctx;
try {
ctx = scope.manage(module.newContext());
if (sandboxOptions.maxStackSize) {
ctx.runtime.setMaxStackSize(sandboxOptions.maxStackSize);
}
if (sandboxOptions.memoryLimit) {
ctx.runtime.setMemoryLimit(sandboxOptions.memoryLimit);
}
// Virtual File System,
const fs = setupFileSystem(sandboxOptions);
// TypeScript Support:
const { transpileVirtualFs, transpileFile } = await getTypescriptSupport(sandboxOptions.transformTypescript, sandboxOptions.typescriptImportFile, sandboxOptions.transformCompilerOptions);
// if typescript support is enabled, transpile all ts files in file system
transpileVirtualFs(fs);
// JS Module Loader
const moduleLoader = sandboxOptions.getModuleLoader
? sandboxOptions.getModuleLoader(fs, sandboxOptions)
: getModuleLoader(fs, sandboxOptions);
ctx.runtime.setModuleLoader(moduleLoader, sandboxOptions.modulePathNormalizer ?? modulePathNormalizer);
// Register Globals to be more Node.js compatible
prepareNodeCompatibility(ctx, sandboxOptions);
// Prepare the Sandbox
// Expose Data and Functions to Client
prepareSandbox(ctx, scope, sandboxOptions, fs);
if (sandboxOptions.executionTimeout) {
ctx.runtime.setInterruptHandler(shouldInterruptAfterDeadline(Date.now() + sandboxOptions.executionTimeout));
}
// Run the given Function
const result = await executeSandboxFunction({
ctx,
fs,
scope,
sandboxOptions,
sandboxedFunction,
transpileFile,
});
return result;
}
catch (error) {
throw error instanceof Error ? error : new Error('Internal Error');
}
finally {
if (ctx) {
await rejectAndFlushPendingHostPromises(ctx);
}
scope.dispose();
}
};
return { runSandboxed, module };
};