Skip to content

fix(service-worker): respondWith synchronously and drop extension bypass - #226

Merged
pi0 merged 1 commit into
mainfrom
fix/service-worker-respondwith
Jul 14, 2026
Merged

pi0 merged 1 commit into
mainfrom
fix/service-worker-respondwith

Conversation

@pi0x

@pi0x pi0x commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Fixes two locked-together bugs in the service-worker adapter.

  • F6event.respondWith() was called after an await, past the dispatch flag, throwing InvalidStateError on every request so all traffic fell through to the network; the window branch also reloaded whenever an active registration existed, causing an infinite reload loop. Now respondWith() is called synchronously with a promise, and the window branch only reloads on first controllerchange.
  • F28 — Removed the /\/[^/]*\.[a-zA-Z0-9]+$/ extension-bypass regex; the handler now sees every same-origin request (e.g. /api/data.json), and an unhandled 404 falls back to the network inside the response promise.

Adds in-process tests for the fetch-listener logic and updates examples/service-worker so the worker script loads from the network.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved service-worker request routing, including explicit handling for root and unknown paths.
    • Added network fallback behavior when a request handler returns a 404.
    • Pages now reload automatically when the service worker takes control.
  • Bug Fixes

    • Improved request handling for URLs with file extensions.
    • Ensured fetch responses are handled consistently and promptly.

…n bypass

F6: respondWith() was called after an await (past the dispatch flag), so it
threw InvalidStateError on every request and all traffic fell through to the
network. Call respondWith() synchronously with a promise that runs the srvx
handler and, on a 404, falls back to fetch(event.request). Also fix the window
branch reload loop: only reload once the freshly installed worker takes control
(controllerchange) instead of whenever an active registration exists.

F28: remove the /\/[^/]*\.[a-zA-Z0-9]+$/ extension-bypass regex so the handler
sees every same-origin request (e.g. /api/data.json); unhandled 404s now fall
back to the network inside the response promise.

Add in-process tests for the fetch-listener logic and update the example so the
worker script loads from the network.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pi0x
pi0x requested a review from pi0 as a code owner July 14, 2026 11:19
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Service worker routing

Layer / File(s) Summary
Example pathname routing
examples/service-worker/public/sw.mjs
The example serves HTML for / and returns 404 Not Found for other pathnames.
Adapter lifecycle and fetch fallback
src/adapters/service-worker.ts, test/service-worker.test.ts
The adapter reloads after gaining control, synchronously registers fetch handling, preserves non-404 responses, and falls back to network fetches for 404 responses. Tests cover registration, promise handling, response routing, and fallback behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: pi0

Poem

I’m a bunny with routes in my den,
Root pages say hello again.
Lost paths hop to 404,
Network fetches cross the door.
Controller changes make pages bright—
Tests keep every hop just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main service-worker fixes: synchronous respondWith and removal of the extension bypass.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/service-worker-respondwith

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/srvx@226

commit: 9ec8959

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/service-worker.test.ts (1)

14-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No coverage for the browser-window reload/controllerchange fix.

window is stubbed to undefined, so only the isServiceWorker branch (service-worker.ts Lines 87-103) is exercised. The other headline fix in this PR — the one-shot controllerchange reload in service-worker.ts Lines 71-86 — has no test in this suite. Consider adding a case that stubs navigator.serviceWorker (controller unset → register → controllerchange → reload called once) to lock in that fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/service-worker.test.ts` around lines 14 - 44, Add a test covering the
browser-window branch in the service-worker adapter setup: stub
navigator.serviceWorker with no initial controller, trigger registration
followed by controllerchange, and assert the reload handler is called exactly
once. Keep the existing service-worker branch test intact and ensure the new
case exercises the one-shot behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adapters/service-worker.ts`:
- Around line 97-100: The fetch path in the service-worker adapter
unconditionally replaces every handler 404 with a network request, preventing
intentional 404 responses from reaching clients. Update the logic around
this.fetch and the response.status check to require an explicit network-fallback
opt-in or sentinel marker before falling back, while preserving ordinary handler
responses—including deliberate 404s—by default.
- Around line 89-103: Update the this.#fetchListener handler to clone
event.request before passing it to this.fetch, while retaining the original
event.request for the 404 fallback fetch. Ensure the cloned request is used only
for handler invocation and the existing response-status behavior remains
unchanged.

---

Nitpick comments:
In `@test/service-worker.test.ts`:
- Around line 14-44: Add a test covering the browser-window branch in the
service-worker adapter setup: stub navigator.serviceWorker with no initial
controller, trigger registration followed by controllerchange, and assert the
reload handler is called exactly once. Keep the existing service-worker branch
test intact and ensure the new case exercises the one-shot behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2b3d56d6-e79b-4d5a-811a-3aaef4ac1c9a

📥 Commits

Reviewing files that changed from the base of the PR and between f2edab1 and 9ec8959.

📒 Files selected for processing (3)
  • examples/service-worker/public/sw.mjs
  • src/adapters/service-worker.ts
  • test/service-worker.test.ts

Comment on lines +89 to 103
this.#fetchListener = (event) => {
Object.defineProperty(event.request, "waitUntil", {
value: event.waitUntil.bind(event),
});
const response = await this.fetch(event.request, event);
if (response.status !== 404) {
event.respondWith(response);
}
// `respondWith` must be called synchronously (before the event
// dispatch completes), passing a promise that resolves the response.
event.respondWith(
(async () => {
const response = await this.fetch(event.request, event);
// Treat a 404 from the handler as "not handled" and fall back
// to the network for the original request.
return response.status === 404 ? fetch(event.request) : response;
})(),
);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n== service-worker.ts ==\n'
sed -n '1,220p' src/adapters/service-worker.ts

printf '\n== search for fetch implementation and request cloning ==\n'
rg -n "class .*fetch|fetch\\(request|request\\.clone\\(|clone\\(\\)" src -g '!**/*.map'

printf '\n== likely adapter files ==\n'
fd -a "service-worker.ts|fetch.*ts|adapter.*ts" src

Repository: h3js/srvx

Length of output: 4816


🏁 Script executed:

echo hi

Repository: h3js/srvx

Length of output: 150


🏁 Script executed:

set -euo pipefail

printf '\n== _middleware.ts outline ==\n'
ast-grep outline src/_middleware.ts --view expanded

printf '\n== _middleware.ts contents ==\n'
sed -n '1,260p' src/_middleware.ts

printf '\n== search for request/body handling in src ==\n'
rg -n "request\\.(json|text|arrayBuffer|formData)|clone\\(\\)|new Request\\(" src

Repository: h3js/srvx

Length of output: 1786


🏁 Script executed:

set -euo pipefail

sed -n '1,260p' src/_middleware.ts

Repository: h3js/srvx

Length of output: 881


Clone the request before invoking the handler
src/adapters/service-worker.ts:89-103 this.fetch(...) and the 404 fallback both use the same Request. If middleware or the handler reads a POST/PUT/PATCH body before returning 404, fetch(event.request) reuses a consumed stream and throws instead of falling back to the network. Clone the request for the handler and keep the original for fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adapters/service-worker.ts` around lines 89 - 103, Update the
this.#fetchListener handler to clone event.request before passing it to
this.fetch, while retaining the original event.request for the 404 fallback
fetch. Ensure the cloned request is used only for handler invocation and the
existing response-status behavior remains unchanged.

Comment on lines +97 to +100
const response = await this.fetch(event.request, event);
// Treat a 404 from the handler as "not handled" and fall back
// to the network for the original request.
return response.status === 404 ? fetch(event.request) : response;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

HTTP 404 is overloaded as "not handled" — legitimate 404 responses from handlers are unrecoverably replaced.

Any handler response with status 404 is discarded in favor of a raw network fetch (Line 100), even when the 404 is a deliberate response (e.g. a JSON "not found" body from an API route). Applications with real API routes can never surface an intentional 404 to the client via this adapter — it's silently swapped for whatever the network returns for that URL.

Since this is explicitly documented as the intended routing-fallback behavior, consider gating it behind an explicit opt-in (e.g. a serviceWorker.networkFallback option or a dedicated sentinel header/marker) rather than keying off any 404 response unconditionally, so handlers that legitimately want to return 404 aren't silently overridden.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adapters/service-worker.ts` around lines 97 - 100, The fetch path in the
service-worker adapter unconditionally replaces every handler 404 with a network
request, preventing intentional 404 responses from reaching clients. Update the
logic around this.fetch and the response.status check to require an explicit
network-fallback opt-in or sentinel marker before falling back, while preserving
ordinary handler responses—including deliberate 404s—by default.

@pi0
pi0 merged commit 7550e3a into main Jul 14, 2026
15 checks passed
@pi0
pi0 deleted the fix/service-worker-respondwith branch July 14, 2026 11:38
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.

2 participants