UVM (Universal Virtual Machine)
UVM (Universal Virtual Machine) is a JavaScript library designed to provide a consistent API for executing code in isolated contexts, leveraging Node.js Worker Threads and Web Workers in browser environments. This abstraction simplifies cross-context communication via an event emitter-based bridge, enabling developers to run potentially untrusted or computationally intensive code without blocking the main thread. Currently at version 4.0.1 (last published 10 months ago, as of current date), the library appears to be actively maintained. Its key differentiator lies in offering a 'universal' approach to sandboxed code execution, providing a unified development experience across different JavaScript runtimes, making it suitable for applications requiring secure and efficient off-main-thread processing. The project originates from Postman Labs.
Common errors
-
TypeError: require is not a function
cause Attempting to use CommonJS `require` syntax in an ESM module context when `uvm` is imported as an ESM module, or when the project is configured for ESM-only.fixIf your project is ESM, change `const uvm = require('uvm');` to `import uvm from 'uvm';`. Ensure your `package.json` has `"type": "module"` or use `.mjs` file extensions for ESM. -
Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': An object could not be cloned.
cause Attempting to pass a non-serializable object (e.g., a DOM element, a function, or a complex class instance) across the UVM bridge to or from a Web Worker.fixBefore dispatching data across the `bridge`, ensure that the data is structured clone algorithm compatible. Convert complex objects into plain JavaScript objects (POJOs), serialize functions to strings, or extract only necessary primitive values. -
ReferenceError: bridge is not defined
cause This error typically occurs if `bridge.on` or `bridge.dispatch` is called outside the `bootCode` context or before the UVM context has fully initialized and established its `bridge` object.fixEnsure that `bridge.on` and `bridge.dispatch` calls are exclusively within the `bootCode` string provided to `uvm.spawn`, which defines the code to run inside the isolated VM/Worker. The main context interacts via `context.on` and `context.dispatch`.
Warnings
- breaking UVM `v3.x` and `v4.x` likely introduced breaking changes related to ESM support and Node.js Worker Threads APIs. Projects upgrading from older `v2.x` versions, especially those relying solely on CommonJS or targeting older Node.js versions, may require code adjustments. Always consult the GitHub releases for specific upgrade guides.
- gotcha Data passed between the main context and the spawned UVM context (Node.js Worker Thread or Web Worker) must be serializable. Complex objects, functions, or non-primitive data types might not transfer correctly or may throw serialization errors.
- gotcha The code executed within the UVM `bootCode` runs in an isolated scope. It does not have direct access to variables or globals from the main application context unless explicitly passed during `spawn` or through the `bridge` mechanism. This isolation is by design for security and stability but can be a source of confusion.
- gotcha While UVM provides an isolated environment, it's not a complete security sandbox for running arbitrary, malicious untrusted code, especially in Node.js. Node.js's native `vm` module and `worker_threads` have known limitations regarding true sandboxing against sophisticated attacks.
Install
-
npm install uvm -
yarn add uvm -
pnpm add uvm
Imports
- uvm
const uvm = require('uvm');import uvm from 'uvm';
- uvm
import uvm from 'uvm';
const uvm = require('uvm'); - spawn
import { spawn } from 'uvm';import uvm from 'uvm'; const context = uvm.spawn({ /* options */ });
Quickstart
import uvm from 'uvm';
const context = uvm.spawn({
bootCode: `
// Code running inside the isolated VM/Worker context
bridge.on('loopback', function (data) {
console.log(`VM received: ${data}`);
bridge.dispatch('loopback', data + ' World!');
});
// Example of handling potential errors inside the VM
try {
// Some potentially error-prone code
// throw new Error('Simulated VM error');
} catch (e) {
bridge.dispatch('error', e.message);
}
`
});
context.on('loopback', function (data) {
console.log(`Main context received: ${data}`); // Expected: Hello World!
});
context.on('error', function (errorMessage) {
console.error(`VM Error: ${errorMessage}`);
});
context.dispatch('loopback', 'Hello');
// To demonstrate cleanup (important for resource management)
setTimeout(() => {
console.log('Disconnecting UVM context...');
context.disconnect();
}, 1000);