Skip to content

fix(android): actually download remote http(s) jsBundleSource#43

Merged
CAMOBAP merged 5 commits into
mainfrom
fix/android-remote-http-bundle
Jul 25, 2026
Merged

fix(android): actually download remote http(s) jsBundleSource#43
CAMOBAP merged 5 commits into
mainfrom
fix/android-remote-http-bundle

Conversation

@CAMOBAP

@CAMOBAP CAMOBAP commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Supersedes #37 — incorporates all of @akelmanson's work with review feedback applied.

Summary

Loading a sandbox from a remote URL (jsBundleSource="https://…/x.bundle") did not work on Android. Two issues compounded:

  1. The URL was never downloaded. createBundleLoader mapped an http(s) source to JSBundleLoader.createFileLoader(url), which treats its argument as a local file path — a remote URL never resolves.
  2. Dev support hijacked the load. In debug builds, ReactHostImpl with dev support enabled ignores jsBundleLoader and fetches through Metro, mangling the URL into http://localhost:8081/http://host/x.bundle.bundle → 404.

Fix

  • Prefetch the remote bundle to a cache file on a worker thread (avoids NetworkOnMainThreadException), then load it via JSBundleLoader.createFileLoader(cacheFile, sourceUrl, true) — synchronous load required on RN 0.85 bridgeless to sequence after TurboModule JSI bindings
  • Disable developer support for remote http(s) sources so Metro doesn't intercept the load
  • Extract download logic into downloadRemoteBundle() private function
  • Cache is keyed by URL hash — same URL reuses the cached copy; change the URL to force a re-fetch

Changes from #37

  • Extracted download block into downloadRemoteBundle() (requested by @CAMOBAP)
  • Shortened both inline comments to 2 lines (requested by @CAMOBAP)

Co-authored-by: akelmanson akelmanson@users.noreply.github.com

🤖 Generated with Claude Code

André Kelmanson and others added 4 commits June 15, 2026 11:26
Loading a sandbox from a remote URL never worked on Android:

- `createBundleLoader` mapped an http(s) source to
  `JSBundleLoader.createFileLoader(url)`, but that loader calls
  `loadScriptFromFile`, which treats its argument as a local path — a
  remote URL is never downloaded.
- Even with a working loader, the sandbox ReactHost was created with
  developer support enabled, so in dev builds the runtime ignored the
  loader and fetched the bundle from Metro using `jsMainModulePath`,
  turning `http://host/x.bundle` into
  `http://localhost:8081/http://host/x.bundle.bundle` (a 404).

Fix:

- Prefetch the remote bundle to a cache file off the main thread
  (avoids NetworkOnMainThreadException) and load it via
  `createCachedBundleFromNetworkLoader`, preserving the source URL for
  stack traces. Returns null (existing failure path) on download error.
- Disable developer support for remote (http/https) sources only, so
  the runtime uses our loader instead of Metro. Local asset/name
  sources keep dev support and Fast Refresh.

Verified on a device: host loads the sandbox bundle from a remote HTTP
URL in a debug build, renders, and exchanges messages both ways.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The remote (http/https) loader downloaded the bundle on every sandbox VM
creation, so each cold start re-fetched the full bundle and failed when
offline.

Treat a remote URL as immutable: if the cached file for that exact URL
already exists, reuse it and skip the network. Repeated loads (cold
starts) become instant and work offline; a partial/failed download is
deleted so a later load retries cleanly. To publish an update, change the
URL (version segment or `?v=` query) — a new URL misses the cache and is
fetched once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
createCachedBundleFromNetworkLoader loads the cached file with
loadSynchronously=false. On RN 0.85 bridgeless this executes the bundle
before the TurboModule JSI bindings are installed on the runtime, so the
bundle's InitializeCore hits TurboModuleRegistry.getEnforcing('PlatformConstants')
with no proxy registered and the sandbox VM aborts. Because this happens
before the sandbox error handler is installed, it escalates to a native
fatal and takes the host process down with it.

Load the already-downloaded cache file synchronously via
JSBundleLoader.createFileLoader(path, sourceURL, true) — mirroring how the
built-in asset loader runs the main bundle — so the runtime is ready before
the bundle runs. The original URL is kept as sourceURL for stack traces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Extract remote bundle download logic into a private downloadRemoteBundle()
  function for clarity
- Shorten the two inline comments in createBundleLoader to 2 lines each

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CAMOBAP
CAMOBAP merged commit 738b7ae into main Jul 25, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant