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.
Files changed (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +731 -0
  3. package/dist/__sw__.js +394 -0
  4. package/dist/ai-chatbot-demo-entry.d.ts +6 -0
  5. package/dist/ai-chatbot-demo-entry.d.ts.map +1 -0
  6. package/dist/ai-chatbot-demo.d.ts +42 -0
  7. package/dist/ai-chatbot-demo.d.ts.map +1 -0
  8. package/dist/assets/runtime-worker-D9x_Ddwz.js +60543 -0
  9. package/dist/assets/runtime-worker-D9x_Ddwz.js.map +1 -0
  10. package/dist/convex-app-demo-entry.d.ts +6 -0
  11. package/dist/convex-app-demo-entry.d.ts.map +1 -0
  12. package/dist/convex-app-demo.d.ts +68 -0
  13. package/dist/convex-app-demo.d.ts.map +1 -0
  14. package/dist/cors-proxy.d.ts +46 -0
  15. package/dist/cors-proxy.d.ts.map +1 -0
  16. package/dist/create-runtime.d.ts +42 -0
  17. package/dist/create-runtime.d.ts.map +1 -0
  18. package/dist/demo.d.ts +6 -0
  19. package/dist/demo.d.ts.map +1 -0
  20. package/dist/dev-server.d.ts +97 -0
  21. package/dist/dev-server.d.ts.map +1 -0
  22. package/dist/frameworks/next-dev-server.d.ts +202 -0
  23. package/dist/frameworks/next-dev-server.d.ts.map +1 -0
  24. package/dist/frameworks/vite-dev-server.d.ts +85 -0
  25. package/dist/frameworks/vite-dev-server.d.ts.map +1 -0
  26. package/dist/index.cjs +14965 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.ts +71 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.mjs +14867 -0
  31. package/dist/index.mjs.map +1 -0
  32. package/dist/next-demo.d.ts +49 -0
  33. package/dist/next-demo.d.ts.map +1 -0
  34. package/dist/npm/index.d.ts +71 -0
  35. package/dist/npm/index.d.ts.map +1 -0
  36. package/dist/npm/registry.d.ts +66 -0
  37. package/dist/npm/registry.d.ts.map +1 -0
  38. package/dist/npm/resolver.d.ts +52 -0
  39. package/dist/npm/resolver.d.ts.map +1 -0
  40. package/dist/npm/tarball.d.ts +29 -0
  41. package/dist/npm/tarball.d.ts.map +1 -0
  42. package/dist/runtime-interface.d.ts +90 -0
  43. package/dist/runtime-interface.d.ts.map +1 -0
  44. package/dist/runtime.d.ts +103 -0
  45. package/dist/runtime.d.ts.map +1 -0
  46. package/dist/sandbox-helpers.d.ts +43 -0
  47. package/dist/sandbox-helpers.d.ts.map +1 -0
  48. package/dist/sandbox-runtime.d.ts +65 -0
  49. package/dist/sandbox-runtime.d.ts.map +1 -0
  50. package/dist/server-bridge.d.ts +89 -0
  51. package/dist/server-bridge.d.ts.map +1 -0
  52. package/dist/shims/assert.d.ts +51 -0
  53. package/dist/shims/assert.d.ts.map +1 -0
  54. package/dist/shims/async_hooks.d.ts +37 -0
  55. package/dist/shims/async_hooks.d.ts.map +1 -0
  56. package/dist/shims/buffer.d.ts +20 -0
  57. package/dist/shims/buffer.d.ts.map +1 -0
  58. package/dist/shims/child_process-browser.d.ts +92 -0
  59. package/dist/shims/child_process-browser.d.ts.map +1 -0
  60. package/dist/shims/child_process.d.ts +93 -0
  61. package/dist/shims/child_process.d.ts.map +1 -0
  62. package/dist/shims/chokidar.d.ts +55 -0
  63. package/dist/shims/chokidar.d.ts.map +1 -0
  64. package/dist/shims/cluster.d.ts +52 -0
  65. package/dist/shims/cluster.d.ts.map +1 -0
  66. package/dist/shims/crypto.d.ts +122 -0
  67. package/dist/shims/crypto.d.ts.map +1 -0
  68. package/dist/shims/dgram.d.ts +34 -0
  69. package/dist/shims/dgram.d.ts.map +1 -0
  70. package/dist/shims/diagnostics_channel.d.ts +80 -0
  71. package/dist/shims/diagnostics_channel.d.ts.map +1 -0
  72. package/dist/shims/dns.d.ts +87 -0
  73. package/dist/shims/dns.d.ts.map +1 -0
  74. package/dist/shims/domain.d.ts +25 -0
  75. package/dist/shims/domain.d.ts.map +1 -0
  76. package/dist/shims/esbuild.d.ts +105 -0
  77. package/dist/shims/esbuild.d.ts.map +1 -0
  78. package/dist/shims/events.d.ts +37 -0
  79. package/dist/shims/events.d.ts.map +1 -0
  80. package/dist/shims/fs.d.ts +115 -0
  81. package/dist/shims/fs.d.ts.map +1 -0
  82. package/dist/shims/fsevents.d.ts +67 -0
  83. package/dist/shims/fsevents.d.ts.map +1 -0
  84. package/dist/shims/http.d.ts +217 -0
  85. package/dist/shims/http.d.ts.map +1 -0
  86. package/dist/shims/http2.d.ts +81 -0
  87. package/dist/shims/http2.d.ts.map +1 -0
  88. package/dist/shims/https.d.ts +36 -0
  89. package/dist/shims/https.d.ts.map +1 -0
  90. package/dist/shims/inspector.d.ts +25 -0
  91. package/dist/shims/inspector.d.ts.map +1 -0
  92. package/dist/shims/module.d.ts +22 -0
  93. package/dist/shims/module.d.ts.map +1 -0
  94. package/dist/shims/net.d.ts +100 -0
  95. package/dist/shims/net.d.ts.map +1 -0
  96. package/dist/shims/os.d.ts +159 -0
  97. package/dist/shims/os.d.ts.map +1 -0
  98. package/dist/shims/path.d.ts +72 -0
  99. package/dist/shims/path.d.ts.map +1 -0
  100. package/dist/shims/perf_hooks.d.ts +50 -0
  101. package/dist/shims/perf_hooks.d.ts.map +1 -0
  102. package/dist/shims/process.d.ts +93 -0
  103. package/dist/shims/process.d.ts.map +1 -0
  104. package/dist/shims/querystring.d.ts +23 -0
  105. package/dist/shims/querystring.d.ts.map +1 -0
  106. package/dist/shims/readdirp.d.ts +52 -0
  107. package/dist/shims/readdirp.d.ts.map +1 -0
  108. package/dist/shims/readline.d.ts +62 -0
  109. package/dist/shims/readline.d.ts.map +1 -0
  110. package/dist/shims/rollup.d.ts +34 -0
  111. package/dist/shims/rollup.d.ts.map +1 -0
  112. package/dist/shims/sentry.d.ts +163 -0
  113. package/dist/shims/sentry.d.ts.map +1 -0
  114. package/dist/shims/stream.d.ts +181 -0
  115. package/dist/shims/stream.d.ts.map +1 -0
  116. package/dist/shims/tls.d.ts +53 -0
  117. package/dist/shims/tls.d.ts.map +1 -0
  118. package/dist/shims/tty.d.ts +30 -0
  119. package/dist/shims/tty.d.ts.map +1 -0
  120. package/dist/shims/url.d.ts +64 -0
  121. package/dist/shims/url.d.ts.map +1 -0
  122. package/dist/shims/util.d.ts +106 -0
  123. package/dist/shims/util.d.ts.map +1 -0
  124. package/dist/shims/v8.d.ts +73 -0
  125. package/dist/shims/v8.d.ts.map +1 -0
  126. package/dist/shims/vfs-adapter.d.ts +126 -0
  127. package/dist/shims/vfs-adapter.d.ts.map +1 -0
  128. package/dist/shims/vm.d.ts +45 -0
  129. package/dist/shims/vm.d.ts.map +1 -0
  130. package/dist/shims/worker_threads.d.ts +66 -0
  131. package/dist/shims/worker_threads.d.ts.map +1 -0
  132. package/dist/shims/ws.d.ts +66 -0
  133. package/dist/shims/ws.d.ts.map +1 -0
  134. package/dist/shims/zlib.d.ts +161 -0
  135. package/dist/shims/zlib.d.ts.map +1 -0
  136. package/dist/transform.d.ts +24 -0
  137. package/dist/transform.d.ts.map +1 -0
  138. package/dist/virtual-fs.d.ts +226 -0
  139. package/dist/virtual-fs.d.ts.map +1 -0
  140. package/dist/vite-demo.d.ts +35 -0
  141. package/dist/vite-demo.d.ts.map +1 -0
  142. package/dist/vite-sw.js +132 -0
  143. package/dist/worker/runtime-worker.d.ts +8 -0
  144. package/dist/worker/runtime-worker.d.ts.map +1 -0
  145. package/dist/worker-runtime.d.ts +50 -0
  146. package/dist/worker-runtime.d.ts.map +1 -0
  147. package/package.json +85 -0
  148. package/src/ai-chatbot-demo-entry.ts +244 -0
  149. package/src/ai-chatbot-demo.ts +509 -0
  150. package/src/convex-app-demo-entry.ts +1107 -0
  151. package/src/convex-app-demo.ts +1316 -0
  152. package/src/cors-proxy.ts +81 -0
  153. package/src/create-runtime.ts +147 -0
  154. package/src/demo.ts +304 -0
  155. package/src/dev-server.ts +274 -0
  156. package/src/frameworks/next-dev-server.ts +2224 -0
  157. package/src/frameworks/vite-dev-server.ts +702 -0
  158. package/src/index.ts +101 -0
  159. package/src/next-demo.ts +1784 -0
  160. package/src/npm/index.ts +347 -0
  161. package/src/npm/registry.ts +152 -0
  162. package/src/npm/resolver.ts +385 -0
  163. package/src/npm/tarball.ts +209 -0
  164. package/src/runtime-interface.ts +103 -0
  165. package/src/runtime.ts +1046 -0
  166. package/src/sandbox-helpers.ts +173 -0
  167. package/src/sandbox-runtime.ts +252 -0
  168. package/src/server-bridge.ts +426 -0
  169. package/src/shims/assert.ts +664 -0
  170. package/src/shims/async_hooks.ts +86 -0
  171. package/src/shims/buffer.ts +75 -0
  172. package/src/shims/child_process-browser.ts +217 -0
  173. package/src/shims/child_process.ts +463 -0
  174. package/src/shims/chokidar.ts +313 -0
  175. package/src/shims/cluster.ts +67 -0
  176. package/src/shims/crypto.ts +830 -0
  177. package/src/shims/dgram.ts +47 -0
  178. package/src/shims/diagnostics_channel.ts +196 -0
  179. package/src/shims/dns.ts +172 -0
  180. package/src/shims/domain.ts +58 -0
  181. package/src/shims/esbuild.ts +805 -0
  182. package/src/shims/events.ts +195 -0
  183. package/src/shims/fs.ts +803 -0
  184. package/src/shims/fsevents.ts +63 -0
  185. package/src/shims/http.ts +904 -0
  186. package/src/shims/http2.ts +96 -0
  187. package/src/shims/https.ts +86 -0
  188. package/src/shims/inspector.ts +30 -0
  189. package/src/shims/module.ts +82 -0
  190. package/src/shims/net.ts +359 -0
  191. package/src/shims/os.ts +195 -0
  192. package/src/shims/path.ts +199 -0
  193. package/src/shims/perf_hooks.ts +92 -0
  194. package/src/shims/process.ts +346 -0
  195. package/src/shims/querystring.ts +97 -0
  196. package/src/shims/readdirp.ts +228 -0
  197. package/src/shims/readline.ts +110 -0
  198. package/src/shims/rollup.ts +80 -0
  199. package/src/shims/sentry.ts +133 -0
  200. package/src/shims/stream.ts +1126 -0
  201. package/src/shims/tls.ts +95 -0
  202. package/src/shims/tty.ts +64 -0
  203. package/src/shims/url.ts +171 -0
  204. package/src/shims/util.ts +312 -0
  205. package/src/shims/v8.ts +113 -0
  206. package/src/shims/vfs-adapter.ts +402 -0
  207. package/src/shims/vm.ts +83 -0
  208. package/src/shims/worker_threads.ts +111 -0
  209. package/src/shims/ws.ts +382 -0
  210. package/src/shims/zlib.ts +289 -0
  211. package/src/transform.ts +313 -0
  212. package/src/types/external.d.ts +67 -0
  213. package/src/virtual-fs.ts +903 -0
  214. package/src/vite-demo.ts +577 -0
  215. package/src/worker/runtime-worker.ts +128 -0
  216. 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
+ }