fix(mobile): patch pyodide.js at runtime to bypass Chrome 61 import() syntax

Chrome 61 (Karoo WebView) cannot parse dynamic import() — a SyntaxError at
parse time prevents loadPyodide from ever being defined.

Fix: fetch pyodide.js as text, replace every import( with a __loadScript(
shim that uses <script> tag injection, then inject via Blob URL. The Blob
script is never pre-scanned for module syntax so the patch is invisible to
the parser.

Also: expose waitForEngine() from extractActivity so callers can await
engine readiness before batching files — manual scan now shows "Preparing
extraction engine…" instead of flooding with N individual failures.
This commit is contained in:
Davide Scaini
2026-04-26 14:59:28 +02:00
parent a5c2810568
commit 1410be7427
3 changed files with 73 additions and 5 deletions
+34 -3
View File
@@ -2,7 +2,9 @@ import { StyleSheet } from 'react-native';
import WebView from 'react-native-webview';
import { handleWebViewMessage, pyodideRef } from './extractActivity';
const CDN = 'https://cdn.jsdelivr.net/pyodide/v0.26.4/full/';
// v0.18.1: newest version whose pyodide.js is ES2017-compatible (no ??, no ?.)
// Chrome 61 (Karoo WebView) throws SyntaxError on 0.19+ which uses nullish-coalescing.
const CDN = 'https://cdn.jsdelivr.net/pyodide/v0.18.1/full/';
// Python snippets embedded as JSON strings to avoid any JS/TS escaping issues.
const PY_INSTALL_PACKAGES = [
@@ -74,10 +76,39 @@ var initError = null;
(async function init() {
try {
_post({ type: 'progress', msg: 'Loading Python runtime…' });
// Chrome <63 (e.g. Karoo WebView 61) cannot parse the dynamic import()
// syntax that Pyodide uses to load pyodide.asm.js. The parser rejects
// the whole file before it runs, so loadPyodide is never defined.
//
// Fix: fetch pyodide.js as text, replace every "import(" with a
// script-tagbased loader that Chrome 61 can execute, then inject the
// patched code via a Blob URL. The Blob script is never parsed by the
// module-aware pre-scanner, so the import keyword is invisible to it.
window.__loadScript = function(url) {
return new Promise(function(res, rej) {
var s = document.createElement('script');
s.src = url;
s.onload = res;
s.onerror = function() { rej(new Error('Failed to load ' + url)); };
document.head.appendChild(s);
});
};
var pyResp = await fetch(_CDN + 'pyodide.js');
if (!pyResp.ok) throw new Error('Could not fetch pyodide.js (' + pyResp.status + ')');
var pyCode = await pyResp.text();
// Replace all dynamic import() calls. Node.js-only paths (importing
// "path", "fs", etc.) are guarded by IN_NODE and never run in WebView.
pyCode = pyCode.replace(/\bimport\(/g, '__loadScript(');
await new Promise(function(res, rej) {
var blob = new Blob([pyCode], { type: 'application/javascript' });
var blobUrl = URL.createObjectURL(blob);
var s = document.createElement('script');
s.src = _CDN + 'pyodide.js';
s.onload = res; s.onerror = rej;
s.src = blobUrl;
s.onload = function() { URL.revokeObjectURL(blobUrl); res(); };
s.onerror = function() { URL.revokeObjectURL(blobUrl); rej(new Error('Failed to execute patched pyodide.js')); };
document.head.appendChild(s);
});