mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Why Manual relay recovery polls every three seconds, repeatedly consuming the session's exponential-backoff timer and leaving reconnect behavior stuck or noisy on degraded networks. ## What - Replace fixed-cadence Phase 3 polling with observation of the RelayClient background reconnect loop - Raise the fast-path deadline above the native websocket timeout and enforce that contract in a regression test - Keep the existing 120-second backstop as a soft UI timeout without stopping background retries ## Risk Assessment Medium — this changes live relay recovery timing, but removes a competing retry loop rather than adding one. The existing connection-state subscription remains the success signal. ## References - Stacked on #2310 (`lazyjoe/reconnect-testability-refactor`) - Investigation: `RESEARCH/BUG_RELAY_RECONNECT_HANG.md` - `just ci` - `cd desktop && pnpm typecheck && pnpm check && pnpm test` (3485 pass) - `git diff --check` Generated with Codex Signed-off-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1x4hk035p3p9q39a3fcrd2fe30lpkrhr5dwe0cqzzjphxyyh8m0gsq4vqap <356f67c681884a0897b14e06d527317fc361dc746bb2fc0042906e6212e7dbd1@sprout-oss.stage.blox.sqprod.co>
90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
/**
|
|
* React binding for the relay reconnect controller.
|
|
*
|
|
* Delegates all reconnect logic to the module-level `relayReconnectController`
|
|
* singleton so that all mounted hook instances (banner + sidebar card) share
|
|
* a single in-flight state. Deliberately uses `preconnect()` rather than the
|
|
* full `reconnectCommunity()` path to avoid unmounting the React tree and
|
|
* clearing draft state.
|
|
*
|
|
* See `relayReconnectController.ts` for the three-phase strategy details.
|
|
*/
|
|
|
|
import * as React from "react";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { toast } from "sonner";
|
|
|
|
import { relayClient } from "@/shared/api/relayClient";
|
|
import { isRelayDependentQuery } from "@/shared/api/relayQueryInvalidation";
|
|
import { relayReconnectController } from "@/shared/api/relayReconnectController";
|
|
|
|
function buildDeps(onSuccess: () => void, onBackstop: () => void) {
|
|
return {
|
|
preconnect: () => relayClient.preconnect(),
|
|
hookConfigured: () => invoke<boolean>("relay_reconnect_hook_configured"),
|
|
runHook: () => invoke<void>("relay_reconnect_hook"),
|
|
subscribeToConnectionState: (listener: (state: string) => void) =>
|
|
relayClient.subscribeToConnectionState(listener),
|
|
onSuccess,
|
|
onBackstop,
|
|
setTimeout: window.setTimeout.bind(window),
|
|
clearTimeout: window.clearTimeout.bind(window),
|
|
};
|
|
}
|
|
|
|
export function useReconnectRelay(): {
|
|
reconnect: () => Promise<boolean>;
|
|
isPending: boolean;
|
|
isWaitingOnReconnectHook: boolean;
|
|
} {
|
|
const queryClient = useQueryClient();
|
|
|
|
const [controllerState, setControllerState] = React.useState(() =>
|
|
relayReconnectController.getState(),
|
|
);
|
|
|
|
// Subscribe to controller state changes for the lifetime of this component.
|
|
// Multiple hook instances receive the same state from the shared singleton.
|
|
React.useEffect(() => {
|
|
return relayReconnectController.subscribe(setControllerState);
|
|
}, []);
|
|
|
|
// Stable mutable refs for callbacks — updated every render so stale closures
|
|
// are never captured, but the reconnect callback itself never changes identity.
|
|
const onSuccessRef = React.useRef<(() => void) | null>(null);
|
|
const onBackstopRef = React.useRef<(() => void) | null>(null);
|
|
|
|
onSuccessRef.current = React.useCallback(() => {
|
|
// Defer query invalidation so callers render the recovered state first.
|
|
window.setTimeout(() => {
|
|
void queryClient
|
|
.invalidateQueries({ predicate: isRelayDependentQuery })
|
|
.catch((err) => {
|
|
console.error(
|
|
"[useReconnectRelay] failed to refresh queries after reconnect:",
|
|
err,
|
|
);
|
|
});
|
|
}, 0);
|
|
}, [queryClient]);
|
|
|
|
onBackstopRef.current = React.useCallback(() => {
|
|
toast("Still trying to reconnect — check your network.");
|
|
}, []);
|
|
|
|
const reconnect = React.useCallback(async () => {
|
|
const deps = buildDeps(
|
|
() => onSuccessRef.current?.(),
|
|
() => onBackstopRef.current?.(),
|
|
);
|
|
return relayReconnectController.start(deps);
|
|
}, []);
|
|
|
|
return {
|
|
reconnect,
|
|
isPending: controllerState.isPending,
|
|
isWaitingOnReconnectHook: controllerState.isWaitingOnReconnectHook,
|
|
};
|
|
}
|