/** * C-03 Pyodide 懒加载运行器(浏览器内 WASM,无服务端执行)。 * 安全约束: * - 默认断网:不 loadPackage、不拉取额外包;只运行解释器自带能力。 * - 超时由调用方(CodeRunner.tsx)经 Promise.race + runId 丢弃过期结果保证; * Pyodide 本体不支持强制 terminate,超时后输出"执行超时"并忽略迟到结果。 */ export const PYODIDE_VERSION = "0.26.4"; export const PYODIDE_CDN = `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`; type PyodideRuntime = { runPython: (code: string) => unknown; setStdout: (opts: { batched: (s: string) => void }) => void; setStderr: (opts: { batched: (s: string) => void }) => void; }; declare global { interface Window { __pyodidePromise?: Promise | null; } } function loadPyodideScript(): Promise { return new Promise((resolve, reject) => { if (document.querySelector('script[data-pyodide="1"]')) { resolve(); return; } const s = document.createElement("script"); s.dataset.pyodide = "1"; s.src = `${PYODIDE_CDN}pyodide.js`; s.onload = () => resolve(); s.onerror = () => reject(new Error("Pyodide 运行时加载失败,请检查网络后重试")); document.head.appendChild(s); }); } async function bootPyodide( onStdout: (s: string) => void, onStderr: (s: string) => void, ): Promise { await loadPyodideScript(); const factory = (window as unknown as { loadPyodide?: (opts: { indexURL: string }) => Promise }).loadPyodide; if (!factory) throw new Error("Pyodide 运行时初始化失败"); const py = await factory({ indexURL: PYODIDE_CDN }); py.setStdout({ batched: onStdout }); py.setStderr({ batched: onStderr }); return py; } export function getPyodide( onStdout: (s: string) => void, onStderr: (s: string) => void, ): Promise { if (!window.__pyodidePromise) { window.__pyodidePromise = bootPyodide(onStdout, onStderr).catch((e) => { window.__pyodidePromise = null; throw e; }); } return window.__pyodidePromise; }