fix(auth): pin each tab to its account so cross-tab changes don't leak in

A tab that never explicitly picked an account rode the session's default, so when ANOTHER
tab changed that default (adding or switching an account), this tab silently swapped identity
on its next refetch — the account chip updated while the feed/saved-views lagged until reload.

- Pin every tab to whatever account it first loaded as (write the sessionStorage override on
  load when none is set), so its requests always carry its own account header and cross-tab
  default changes can't reach it.
- 'Add account' now clears this tab's pin before the Google redirect, so on return the tab
  adopts the freshly-added account (and pins that) — the current tab switches to it while other
  tabs keep their own, matching the Gmail add-account model.
This commit is contained in:
npeter83 2026-07-02 00:16:51 +02:00
parent 5cd807ec51
commit f32c3f08dc
2 changed files with 21 additions and 1 deletions

View file

@ -1,7 +1,14 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { api, HttpError, setUnauthorizedHandler, type FeedFilters } from "./lib/api";
import {
api,
getActiveAccount,
HttpError,
setActiveAccount,
setUnauthorizedHandler,
type FeedFilters,
} from "./lib/api";
import { setLanguage, isSupported, type LangCode } from "./i18n";
import {
applyTheme,
@ -367,6 +374,15 @@ export default function App() {
refetchInterval: (query) => (query.state.data ? 60_000 : false),
});
// Pin this tab to whatever account it first loaded as. Without this, a tab that never picked
// an account (it was riding the session's default) would silently swap identity when ANOTHER
// tab changes the default — e.g. that other tab adds/switches an account. Once pinned, this
// tab always sends its own account header, so cross-tab changes can't reach it. An explicit
// switch on THIS tab sets the override itself (so this no-ops then).
useEffect(() => {
if (meQuery.data && getActiveAccount() == null) setActiveAccount(meQuery.data.id);
}, [meQuery.data?.id]);
// Once signed in, drop any leftover query string from the pre-login flow (e.g.
// ?access=requested, ?verify, ?reset). The logged-in app reads nothing from the URL, so the
// address bar stays clean. Gated on auth so the logged-out Welcome page still reads its params.