The ask
A supported way for an embedder to rewrite a data URL immediately before igv fetches it — composed on top of igv's existing built-in rewrites, not replacing them. Something like:
igv.setUrlMapper(url => /* return url, or a rewritten one */)
Unset by default, so an application that never calls it behaves exactly as it does today.
Why
www.encodeproject.org puts AWS WAF in front of its files and answers any Origin not on its allowlist with a CAPTCHA page, under a misleading 405. localhost is never allowlisted, so ENCODE-hosted data cannot be loaded during local development. A background fetch cannot solve a CAPTCHA.
The workable fix is a dev-server middleware that fetches the file from Node with an allowlisted Origin and hands the resulting 307 back to the browser — the bytes still stream directly from S3, so nothing proxies the payload. That requires the client to send the read to the middleware's path instead of the origin URL, which is where the extension point is needed.
Measured against an ENCODE bigWig on 2026-08-03:
| Request |
Result |
Direct from localhost, any Origin |
405 + x-amzn-waf-action: captcha |
Origin: https://aidenlab.org (allowlisted) |
307 → encode-public.s3.amazonaws.com |
| Through the middleware, ranged |
307 → S3 → 206 Partial Content |
Why setCORSProxy doesn't cover it
igv.setCORSProxy exists and looked like the answer, but tryCorsProxy is only reachable from xhr.onerror or xhr.status === 0. ENCODE's challenge response is a 405 carrying valid CORS headers, so onload runs with status 405 and goes straight to handleError. The retry never fires.
It is also a fallback rather than a routing decision — it costs a failed request first, and its URL shape is fixed (<proxy>?url=<encoded>). A pre-fetch mapper expresses "this host is known to need routing" directly.
Precedent
hic-straw added exactly this in v4.0.0 (aidenlab/hic-straw#49) as config.mapUrl, composed after its built-in Dropbox/NCBI rewrites — normalize first, then route. It has worked well: the rewrite is host-scoped, so genuine CORS and permissions problems still surface in development exactly as they would in production, which routing everything through a proxy would hide.
juicebox.js embeds both hic-straw and igv. Its .hic reads now work from localhost against ENCODE; its igv-backed tracks on the same host still fail, because there is no equivalent seam. Context: aidenlab/juicebox.js#450, and the rationale in ADR 0001.
Notes
- A global setter is enough for the dev-mode use case, though a per-
Browser or per-config option would work too.
- The mapper should apply to index/companion reads (
.bai, .tbi, .csi) as well as the primary URL, since a blocked host blocks both.
- Observed on igv.js 3.8.1, where
igvxhr's mapUrl is module-private.
Happy to open a PR if the shape sounds right — I'd want to match whatever composition order you prefer.
The ask
A supported way for an embedder to rewrite a data URL immediately before igv fetches it — composed on top of igv's existing built-in rewrites, not replacing them. Something like:
Unset by default, so an application that never calls it behaves exactly as it does today.
Why
www.encodeproject.orgputs AWS WAF in front of its files and answers any Origin not on its allowlist with a CAPTCHA page, under a misleading405.localhostis never allowlisted, so ENCODE-hosted data cannot be loaded during local development. A backgroundfetchcannot solve a CAPTCHA.The workable fix is a dev-server middleware that fetches the file from Node with an allowlisted
Originand hands the resulting307back to the browser — the bytes still stream directly from S3, so nothing proxies the payload. That requires the client to send the read to the middleware's path instead of the origin URL, which is where the extension point is needed.Measured against an ENCODE bigWig on 2026-08-03:
localhost, any Origin405+x-amzn-waf-action: captchaOrigin: https://aidenlab.org(allowlisted)307→encode-public.s3.amazonaws.com307→ S3 →206 Partial ContentWhy
setCORSProxydoesn't cover itigv.setCORSProxyexists and looked like the answer, buttryCorsProxyis only reachable fromxhr.onerrororxhr.status === 0. ENCODE's challenge response is a405carrying valid CORS headers, soonloadruns with status 405 and goes straight tohandleError. The retry never fires.It is also a fallback rather than a routing decision — it costs a failed request first, and its URL shape is fixed (
<proxy>?url=<encoded>). A pre-fetch mapper expresses "this host is known to need routing" directly.Precedent
hic-strawadded exactly this in v4.0.0 (aidenlab/hic-straw#49) asconfig.mapUrl, composed after its built-in Dropbox/NCBI rewrites — normalize first, then route. It has worked well: the rewrite is host-scoped, so genuine CORS and permissions problems still surface in development exactly as they would in production, which routing everything through a proxy would hide.juicebox.js embeds both hic-straw and igv. Its
.hicreads now work fromlocalhostagainst ENCODE; its igv-backed tracks on the same host still fail, because there is no equivalent seam. Context: aidenlab/juicebox.js#450, and the rationale in ADR 0001.Notes
Browseror per-config option would work too..bai,.tbi,.csi) as well as the primary URL, since a blocked host blocks both.igvxhr'smapUrlis module-private.Happy to open a PR if the shape sounds right — I'd want to match whatever composition order you prefer.