Repository navigation
fix: bound the service worker's navigation fetch with a timeout - #298
Merged
DuarteSantos8 merged 1 commit intoSep 28, 2026
Merged
DuarteSantos8 merged 1 commit into
DuarteSantos8 merged 1 commit into
Conversation
On iOS, a cold-launch of the installed PWA with no network stayed on a black/white screen forever instead of falling back to the cached app shell. A dead radio does not reject fetch() quickly - it just never settles - so the .catch() that reads the shell from cache never got a chance to run. Bound the network-first fetch with AbortSignal.timeout(3000), same as the reporter tested and confirmed fixes it for them in DuarteSantos8#274. Added a test that loads the real public/sw.js into a sandbox and drives its fetch listener with a fetch that never resolves on its own: it checks a timeout is actually wired to the request, that the response does not settle early, and that aborting it recovers from cache instead of hanging. Fixes DuarteSantos8#274. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DuarteSantos8
added a commit
that referenced
this pull request
Sep 28, 2026
…ff mid-download, and iOS 15 keeps its offline start (#274) The 3 s abort from #298 also fired on responses whose headers had arrived, erroring the body the page was still reading, and AbortSignal.timeout does not exist before iOS 16. A request the network has not answered within 3 s is now answered from the cache when the cache has it; nothing is aborted, and a request the cache cannot answer waits for the network.
DuarteSantos8
added a commit
that referenced
this pull request
Sep 28, 2026
Owner
|
Thanks @bluzername, this fixes #274. I reworked it before merging: a 3 s timeout on every GET kept running while the body streamed, so on a slow link the 1.6 MB main bundle would have been cut mid-download. The timeout now applies only to navigations and is cleared once headers arrive, and a chunk or language pack is never cut off. iOS 15 keeps its offline start. Released in v1.3.9: https://github.com/DuarteSantos8/openGym/releases/tag/v1.3.9 |
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #274.
What was wrong
On iOS, launching the installed PWA from the Home Screen with no network
showed a black screen, then a blank white page, forever. The reporter
traced it exactly:
sw.js's network-first fetch handler callsfetch(e.request)with no timeout. A dead radio does not reject thatfetch quickly - it just never settles - so the
.catch()a few linesdown, which falls back to the cached app shell, never gets a turn to run.
The app is stuck waiting on a request that will never finish.
The fix
Same one the reporter already tested and confirmed works for them:
fetch(e.request, { signal: AbortSignal.timeout(3000) }). After 3seconds the fetch aborts, the existing
.catch()fallback runs, and thecached shell (or the matching cached asset) is served instead.
Test
sw.jslives infrontend/public/and isn't itself undersrc/lib, sothere is no existing unit-test convention for it - and this isn't
something clicking through the app can reliably reproduce (it needs an
installed PWA and a truly dead network, not just devtools' "Offline"
checkbox, which rejects fetches immediately instead of hanging). Rather
than skip a test, I added
frontend/src/lib/sw-offline-launch.test.js, which loads the actualproduction
public/sw.jsfile into a sandboxedvmcontext (realself/caches/fetch/AbortSignalstand-ins) and drives its realfetchevent listener directly:fetchthat never resolves on its own (simulating a deadradio, not a quick rejection),
missing on unmodified code - the test fails there with "expected 0 to
be greater than 0" because
AbortSignal.timeoutis never called),microtask turns,
instead of hanging.
I confirmed this fails against unmodified
sw.js(stashed the fix,reran, got the RED failure above, restored the fix) and passes with it.
Verification
cd frontend && npx vitest run src/lib/sw-offline-launch.test.js-1/1 pass.
cd frontend && npx vitest run- full suite, 1573 tests (1 new), allpass (a few files hit a vitest worker-pool timeout under sandboxed
parallelism and pass cleanly alone - unrelated to this change).
No new dependency, one line of production code changed
(
frontend/public/sw.js), plus a new test file.This account (bluzername) is not a collaborator on this repo, so I can't
merge - opening this for a maintainer to review.
🤖 Generated with Claude Code