- 实现 sandbox 引擎支持 JavaScript/TypeScript 执行 - 实现 pyodide 引擎支持 Python 浏览器端执行 - 实现 remote 引擎支持 Java/Go/C/C++/Rust 等远程执行 - 添加语言配置和入口点检测逻辑 - 支持执行状态回调和加载进度显示
225 lines
6.7 KiB
TypeScript
225 lines
6.7 KiB
TypeScript
/**
|
||
* 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<ExecutionResult> {
|
||
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<string> {
|
||
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(`<script>${sandboxCode}</script>`);
|
||
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();
|