diff --git a/mobile/app/(tabs)/import.tsx b/mobile/app/(tabs)/import.tsx
index 0c6458e..beb99dc 100644
--- a/mobile/app/(tabs)/import.tsx
+++ b/mobile/app/(tabs)/import.tsx
@@ -91,14 +91,17 @@ export default function ImportScreen() {
encoding: FileSystem.EncodingType.Base64,
});
- // Wheel URL: prefer the configured instance; fall back to bincio.org.
+ // Fetch the bincio wheel here (React Native networking), not inside the
+ // WebView. WKWebView blocks HTTP requests via ATS; RN native networking
+ // allows local-network HTTP (NSAllowsLocalNetworking=true in Info.plist).
const instanceUrl = await getInstanceUrl(dbCtx);
- const wheelUrl = await resolveWheelUrl(instanceUrl);
+ setState({ status: 'loading', msg: 'Fetching Bincio engine…' });
+ const wheelBase64 = await fetchWheelBase64(instanceUrl);
const result = await extractFile(
name,
base64,
- wheelUrl,
+ wheelBase64,
(msg) => setState({ status: 'loading', msg }),
);
@@ -146,11 +149,9 @@ export default function ImportScreen() {
{state.status === 'loading' && (
{state.msg}
- {state.msg.startsWith('Load') && (
-
- First run downloads ~35 MB of the Python runtime. Subsequent runs are instant.
-
- )}
+
+ First run downloads ~35 MB (Python runtime + packages). Subsequent runs are instant.
+
)}
@@ -204,17 +205,31 @@ async function getInstanceUrl(db: ReturnType): Promise<
return (row?.value ?? '').replace(/\/$/, '');
}
-async function resolveWheelUrl(instanceUrl: string): Promise {
+// In-memory cache so repeated imports in one session don't re-download the wheel.
+let _cachedWheelBase64: string | null = null;
+
+async function fetchWheelBase64(instanceUrl: string): Promise {
+ if (_cachedWheelBase64) return _cachedWheelBase64;
+
const base = instanceUrl || 'https://bincio.org';
+
+ // Ask the instance for the canonical wheel URL (handles both dev and prod layouts).
+ let wheelUrl = `${base}/api/wheel/download`;
try {
- const resp = await fetch(`${base}/api/wheel/version`, { signal: AbortSignal.timeout(5000) });
- if (resp.ok) {
- const d = await resp.json() as { api_url?: string; url?: string };
+ const vr = await fetch(`${base}/api/wheel/version`, { signal: AbortSignal.timeout(5000) });
+ if (vr.ok) {
+ const d = await vr.json() as { api_url?: string; url?: string };
const path = d.api_url ?? d.url ?? '/api/wheel/download';
- return path.startsWith('http') ? path : `${base}${path}`;
+ wheelUrl = path.startsWith('http') ? path : `${base}${path}`;
}
} catch {}
- return `${base}/api/wheel/download`;
+
+ // Fetch via React Native networking (supports local HTTP; WKWebView would block it).
+ const resp = await fetch(wheelUrl);
+ if (!resp.ok) throw new Error(`Could not download Bincio engine (${resp.status}). Is the instance running?`);
+ const buf = await resp.arrayBuffer();
+ _cachedWheelBase64 = Buffer.from(buf).toString('base64');
+ return _cachedWheelBase64;
}
// ── Styles ───────────────────────────────────────────────────────────────────
diff --git a/mobile/extraction/PyodideWebView.tsx b/mobile/extraction/PyodideWebView.tsx
index f430e06..222ef8b 100644
--- a/mobile/extraction/PyodideWebView.tsx
+++ b/mobile/extraction/PyodideWebView.tsx
@@ -87,10 +87,10 @@ var initError = null;
})();
window._bincioExtract = async function(params) {
- var reqId = params.reqId;
- var filename = params.filename;
- var base64 = params.base64;
- var wheelUrl = params.wheelUrl;
+ var reqId = params.reqId;
+ var filename = params.filename;
+ var base64 = params.base64;
+ var wheelBase64 = params.wheelBase64; // pre-fetched by React Native (avoids ATS/HTTP issues)
function post(m) { _post(Object.assign({}, m, { reqId: reqId })); }
@@ -108,13 +108,14 @@ window._bincioExtract = async function(params) {
}
if (initError) throw new Error(initError);
- // Install bincio wheel on first extraction (lazy: keeps startup fast)
+ // Install bincio wheel on first extraction.
+ // Wheel bytes arrive pre-fetched from the React Native side as base64,
+ // so the WebView never needs to make an HTTP request (avoids ATS blocks).
if (!wheelReady) {
post({ type: 'progress', msg: 'Loading Bincio…' });
- var resp = await fetch(wheelUrl);
- if (!resp.ok) throw new Error('Failed to fetch Bincio wheel (' + resp.status + ')');
- var wheelBlob = new Blob([await resp.arrayBuffer()]);
- var blobUrl = URL.createObjectURL(wheelBlob);
+ var wheelBytes = Uint8Array.from(atob(wheelBase64), function(c) { return c.charCodeAt(0); });
+ var wheelBlob = new Blob([wheelBytes]);
+ var blobUrl = URL.createObjectURL(wheelBlob);
pyodide.globals.set('_blobUrl', blobUrl);
await pyodide.runPythonAsync(_PY_INSTALL_WHEEL);
URL.revokeObjectURL(blobUrl);
@@ -158,7 +159,7 @@ export function PyodideWebView() {
return (
void = () => {},
): Promise {
if (isExtracting) return Promise.reject(new Error('Another extraction is already in progress'));
@@ -67,7 +69,7 @@ export function extractFile(
isExtracting = true;
const reqId = String(++reqCounter);
- const args = JSON.stringify({ reqId, filename, base64, wheelUrl });
+ const args = JSON.stringify({ reqId, filename, base64, wheelBase64 });
return new Promise((resolve, reject) => {
pending.set(reqId, {