request-refresher
v1.0.0
Published
A small service worker helper for 401 responses, coordinated refresh fetch, and retry
Maintainers
Readme
request-refresher
A small service worker helper that wraps fetch so 401 responses trigger a single coordinated refresh fetch, then one retry of the original request.
Features
- Transparent to the call site — application code keeps using ordinary
fetch; you wire the wrapper once in the service worker, and callers do not need refresh-specific logic or extra handling - 401-only — reacts when
response.status === 401 - Single-flight refresh — concurrent 401s share one in-flight refresh
fetch - Optional callbacks —
on_refresh_succeeded/on_refresh_failedfor follow-up or cleanup (each runs at most once per coordinated refresh; see Configuration) - No runtime dependencies
- ES modules — intended for module service workers
Installation
CDN (pinned)
// sw.js
import { build_fetch_with_refresh } from "https://cdn.jsdelivr.net/npm/[email protected]/src/index.js";npm
npm install request-refresherimport { build_fetch_with_refresh } from "request-refresher";Quick start
1. Register a module service worker
try {
await navigator.serviceWorker.register("/sw.js", { type: "module" });
} catch (error) {
console.error("Error registering module service worker:", error);
}2. Use the wrapper in sw.js
import { build_fetch_with_refresh } from "https://cdn.jsdelivr.net/npm/[email protected]/src/index.js";
const fetch_with_refresh = build_fetch_with_refresh({
refresh_request: new Request("/api/auth/refresh", { method: "POST" })
});
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
if (url.pathname.startsWith("/api/")) {
event.respondWith(fetch_with_refresh(event.request));
}
});You must pass a refresh_request Request (at minimum a URL and method; add headers, body, or other Request options only if your API needs them). The wrapper fetches the incoming RequestInfo as usual. If the server responds with 401, the library performs that refresh fetch (each attempt uses a clone of refresh_request), then issues one more fetch on the original RequestInfo. Backends typically return 401 when the session is invalid; no special headers or service-worker-specific signals are required.
Backend refresh endpoint
A typical refresh route is a POST (or whatever your API uses) that validates a refresh credential and responds by setting two cookies:
- Session cookie — scoped to
Path=/so it is sent on normal app requests (for example/api/...). The encoded session token is usually short-lived (for example about 1 hour). - Refresh cookie — scoped narrowly to the refresh URL only, for example
Path=/api/auth/refreshin the snippets above, so the browser only sends it when calling that endpoint. The refresh token and cookie usually last longer (for example about 1 week).
Both are commonly HttpOnly and Secure (and SameSite as your threat model requires). The service worker does not read these cookies; the browser attaches them automatically on the refresh fetch and on the retried request. Your backend decides names, encoding, and exact lifetimes—this library only needs the refresh URL to succeed when a new session should be issued.
Configuration
refresh_request is required. Optional fields: customFetch (swap in your own fetch for mocks or composition), loggingEnabled, on_refresh_succeeded, on_refresh_failed.
Each of on_refresh_succeeded and on_refresh_failed runs at most once per refresh attempt, and concurrent 401s share one in-flight refresh—so each callback fires at most once for that shared refresh. Thrown errors in these callbacks are logged and do not alter which Response the wrapper returns.
const fetch_with_refresh = build_fetch_with_refresh({
refresh_request: new Request("https://api.example.com/session/refresh", {
method: "POST"
}),
loggingEnabled: false,
on_refresh_succeeded: async () => {
// e.g. update locally cached auth state — runs when the refresh response is OK
},
on_refresh_failed: async () => {
// e.g. clear app cache — runs when refresh response is not OK
}
});Limitations
- Only 401 triggers refresh (not other status codes).
- At most one refresh attempt per 401 branch; no loop until success. If the retry still returns 401, that response is returned.
- Refresh is always a
fetchofrefresh_request— there is no arbitrary async callback instead offetch.
Example site
From the package root:
npm install
npm run exampleOpen the URL printed by serve (see package.json — default port 4173). Use the buttons to call a mocked /api/profile flow and reset demo state.
Development
npm testRuns unit tests, Node e2e-style tests, and Playwright against example/.
License
MIT
