/** * JavaScript/TypeScript 沙箱执行引擎 * 在隔离的 iframe 中安全执行代码 */ import type { IExecutionEngine, ExecutionResult } from '../types'; // 执行超时时间(毫秒) const EXECUTION_TIMEOUT = 10000; // 最大输出长度 const MAX_OUTPUT_LENGTH = 50000; export class SandboxEngine implements IExecutionEngine { private iframe: HTMLIFrameElement | null = null; private abortController: AbortController | null = null; supports(language: string): boolean { const lang = language.toLowerCase(); return ['javascript', 'js', 'typescript', 'ts'].includes(lang); } async execute(code: string, language: string): Promise { const startTime = performance.now(); const lang = language.toLowerCase(); try { // TypeScript 需要先编译 let executableCode = code; if (lang === 'typescript' || lang === 'ts') { executableCode = this.transpileTypeScript(code); } // 在沙箱中执行 const output = await this.executeInSandbox(executableCode); const executionTime = Math.round(performance.now() - startTime); return { success: true, output: output.slice(0, MAX_OUTPUT_LENGTH), executionTime, engine: 'sandbox', }; } catch (error) { const executionTime = Math.round(performance.now() - startTime); return { success: false, output: '', error: error instanceof Error ? error.message : String(error), executionTime, engine: 'sandbox', }; } } stop(): void { if (this.abortController) { this.abortController.abort(); } this.cleanup(); } private transpileTypeScript(code: string): string { // 简单的 TypeScript 转换(移除类型注解) // 实际项目中可以使用 @babel/standalone 或 typescript return code // 移除类型注解 .replace(/:\s*\w+(\[\])?(\s*[=,)])/g, '$2') // 移除接口定义 .replace(/interface\s+\w+\s*\{[^}]*\}/g, '') // 移除类型别名 .replace(/type\s+\w+\s*=\s*[^;]+;/g, '') // 移除泛型 .replace(/<[^>]+>/g, '') // 移除 as 类型断言 .replace(/\s+as\s+\w+/g, '') // 移除 ! 非空断言 .replace(/!\./g, '.') // 移除可选链前的类型 .replace(/\?\./g, '?.'); } private executeInSandbox(code: string): Promise { return new Promise((resolve, reject) => { this.abortController = new AbortController(); const outputs: string[] = []; // 创建隔离的 iframe this.iframe = document.createElement('iframe'); this.iframe.style.display = 'none'; this.iframe.sandbox.add('allow-scripts'); document.body.appendChild(this.iframe); const iframeWindow = this.iframe.contentWindow; if (!iframeWindow) { this.cleanup(); reject(new Error('无法创建执行沙箱')); return; } // 设置超时 const timeoutId = setTimeout(() => { this.cleanup(); reject(new Error(`执行超时(${EXECUTION_TIMEOUT / 1000}秒)`)); }, EXECUTION_TIMEOUT); // 监听消息 const messageHandler = (event: MessageEvent) => { if (event.source !== iframeWindow) return; const { type, data } = event.data; if (type === 'console') { outputs.push(data); } else if (type === 'done') { clearTimeout(timeoutId); window.removeEventListener('message', messageHandler); this.cleanup(); resolve(outputs.join('\n')); } else if (type === 'error') { clearTimeout(timeoutId); window.removeEventListener('message', messageHandler); this.cleanup(); reject(new Error(data)); } }; window.addEventListener('message', messageHandler); // 中止处理 this.abortController.signal.addEventListener('abort', () => { clearTimeout(timeoutId); window.removeEventListener('message', messageHandler); this.cleanup(); reject(new Error('执行已取消')); }); // 在 iframe 中执行代码 const sandboxCode = ` (function() { const outputs = []; // 重写 console 方法 const originalConsole = console; const customConsole = { log: (...args) => { const msg = args.map(a => { if (typeof a === 'object') { try { return JSON.stringify(a, null, 2); } catch { return String(a); } } return String(a); }).join(' '); parent.postMessage({ type: 'console', data: msg }, '*'); }, error: (...args) => customConsole.log('[Error]', ...args), warn: (...args) => customConsole.log('[Warn]', ...args), info: (...args) => customConsole.log(...args), debug: (...args) => customConsole.log('[Debug]', ...args), table: (data) => customConsole.log(JSON.stringify(data, null, 2)), clear: () => {}, dir: (obj) => customConsole.log(obj), time: () => {}, timeEnd: () => {}, group: () => {}, groupEnd: () => {}, }; // 替换全局 console window.console = customConsole; // 限制危险 API delete window.fetch; delete window.XMLHttpRequest; delete window.WebSocket; delete window.localStorage; delete window.sessionStorage; delete window.indexedDB; delete window.open; delete window.close; delete window.alert; delete window.confirm; delete window.prompt; try { // 执行用户代码 const result = eval(${JSON.stringify(code)}); // 如果有返回值且不是 undefined,输出它 if (result !== undefined) { customConsole.log(result); } parent.postMessage({ type: 'done' }, '*'); } catch (error) { parent.postMessage({ type: 'error', data: error.message || String(error) }, '*'); } })(); `; // 写入并执行 const iframeDoc = this.iframe.contentDocument; if (iframeDoc) { iframeDoc.open(); iframeDoc.write(``); iframeDoc.close(); } }); } private cleanup(): void { if (this.iframe && this.iframe.parentNode) { this.iframe.parentNode.removeChild(this.iframe); } this.iframe = null; this.abortController = null; } } // 导出单例 export const sandboxEngine = new SandboxEngine();