function requireWorkerUrl(value) { if (typeof value === "string" && value.length > 0) { return value; } if (value instanceof URL) { return value; } throw new Error("workerUrl must be a non-empty string or URL."); } function makeRequestId() { return `python-remap-worker-${Date.now()}-${Math.random().toString(16).slice(2)}`; } export function createLinuxCncPythonRemapBrowserWorkerAdapter({ workerUrl, pythonRuntimeModuleUrl = null, workerFactory = (url) => new Worker(url, { type: "module" }), timeoutMs = 5000, } = {}) { const normalizedWorkerUrl = requireWorkerUrl(workerUrl); const normalizedPythonRuntimeModuleUrl = pythonRuntimeModuleUrl ? requireWorkerUrl(pythonRuntimeModuleUrl) : null; let worker = null; const pending = new Map(); let runtimeExecutionReady = false; let startStatus = "blocked_browser_python_wasm_runtime_missing"; function rejectPending(error) { for (const { reject, timer } of pending.values()) { clearTimeout(timer); reject(error); } pending.clear(); } function request(type, payload = {}) { if (!worker) { return Promise.reject(new Error("Python remap browser worker is not started.")); } const id = makeRequestId(); return new Promise((resolve, reject) => { const timer = setTimeout(() => { pending.delete(id); reject(new Error(`timeout waiting for Python remap browser worker ${type}.`)); }, timeoutMs); pending.set(id, { resolve, reject, timer }); worker.postMessage({ id, type, ...payload }); }); } function handleMessage(event) { const message = event.data ?? {}; const waiter = pending.get(message.id); if (!waiter) { return; } pending.delete(message.id); clearTimeout(waiter.timer); if (message.ok === false) { waiter.reject(new Error(message.error ?? `Python remap browser worker ${message.type ?? "request"} failed.`)); return; } waiter.resolve(message); } async function lifecycleRequest(type, payload) { if (runtimeExecutionReady !== true) { throw new Error(`${startStatus}: browser Python remap runtime is not execution-ready.`); } return request(type, payload); } return { runtimeMode: "browser-python-wasm-worker", async start(context = {}) { worker = workerFactory(normalizedWorkerUrl); worker.addEventListener("message", handleMessage); worker.addEventListener("error", (event) => { rejectPending(new Error(event.message ?? "Python remap browser worker error.")); }); const result = await request("start", { ...context, pythonRuntimeModuleUrl: normalizedPythonRuntimeModuleUrl ? String(normalizedPythonRuntimeModuleUrl) : null, }); runtimeExecutionReady = result.runtimeExecutionReady === true; startStatus = result.status ?? (runtimeExecutionReady ? "browser_python_remap_wasm_runtime_ready" : "blocked_browser_python_wasm_runtime_missing"); return { status: startStatus, runtimeExecutionReady, }; }, async initializePython(payload) { return lifecycleRequest("initializePython", payload); }, async applyIniPythonPath(payload) { return lifecycleRequest("applyIniPythonPath", payload); }, async executeTopLevel(payload) { return lifecycleRequest("executeTopLevel", payload); }, async importModule(payload) { return lifecycleRequest("importModule", payload); }, async lookupCallable(payload) { return lifecycleRequest("lookupCallable", payload); }, async invokeGenerator(payload) { return lifecycleRequest("invokeGenerator", payload); }, async observeFirstYield(payload) { return lifecycleRequest("observeFirstYield", payload); }, async finishGenerator(payload) { return lifecycleRequest("finishGenerator", payload); }, async stageNgcRemapAsset(payload) { return lifecycleRequest("stageNgcRemapAsset", payload); }, async rejectNgcOnlyStandalone(payload) { return lifecycleRequest("rejectNgcOnlyStandalone", payload); }, async exportInterpreterState(payload) { return lifecycleRequest("exportInterpreterState", payload); }, async exportCanonicalEvents(payload) { return lifecycleRequest("exportCanonicalEvents", payload); }, async exportDiagnostics(payload) { return lifecycleRequest("exportDiagnostics", payload); }, async close() { if (worker) { try { await request("close"); } catch { // The worker may be blocked or already gone; termination below is authoritative. } } rejectPending(new Error("Python remap browser worker closed.")); worker?.terminate(); worker = null; }, }; }