almostnode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +731 -0
- package/dist/__sw__.js +394 -0
- package/dist/ai-chatbot-demo-entry.d.ts +6 -0
- package/dist/ai-chatbot-demo-entry.d.ts.map +1 -0
- package/dist/ai-chatbot-demo.d.ts +42 -0
- package/dist/ai-chatbot-demo.d.ts.map +1 -0
- package/dist/assets/runtime-worker-D9x_Ddwz.js +60543 -0
- package/dist/assets/runtime-worker-D9x_Ddwz.js.map +1 -0
- package/dist/convex-app-demo-entry.d.ts +6 -0
- package/dist/convex-app-demo-entry.d.ts.map +1 -0
- package/dist/convex-app-demo.d.ts +68 -0
- package/dist/convex-app-demo.d.ts.map +1 -0
- package/dist/cors-proxy.d.ts +46 -0
- package/dist/cors-proxy.d.ts.map +1 -0
- package/dist/create-runtime.d.ts +42 -0
- package/dist/create-runtime.d.ts.map +1 -0
- package/dist/demo.d.ts +6 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/dev-server.d.ts +97 -0
- package/dist/dev-server.d.ts.map +1 -0
- package/dist/frameworks/next-dev-server.d.ts +202 -0
- package/dist/frameworks/next-dev-server.d.ts.map +1 -0
- package/dist/frameworks/vite-dev-server.d.ts +85 -0
- package/dist/frameworks/vite-dev-server.d.ts.map +1 -0
- package/dist/index.cjs +14965 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +14867 -0
- package/dist/index.mjs.map +1 -0
- package/dist/next-demo.d.ts +49 -0
- package/dist/next-demo.d.ts.map +1 -0
- package/dist/npm/index.d.ts +71 -0
- package/dist/npm/index.d.ts.map +1 -0
- package/dist/npm/registry.d.ts +66 -0
- package/dist/npm/registry.d.ts.map +1 -0
- package/dist/npm/resolver.d.ts +52 -0
- package/dist/npm/resolver.d.ts.map +1 -0
- package/dist/npm/tarball.d.ts +29 -0
- package/dist/npm/tarball.d.ts.map +1 -0
- package/dist/runtime-interface.d.ts +90 -0
- package/dist/runtime-interface.d.ts.map +1 -0
- package/dist/runtime.d.ts +103 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/sandbox-helpers.d.ts +43 -0
- package/dist/sandbox-helpers.d.ts.map +1 -0
- package/dist/sandbox-runtime.d.ts +65 -0
- package/dist/sandbox-runtime.d.ts.map +1 -0
- package/dist/server-bridge.d.ts +89 -0
- package/dist/server-bridge.d.ts.map +1 -0
- package/dist/shims/assert.d.ts +51 -0
- package/dist/shims/assert.d.ts.map +1 -0
- package/dist/shims/async_hooks.d.ts +37 -0
- package/dist/shims/async_hooks.d.ts.map +1 -0
- package/dist/shims/buffer.d.ts +20 -0
- package/dist/shims/buffer.d.ts.map +1 -0
- package/dist/shims/child_process-browser.d.ts +92 -0
- package/dist/shims/child_process-browser.d.ts.map +1 -0
- package/dist/shims/child_process.d.ts +93 -0
- package/dist/shims/child_process.d.ts.map +1 -0
- package/dist/shims/chokidar.d.ts +55 -0
- package/dist/shims/chokidar.d.ts.map +1 -0
- package/dist/shims/cluster.d.ts +52 -0
- package/dist/shims/cluster.d.ts.map +1 -0
- package/dist/shims/crypto.d.ts +122 -0
- package/dist/shims/crypto.d.ts.map +1 -0
- package/dist/shims/dgram.d.ts +34 -0
- package/dist/shims/dgram.d.ts.map +1 -0
- package/dist/shims/diagnostics_channel.d.ts +80 -0
- package/dist/shims/diagnostics_channel.d.ts.map +1 -0
- package/dist/shims/dns.d.ts +87 -0
- package/dist/shims/dns.d.ts.map +1 -0
- package/dist/shims/domain.d.ts +25 -0
- package/dist/shims/domain.d.ts.map +1 -0
- package/dist/shims/esbuild.d.ts +105 -0
- package/dist/shims/esbuild.d.ts.map +1 -0
- package/dist/shims/events.d.ts +37 -0
- package/dist/shims/events.d.ts.map +1 -0
- package/dist/shims/fs.d.ts +115 -0
- package/dist/shims/fs.d.ts.map +1 -0
- package/dist/shims/fsevents.d.ts +67 -0
- package/dist/shims/fsevents.d.ts.map +1 -0
- package/dist/shims/http.d.ts +217 -0
- package/dist/shims/http.d.ts.map +1 -0
- package/dist/shims/http2.d.ts +81 -0
- package/dist/shims/http2.d.ts.map +1 -0
- package/dist/shims/https.d.ts +36 -0
- package/dist/shims/https.d.ts.map +1 -0
- package/dist/shims/inspector.d.ts +25 -0
- package/dist/shims/inspector.d.ts.map +1 -0
- package/dist/shims/module.d.ts +22 -0
- package/dist/shims/module.d.ts.map +1 -0
- package/dist/shims/net.d.ts +100 -0
- package/dist/shims/net.d.ts.map +1 -0
- package/dist/shims/os.d.ts +159 -0
- package/dist/shims/os.d.ts.map +1 -0
- package/dist/shims/path.d.ts +72 -0
- package/dist/shims/path.d.ts.map +1 -0
- package/dist/shims/perf_hooks.d.ts +50 -0
- package/dist/shims/perf_hooks.d.ts.map +1 -0
- package/dist/shims/process.d.ts +93 -0
- package/dist/shims/process.d.ts.map +1 -0
- package/dist/shims/querystring.d.ts +23 -0
- package/dist/shims/querystring.d.ts.map +1 -0
- package/dist/shims/readdirp.d.ts +52 -0
- package/dist/shims/readdirp.d.ts.map +1 -0
- package/dist/shims/readline.d.ts +62 -0
- package/dist/shims/readline.d.ts.map +1 -0
- package/dist/shims/rollup.d.ts +34 -0
- package/dist/shims/rollup.d.ts.map +1 -0
- package/dist/shims/sentry.d.ts +163 -0
- package/dist/shims/sentry.d.ts.map +1 -0
- package/dist/shims/stream.d.ts +181 -0
- package/dist/shims/stream.d.ts.map +1 -0
- package/dist/shims/tls.d.ts +53 -0
- package/dist/shims/tls.d.ts.map +1 -0
- package/dist/shims/tty.d.ts +30 -0
- package/dist/shims/tty.d.ts.map +1 -0
- package/dist/shims/url.d.ts +64 -0
- package/dist/shims/url.d.ts.map +1 -0
- package/dist/shims/util.d.ts +106 -0
- package/dist/shims/util.d.ts.map +1 -0
- package/dist/shims/v8.d.ts +73 -0
- package/dist/shims/v8.d.ts.map +1 -0
- package/dist/shims/vfs-adapter.d.ts +126 -0
- package/dist/shims/vfs-adapter.d.ts.map +1 -0
- package/dist/shims/vm.d.ts +45 -0
- package/dist/shims/vm.d.ts.map +1 -0
- package/dist/shims/worker_threads.d.ts +66 -0
- package/dist/shims/worker_threads.d.ts.map +1 -0
- package/dist/shims/ws.d.ts +66 -0
- package/dist/shims/ws.d.ts.map +1 -0
- package/dist/shims/zlib.d.ts +161 -0
- package/dist/shims/zlib.d.ts.map +1 -0
- package/dist/transform.d.ts +24 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/virtual-fs.d.ts +226 -0
- package/dist/virtual-fs.d.ts.map +1 -0
- package/dist/vite-demo.d.ts +35 -0
- package/dist/vite-demo.d.ts.map +1 -0
- package/dist/vite-sw.js +132 -0
- package/dist/worker/runtime-worker.d.ts +8 -0
- package/dist/worker/runtime-worker.d.ts.map +1 -0
- package/dist/worker-runtime.d.ts +50 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/package.json +85 -0
- package/src/ai-chatbot-demo-entry.ts +244 -0
- package/src/ai-chatbot-demo.ts +509 -0
- package/src/convex-app-demo-entry.ts +1107 -0
- package/src/convex-app-demo.ts +1316 -0
- package/src/cors-proxy.ts +81 -0
- package/src/create-runtime.ts +147 -0
- package/src/demo.ts +304 -0
- package/src/dev-server.ts +274 -0
- package/src/frameworks/next-dev-server.ts +2224 -0
- package/src/frameworks/vite-dev-server.ts +702 -0
- package/src/index.ts +101 -0
- package/src/next-demo.ts +1784 -0
- package/src/npm/index.ts +347 -0
- package/src/npm/registry.ts +152 -0
- package/src/npm/resolver.ts +385 -0
- package/src/npm/tarball.ts +209 -0
- package/src/runtime-interface.ts +103 -0
- package/src/runtime.ts +1046 -0
- package/src/sandbox-helpers.ts +173 -0
- package/src/sandbox-runtime.ts +252 -0
- package/src/server-bridge.ts +426 -0
- package/src/shims/assert.ts +664 -0
- package/src/shims/async_hooks.ts +86 -0
- package/src/shims/buffer.ts +75 -0
- package/src/shims/child_process-browser.ts +217 -0
- package/src/shims/child_process.ts +463 -0
- package/src/shims/chokidar.ts +313 -0
- package/src/shims/cluster.ts +67 -0
- package/src/shims/crypto.ts +830 -0
- package/src/shims/dgram.ts +47 -0
- package/src/shims/diagnostics_channel.ts +196 -0
- package/src/shims/dns.ts +172 -0
- package/src/shims/domain.ts +58 -0
- package/src/shims/esbuild.ts +805 -0
- package/src/shims/events.ts +195 -0
- package/src/shims/fs.ts +803 -0
- package/src/shims/fsevents.ts +63 -0
- package/src/shims/http.ts +904 -0
- package/src/shims/http2.ts +96 -0
- package/src/shims/https.ts +86 -0
- package/src/shims/inspector.ts +30 -0
- package/src/shims/module.ts +82 -0
- package/src/shims/net.ts +359 -0
- package/src/shims/os.ts +195 -0
- package/src/shims/path.ts +199 -0
- package/src/shims/perf_hooks.ts +92 -0
- package/src/shims/process.ts +346 -0
- package/src/shims/querystring.ts +97 -0
- package/src/shims/readdirp.ts +228 -0
- package/src/shims/readline.ts +110 -0
- package/src/shims/rollup.ts +80 -0
- package/src/shims/sentry.ts +133 -0
- package/src/shims/stream.ts +1126 -0
- package/src/shims/tls.ts +95 -0
- package/src/shims/tty.ts +64 -0
- package/src/shims/url.ts +171 -0
- package/src/shims/util.ts +312 -0
- package/src/shims/v8.ts +113 -0
- package/src/shims/vfs-adapter.ts +402 -0
- package/src/shims/vm.ts +83 -0
- package/src/shims/worker_threads.ts +111 -0
- package/src/shims/ws.ts +382 -0
- package/src/shims/zlib.ts +289 -0
- package/src/transform.ts +313 -0
- package/src/types/external.d.ts +67 -0
- package/src/virtual-fs.ts +903 -0
- package/src/vite-demo.ts +577 -0
- package/src/worker/runtime-worker.ts +128 -0
- package/src/worker-runtime.ts +145 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Helpers - Generate files for deploying a cross-origin sandbox
|
|
3
|
+
*
|
|
4
|
+
* The sandbox runs on a different origin (e.g., myapp-sandbox.vercel.app)
|
|
5
|
+
* to provide browser-enforced isolation from the main application.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* HTML template for the sandbox page.
|
|
10
|
+
* This loads almostnode and handles postMessage communication with the parent.
|
|
11
|
+
*
|
|
12
|
+
* @param justNodeUrl - URL to load almostnode from (e.g., unpkg, jsdelivr, or your CDN)
|
|
13
|
+
*/
|
|
14
|
+
export function getSandboxHtml(justNodeUrl = 'https://unpkg.com/almostnode/dist/index.js'): string {
|
|
15
|
+
return `<!DOCTYPE html>
|
|
16
|
+
<html>
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="UTF-8">
|
|
19
|
+
<title>almostnode Sandbox</title>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<script type="module">
|
|
23
|
+
import { VirtualFS, Runtime } from '${justNodeUrl}';
|
|
24
|
+
|
|
25
|
+
let vfs = null;
|
|
26
|
+
let runtime = null;
|
|
27
|
+
let consoleCallback = null;
|
|
28
|
+
|
|
29
|
+
// Handle messages from parent
|
|
30
|
+
window.addEventListener('message', async (event) => {
|
|
31
|
+
const { type, id, code, filename, vfsSnapshot, options, path, content } = event.data;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
switch (type) {
|
|
35
|
+
case 'init':
|
|
36
|
+
// Initialize VFS from snapshot
|
|
37
|
+
vfs = VirtualFS.fromSnapshot(vfsSnapshot);
|
|
38
|
+
|
|
39
|
+
// Create runtime with options
|
|
40
|
+
runtime = new Runtime(vfs, {
|
|
41
|
+
cwd: options?.cwd,
|
|
42
|
+
env: options?.env,
|
|
43
|
+
onConsole: (method, args) => {
|
|
44
|
+
// Forward console to parent
|
|
45
|
+
parent.postMessage({
|
|
46
|
+
type: 'console',
|
|
47
|
+
consoleMethod: method,
|
|
48
|
+
consoleArgs: args,
|
|
49
|
+
}, '*');
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 'syncFile':
|
|
55
|
+
// Sync file changes from parent
|
|
56
|
+
if (vfs) {
|
|
57
|
+
if (content === null) {
|
|
58
|
+
try { vfs.unlinkSync(path); } catch {}
|
|
59
|
+
} else {
|
|
60
|
+
vfs.writeFileSync(path, content);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
case 'execute':
|
|
66
|
+
if (!runtime) {
|
|
67
|
+
parent.postMessage({ type: 'error', id, error: 'Runtime not initialized' }, '*');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const execResult = runtime.execute(code, filename);
|
|
71
|
+
parent.postMessage({ type: 'result', id, result: execResult }, '*');
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 'runFile':
|
|
75
|
+
if (!runtime) {
|
|
76
|
+
parent.postMessage({ type: 'error', id, error: 'Runtime not initialized' }, '*');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const runResult = runtime.runFile(filename);
|
|
80
|
+
parent.postMessage({ type: 'result', id, result: runResult }, '*');
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case 'clearCache':
|
|
84
|
+
if (runtime) {
|
|
85
|
+
runtime.clearCache();
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (id) {
|
|
91
|
+
parent.postMessage({
|
|
92
|
+
type: 'error',
|
|
93
|
+
id,
|
|
94
|
+
error: error instanceof Error ? error.message : String(error),
|
|
95
|
+
}, '*');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Signal ready to parent
|
|
101
|
+
parent.postMessage({ type: 'ready' }, '*');
|
|
102
|
+
</script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get vercel.json configuration for the sandbox.
|
|
109
|
+
* Sets up CORS headers to allow embedding as a cross-origin iframe.
|
|
110
|
+
*/
|
|
111
|
+
export function getSandboxVercelConfig(): object {
|
|
112
|
+
return {
|
|
113
|
+
headers: [
|
|
114
|
+
{
|
|
115
|
+
source: '/(.*)',
|
|
116
|
+
headers: [
|
|
117
|
+
{ key: 'Access-Control-Allow-Origin', value: '*' },
|
|
118
|
+
{ key: 'Cross-Origin-Resource-Policy', value: 'cross-origin' },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate all files needed for deploying a sandbox to Vercel.
|
|
127
|
+
*
|
|
128
|
+
* @param justNodeUrl - URL to load almostnode from
|
|
129
|
+
* @returns Object with file names as keys and content as values
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* import { generateSandboxFiles } from 'almostnode/sandbox-helpers';
|
|
134
|
+
*
|
|
135
|
+
* const files = generateSandboxFiles();
|
|
136
|
+
* // Write files to sandbox/ directory
|
|
137
|
+
* // Deploy to Vercel: cd sandbox && vercel --prod
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export function generateSandboxFiles(justNodeUrl?: string): {
|
|
141
|
+
'index.html': string;
|
|
142
|
+
'vercel.json': string;
|
|
143
|
+
} {
|
|
144
|
+
return {
|
|
145
|
+
'index.html': getSandboxHtml(justNodeUrl),
|
|
146
|
+
'vercel.json': JSON.stringify(getSandboxVercelConfig(), null, 2),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Instructions for setting up a sandbox on Vercel.
|
|
152
|
+
* Useful for documentation or CLI output.
|
|
153
|
+
*/
|
|
154
|
+
export const SANDBOX_SETUP_INSTRUCTIONS = `
|
|
155
|
+
# Setting up a almostnode Sandbox on Vercel
|
|
156
|
+
|
|
157
|
+
## 1. Create sandbox directory
|
|
158
|
+
mkdir sandbox
|
|
159
|
+
|
|
160
|
+
## 2. Generate sandbox files
|
|
161
|
+
Use generateSandboxFiles() or copy the templates manually.
|
|
162
|
+
|
|
163
|
+
## 3. Deploy to Vercel
|
|
164
|
+
cd sandbox
|
|
165
|
+
vercel --prod
|
|
166
|
+
|
|
167
|
+
## 4. Use in your app
|
|
168
|
+
const runtime = await createRuntime(vfs, {
|
|
169
|
+
sandbox: 'https://your-sandbox.vercel.app'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
For more details, see: https://github.com/anthropics/almostnode#sandbox-setup
|
|
173
|
+
`.trim();
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SandboxRuntime - Runs code in a cross-origin iframe for secure execution
|
|
3
|
+
*
|
|
4
|
+
* This provides browser-enforced isolation from cookies, localStorage,
|
|
5
|
+
* sessionStorage, and IndexedDB by running code on a different origin.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { VirtualFS } from './virtual-fs';
|
|
9
|
+
import type { IRuntime, IExecuteResult, IRuntimeOptions, VFSSnapshot } from './runtime-interface';
|
|
10
|
+
|
|
11
|
+
interface SandboxMessage {
|
|
12
|
+
type: 'init' | 'execute' | 'runFile' | 'clearCache' | 'syncFile' | 'ready' | 'result' | 'error' | 'console';
|
|
13
|
+
id?: string;
|
|
14
|
+
code?: string;
|
|
15
|
+
filename?: string;
|
|
16
|
+
vfsSnapshot?: VFSSnapshot;
|
|
17
|
+
options?: IRuntimeOptions;
|
|
18
|
+
result?: IExecuteResult;
|
|
19
|
+
error?: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
content?: string | null;
|
|
22
|
+
consoleMethod?: string;
|
|
23
|
+
consoleArgs?: unknown[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* SandboxRuntime - Executes code in a cross-origin iframe
|
|
28
|
+
*/
|
|
29
|
+
export class SandboxRuntime implements IRuntime {
|
|
30
|
+
private iframe: HTMLIFrameElement;
|
|
31
|
+
private sandboxOrigin: string;
|
|
32
|
+
private vfs: VirtualFS;
|
|
33
|
+
private options: IRuntimeOptions;
|
|
34
|
+
private initialized: Promise<void>;
|
|
35
|
+
private pending = new Map<string, { resolve: (result: IExecuteResult) => void; reject: (error: Error) => void }>();
|
|
36
|
+
private messageId = 0;
|
|
37
|
+
private changeListener: ((path: string, content: string) => void) | null = null;
|
|
38
|
+
private deleteListener: ((path: string) => void) | null = null;
|
|
39
|
+
private messageHandler: ((event: MessageEvent) => void) | null = null;
|
|
40
|
+
|
|
41
|
+
constructor(sandboxUrl: string, vfs: VirtualFS, options: IRuntimeOptions = {}) {
|
|
42
|
+
this.sandboxOrigin = new URL(sandboxUrl).origin;
|
|
43
|
+
this.vfs = vfs;
|
|
44
|
+
this.options = options;
|
|
45
|
+
|
|
46
|
+
// Create hidden iframe
|
|
47
|
+
// NOTE: Security comes from the DIFFERENT ORIGIN (e.g., localhost:3002 vs localhost:5173)
|
|
48
|
+
// The browser's same-origin policy prevents the sandbox from accessing the parent's
|
|
49
|
+
// cookies, localStorage, sessionStorage, and IndexedDB.
|
|
50
|
+
// We use the 'credentialless' attribute for compatibility with pages that have
|
|
51
|
+
// Cross-Origin-Embedder-Policy set (like Vite dev server).
|
|
52
|
+
this.iframe = document.createElement('iframe');
|
|
53
|
+
this.iframe.src = sandboxUrl;
|
|
54
|
+
this.iframe.style.display = 'none';
|
|
55
|
+
// @ts-expect-error - credentialless is a newer attribute not in all TypeScript definitions
|
|
56
|
+
this.iframe.credentialless = true;
|
|
57
|
+
this.iframe.setAttribute('credentialless', '');
|
|
58
|
+
document.body.appendChild(this.iframe);
|
|
59
|
+
|
|
60
|
+
// Set up message handler
|
|
61
|
+
this.setupMessageHandler();
|
|
62
|
+
|
|
63
|
+
// Wait for iframe to be ready, then initialize
|
|
64
|
+
this.initialized = this.waitForReady().then(() => this.initSandbox());
|
|
65
|
+
|
|
66
|
+
// Set up VFS change listeners
|
|
67
|
+
this.setupVFSListeners();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set up the message event handler
|
|
72
|
+
*/
|
|
73
|
+
private setupMessageHandler(): void {
|
|
74
|
+
this.messageHandler = (event: MessageEvent) => {
|
|
75
|
+
// Only accept messages from our sandbox origin
|
|
76
|
+
if (event.origin !== this.sandboxOrigin) return;
|
|
77
|
+
|
|
78
|
+
const message = event.data as SandboxMessage;
|
|
79
|
+
|
|
80
|
+
if (message.type === 'result' && message.id) {
|
|
81
|
+
const pending = this.pending.get(message.id);
|
|
82
|
+
if (pending && message.result) {
|
|
83
|
+
pending.resolve(message.result);
|
|
84
|
+
this.pending.delete(message.id);
|
|
85
|
+
}
|
|
86
|
+
} else if (message.type === 'error' && message.id) {
|
|
87
|
+
const pending = this.pending.get(message.id);
|
|
88
|
+
if (pending) {
|
|
89
|
+
pending.reject(new Error(message.error || 'Unknown sandbox error'));
|
|
90
|
+
this.pending.delete(message.id);
|
|
91
|
+
}
|
|
92
|
+
} else if (message.type === 'console' && this.options.onConsole) {
|
|
93
|
+
this.options.onConsole(message.consoleMethod || 'log', message.consoleArgs || []);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
window.addEventListener('message', this.messageHandler);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Wait for the sandbox iframe to signal it's ready
|
|
102
|
+
*/
|
|
103
|
+
private waitForReady(): Promise<void> {
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
const handler = (event: MessageEvent) => {
|
|
106
|
+
if (event.origin !== this.sandboxOrigin) return;
|
|
107
|
+
const message = event.data as SandboxMessage;
|
|
108
|
+
if (message.type === 'ready') {
|
|
109
|
+
window.removeEventListener('message', handler);
|
|
110
|
+
resolve();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
window.addEventListener('message', handler);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Initialize the sandbox with VFS snapshot and options
|
|
119
|
+
*/
|
|
120
|
+
private async initSandbox(): Promise<void> {
|
|
121
|
+
const snapshot = this.vfs.toSnapshot();
|
|
122
|
+
|
|
123
|
+
const message: SandboxMessage = {
|
|
124
|
+
type: 'init',
|
|
125
|
+
vfsSnapshot: snapshot,
|
|
126
|
+
options: {
|
|
127
|
+
cwd: this.options.cwd,
|
|
128
|
+
env: this.options.env,
|
|
129
|
+
// Note: onConsole callback can't be sent cross-origin,
|
|
130
|
+
// sandbox will send console messages back via postMessage
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
135
|
+
console.log('[SandboxRuntime] Sandbox initialized');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Set up listeners for VFS changes to sync to sandbox
|
|
140
|
+
*/
|
|
141
|
+
private setupVFSListeners(): void {
|
|
142
|
+
this.changeListener = (path: string, content: string) => {
|
|
143
|
+
const message: SandboxMessage = {
|
|
144
|
+
type: 'syncFile',
|
|
145
|
+
path,
|
|
146
|
+
content,
|
|
147
|
+
};
|
|
148
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
149
|
+
};
|
|
150
|
+
this.vfs.on('change', this.changeListener);
|
|
151
|
+
|
|
152
|
+
this.deleteListener = (path: string) => {
|
|
153
|
+
const message: SandboxMessage = {
|
|
154
|
+
type: 'syncFile',
|
|
155
|
+
path,
|
|
156
|
+
content: null,
|
|
157
|
+
};
|
|
158
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
159
|
+
};
|
|
160
|
+
this.vfs.on('delete', this.deleteListener);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Send a message and wait for response
|
|
165
|
+
*/
|
|
166
|
+
private sendAndWait(message: SandboxMessage): Promise<IExecuteResult> {
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
const id = String(this.messageId++);
|
|
169
|
+
this.pending.set(id, { resolve, reject });
|
|
170
|
+
|
|
171
|
+
this.iframe.contentWindow?.postMessage(
|
|
172
|
+
{ ...message, id },
|
|
173
|
+
this.sandboxOrigin
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Timeout after 60 seconds
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
if (this.pending.has(id)) {
|
|
179
|
+
this.pending.delete(id);
|
|
180
|
+
reject(new Error('Sandbox execution timeout'));
|
|
181
|
+
}
|
|
182
|
+
}, 60000);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Execute code in the sandbox
|
|
188
|
+
*/
|
|
189
|
+
async execute(code: string, filename?: string): Promise<IExecuteResult> {
|
|
190
|
+
await this.initialized;
|
|
191
|
+
return this.sendAndWait({
|
|
192
|
+
type: 'execute',
|
|
193
|
+
code,
|
|
194
|
+
filename,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Run a file from the VFS in the sandbox
|
|
200
|
+
*/
|
|
201
|
+
async runFile(filename: string): Promise<IExecuteResult> {
|
|
202
|
+
await this.initialized;
|
|
203
|
+
return this.sendAndWait({
|
|
204
|
+
type: 'runFile',
|
|
205
|
+
filename,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Clear the module cache in the sandbox
|
|
211
|
+
*/
|
|
212
|
+
clearCache(): void {
|
|
213
|
+
const message: SandboxMessage = { type: 'clearCache' };
|
|
214
|
+
this.iframe.contentWindow?.postMessage(message, this.sandboxOrigin);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get the VFS (main thread instance)
|
|
219
|
+
*/
|
|
220
|
+
getVFS(): VirtualFS {
|
|
221
|
+
return this.vfs;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Terminate the sandbox
|
|
226
|
+
*/
|
|
227
|
+
terminate(): void {
|
|
228
|
+
// Remove VFS listeners
|
|
229
|
+
if (this.changeListener) {
|
|
230
|
+
this.vfs.off('change', this.changeListener);
|
|
231
|
+
}
|
|
232
|
+
if (this.deleteListener) {
|
|
233
|
+
this.vfs.off('delete', this.deleteListener);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Remove message handler
|
|
237
|
+
if (this.messageHandler) {
|
|
238
|
+
window.removeEventListener('message', this.messageHandler);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Remove iframe
|
|
242
|
+
this.iframe.remove();
|
|
243
|
+
|
|
244
|
+
// Reject any pending promises
|
|
245
|
+
for (const [id, { reject }] of this.pending) {
|
|
246
|
+
reject(new Error('Sandbox terminated'));
|
|
247
|
+
this.pending.delete(id);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log('[SandboxRuntime] Sandbox terminated');
|
|
251
|
+
}
|
|
252
|
+
}
|