mirror of
https://github.com/ruvnet/RuView
synced 2026-07-26 18:01:48 +00:00
fix(ui): service worker cached /oauth/status, freezing browser sign-in
Browser sign-in worked, but the settings panel kept offering "Sign in with
Cognitum" afterwards; only a hard reload showed the true state. The server was
correct throughout.
Root cause: `ui/sw.js` routed cache-first as its CATCH-ALL for every path
outside `/api/` and `/health/`. `/oauth/status` therefore had its first
(signed-out) response stored in the Cache API and replayed to the page forever.
The Cache API is not the HTTP cache and ignores `Cache-Control` entirely, so the
`no-store, no-cache, must-revalidate` the server already sends could not prevent
it. A hard reload bypasses the service worker, which is why that alone appeared
to fix it, and why signing out re-poisoned the entry.
Fixes, in order of blast radius:
- `/oauth/` is never handled by the worker. Not network-first — that still
writes a copy, which would be replayed the moment the server is briefly
unreachable, silently reinstating a stale sign-in state.
- Cache-first is now an ALLOWLIST (navigations and static asset extensions)
rather than the catch-all. This is the underlying defect: any endpoint added
outside `/api/` was frozen on its first response. Unrecognised paths now go
to the network untouched.
- `CACHE_NAME` bumped to `ruview-v2`, so `activate` evicts the poisoned
`ruview-v1` from browsers that already ran the old worker. Verified: the
cache list went from `[ruview-v1]` to `[ruview-v2]` on update.
- Shell lookups use `ignoreSearch` and store a search-less key, so the
`?signed_in=<ms>` the callback redirects to does not mint a fresh, never-hit
cache entry per sign-in.
Also: re-check sign-in state on `pageshow` (bfcache restore, where no script
re-runs) and on `visibilitychange` (signed in or out in another tab). Opening
the panel alone is not sufficient — it may already be open.
Verification, in a real browser driven end-to-end:
- Reproduced with a valid session cookie: server returned `signed_in: true` to
curl while the page's own fetch got a cached `signed_in: false`, and the
request never appeared in the server log at all.
- Confirmed the cached entry existed: `caches.open('ruview-v1').match(
'/oauth/status')` returned the signed-out body.
- After the fix, on a NORMAL (not hard) reload the panel reads
"Signed in as <account> - sensing:read" with Sign out shown.
Tests: `ui/sw.test.mjs` loads the real `sw.js` with stubbed worker globals and
asserts the routing decision per path. 4 of its 10 tests fail against the
pre-fix worker and pass after; the other 6 pin pre-existing guards (non-GET,
websocket upgrade, cross-origin, API paths, static assets, navigation) and pass
in both, so they track behaviour rather than the rewrite.
CI: adds a `ui-tests` job. Nothing ran the UI JavaScript before, so both this
suite and the ADR-272 ws-ticket suite would have rotted unexecuted — which is
the same blind spot that let this defect ship.
Removes the temporary `/oauth/status` cookie logging and the panel console
diagnostic added while tracking this down.
Co-Authored-By: Ruflo & AQE
This commit is contained in:
@@ -77,7 +77,7 @@ export class QuickSettings {
|
||||
<div class="qs-section">
|
||||
<div class="qs-section-title">Cognitum Account</div>
|
||||
<div class="qs-row" style="flex-direction: column; align-items: stretch; gap: 6px;">
|
||||
<span id="qs-signin-status" style="font-size: 0.9em; opacity: 0.85;">Checking\u2026</span>
|
||||
<span id="qs-signin-status" style="font-size: 0.9em; opacity: 0.85;">Checking...</span>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="qs-btn" id="qs-signin" hidden>Sign in with Cognitum</button>
|
||||
<button class="qs-btn-danger" id="qs-signout" hidden>Sign out</button>
|
||||
@@ -113,6 +113,16 @@ export class QuickSettings {
|
||||
// Bind events
|
||||
this.panel.querySelector('.qs-close').addEventListener('click', () => this.close());
|
||||
|
||||
// Re-check sign-in state whenever the page could be showing a stale view:
|
||||
// `pageshow` fires on a back/forward-cache restore (where no script re-runs
|
||||
// and no fetch would otherwise happen), and `visibilitychange` covers
|
||||
// signing in or out in another tab. Opening the panel alone is not enough —
|
||||
// the panel may already be open, or the page may be restored wholesale.
|
||||
window.addEventListener('pageshow', () => { void refreshSignInPanel(this.panel); });
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) void refreshSignInPanel(this.panel);
|
||||
});
|
||||
|
||||
// ADR-271 sign-in. Bound here as well as in refreshSignInPanel so a click
|
||||
// works even if the status fetch has not resolved yet.
|
||||
this.panel.querySelector('#qs-signin')
|
||||
@@ -293,7 +303,7 @@ export async function refreshSignInPanel(root = document) {
|
||||
|
||||
if (info.signed_in) {
|
||||
status.textContent = `Signed in${info.account ? ` as ${info.account}` : ''}${
|
||||
info.scope ? ` \u2014 ${info.scope}` : ''
|
||||
info.scope ? ` - ${info.scope}` : ''
|
||||
}`;
|
||||
signIn.hidden = true;
|
||||
signOut.hidden = false;
|
||||
|
||||
Reference in New Issue
Block a user