Files
deer-flow/frontend/src/components/workspace/gateway-offline-fallback.tsx
T
Huixin615 b6fbf0d105 fix(frontend): keep workspace interactive when SSR auth probe cannot reach gateway (#3493) (#3495)
* fix(frontend): keep workspace interactive when SSR auth probe cannot reach gateway (#3493)

When the SSR auth probe at /api/v1/auth/me times out or fails, the
workspace layout used to render a static fallback page without
AuthProvider or QueryClientProvider, making logout and every other
interaction non-functional until the gateway recovered.

Render the normal WorkspaceContent in 'gateway_unavailable' mode
instead, surfacing a polite offline banner that re-probes the gateway
in the background and hides itself the moment refreshUser() returns
an authenticated user. The probe is reentrancy-guarded so a slow
gateway cannot pile up parallel /auth/me requests.

Closes #3493

* fix(workspace): silent probe in offline banner to avoid /login redirect during gateway recovery (#3493)

The banner previously delegated retry probes to AuthProvider.refreshUser(),
which treats any 401 from /api/v1/auth/me as 'session expired' and
force-redirects to /login. During gateway recovery, the first few requests
may transiently return 401 before the gateway is fully ready, which would
incorrectly kick the user out — defeating the purpose of the offline banner.

Now the banner silently fetches /api/v1/auth/me itself and only delegates
to refreshUser() on 200 OK. Non-200 responses (401 / 5xx / network) are
swallowed and retried on the next interval tick, ensuring the user stays
logged in across short gateway outages.

Verified in Docker:
- docker pause deer-flow-gateway → banner appears, page interactive
- docker unpause deer-flow-gateway → banner auto-disappears within 10s,
  user remains on /workspace/chats/new with full session restored
- All 117 unit tests pass

* fix(workspace): fix banner polling leak and persistent 401 handling (#3493)
- Stop polling immediately after user recovery: add user to effect dependencies, cleanup interval when user !== null
- Handle persistent 401: trigger login redirect after 3 consecutive unauthorized responses
- Extract decision logic to pure helper, add 8 unit tests covering all critical paths

* fix(workspace): address CR feedback on gateway offline recovery (#3493)

- gateway-offline-banner-helpers: decrement (not reset) auth-failure
  streak on transient outcomes so a flapping gateway (401 alternating
  with 5xx) still converges on session-expired
- gateway-offline-banner: reuse probe response body to apply user
  directly via new AuthProvider.applyUser, halving the recovery burst
  against an already-struggling gateway
- gateway-offline-banner: extract classifyProbe into helpers for unit
  testability; log probe failures via console.warn instead of swallowing
- gateway-offline-fallback: new shared component used by both workspace
  and (auth) layouts so auth pages recover the same way the workspace
  does, fixing the lockup where bare static HTML had no AuthProvider
- AuthProvider.logout: fall back to hard navigation when the gateway
  logout fetch fails, matching legacy form-POST behaviour and avoiding
  stale client state during outage
- tests: extend gateway-offline-banner-helpers.test with flapping
  convergence and classifyProbe branch coverage (19 cases total)
2026-06-11 21:14:49 +08:00

37 lines
1.2 KiB
TypeScript

"use client";
import { AuthProvider } from "@/core/auth/AuthProvider";
import { GatewayOfflineBanner } from "./gateway-offline-banner";
interface GatewayOfflineFallbackProps {
/**
* When true, this component renders its own banner. The workspace layout
* sets this to false because WorkspaceContent already mounts the banner
* inside its sidebar layout. The (auth) layout sets it to true because
* its plain children have no banner of their own.
*/
renderBanner?: boolean;
children?: React.ReactNode;
}
/**
* Shared fallback shown by both the workspace and (auth) layouts when the
* server-side auth probe could not reach the gateway. Wraps the children
* with an AuthProvider so the banner's probe / logout / refresh hooks work
* — fixing the `(auth)/layout.tsx` lockup where the bare static HTML had
* no AuthProvider / QueryClientProvider and the user could not recover
* without a manual reload.
*/
export function GatewayOfflineFallback({
renderBanner = false,
children,
}: GatewayOfflineFallbackProps) {
return (
<AuthProvider initialUser={null}>
{renderBanner && <GatewayOfflineBanner gatewayUnavailable />}
{children}
</AuthProvider>
);
}