aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-17 10:26:09 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-17 10:26:09 +0200
commit1fd284dbe33d05fd5037b172fe652a8a98f7b68d (patch)
tree52465b6d438fc7d7bd96f1cf83571069eb0fa3d3 /packages/meshbay-hub/src/meshbay_hub/static
parenta98445ce246509b0d2600df14907f72b448a6d10 (diff)
downloadmeshbay-1fd284dbe33d05fd5037b172fe652a8a98f7b68d.tar.gz
auth: a sign-out during a renewal must not write half a session
`{ ..._auth }` after the await spreads a null _auth to {}, so the renewal stored a token with no username and no userId. The app renders the signed-in interface from that and throws on user.username[0] — a blank page on every load, in localStorage, until the site's data is cleared. The sign-out wins the race now, and loadAuth treats an identity-less object as signed out so a browser already holding one heals itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/hub-client.js27
1 files changed, 26 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js b/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js
index eead457..81185a5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js
@@ -188,11 +188,23 @@ async function _clearKeyDB() {
}
function loadAuth() {
+ let auth;
try {
- return JSON.parse(localStorage.getItem(AUTH_KEY));
+ auth = JSON.parse(localStorage.getItem(AUTH_KEY));
} catch {
return null;
}
+ // A session is an identity and a token. An object carrying only the token is
+ // what a sign-out racing a renewal used to write (see `refreshAccessToken`),
+ // and it is worse than no session: the app renders the signed-in interface
+ // from it and throws on the first field it reads. That fix stops new ones
+ // being written; this one lets a browser already holding one heal itself on
+ // the next load, instead of needing somebody to find the reset button.
+ if (auth && (!auth.username || !auth.userId)) {
+ try { localStorage.removeItem(AUTH_KEY); } catch { /* private mode */ }
+ return null;
+ }
+ return auth;
}
function saveAuth(auth) {
@@ -276,6 +288,19 @@ async function refreshAccessToken() {
return null;
}
const data = await r.json();
+ // Signing out while this was in flight is not rare — it is the ordinary
+ // shape of a tab left open: the idle watch signs out at the same moment
+ // the renewal fires, one second apart in the hub's log. `_auth` is null
+ // by now, and `{ ..._auth }` spreads null to `{}` without complaining, so
+ // what got written back was a token and *no identity at all*: an object
+ // the app believes is a session, renders the signed-in interface from,
+ // and throws on at the first field it reads — `username[0]`, a blank page
+ // on every load afterwards, in localStorage, surviving everything but a
+ // reset of the site's data.
+ //
+ // A sign-out that arrives during a renewal wins. There is nothing here
+ // worth saving over it.
+ if (!_auth) return null;
setAuth({
..._auth,
token: data.access_token,