mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-15 19:45:42 -04:00
fix(native): sync React Query online state with server reachability
Probe the health endpoint while unreachable, refetch errored queries when the server returns, and keep gcTime at least as long as the persister maxAge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
6 files changed
+43
-8
No files matched your search
@@ -25,6 +25,7 @@ import { useWidgetRefresh } from "@/hooks/use-widget-refresh";
|
||||
import { initLocale } from "@/lib/i18n";
|
||||
import { initAnalytics, posthog } from "@/lib/posthog";
|
||||
import { queryClient } from "@/lib/query-client";
|
||||
import { QUERY_PERSIST_MAX_AGE } from "@/lib/query-config";
|
||||
import { initSentry, Sentry } from "@/lib/sentry";
|
||||
import { getScopeKey, initSession, onStorageScopeChange, queryPersister } from "@/lib/server";
|
||||
import { sofaTheme } from "@/lib/theme";
|
||||
@@ -170,7 +171,7 @@ function QueryProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
if (!scopeKey) return;
|
||||
|
||||
const options = { queryClient, persister: queryPersister };
|
||||
const options = { queryClient, persister: queryPersister, maxAge: QUERY_PERSIST_MAX_AGE };
|
||||
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
let aborted = false;
|
||||
|
||||
@@ -87,6 +87,10 @@ export function useServerConnection() {
|
||||
onServerReachabilityChange((reachable) => {
|
||||
if (reachable) {
|
||||
authClient.$store.atoms.session.get().refetch?.();
|
||||
void queryClient.refetchQueries({
|
||||
type: "active",
|
||||
predicate: (q) => q.state.status === "error",
|
||||
});
|
||||
}
|
||||
}),
|
||||
[],
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { isClientError, isUnauthorizedError } from "@/lib/error-messages";
|
||||
import { posthog } from "@/lib/posthog";
|
||||
import { QUERY_GC_TIME } from "@/lib/query-config";
|
||||
import { authClient, getIsReachable, isNetworkError, markSessionRejected } from "@/lib/server";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { i18n } from "@sofa/i18n";
|
||||
@@ -23,7 +24,7 @@ export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60_000,
|
||||
gcTime: 300_000,
|
||||
gcTime: QUERY_GC_TIME,
|
||||
networkMode: "online",
|
||||
retry: (failureCount, error) => {
|
||||
// 4xx errors can't succeed on retry.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { QUERY_GC_TIME, QUERY_PERSIST_MAX_AGE } from "@/lib/query-config";
|
||||
|
||||
describe("query cache lifetimes", () => {
|
||||
test("gcTime is at least the persister maxAge", () => {
|
||||
expect(QUERY_GC_TIME).toBeGreaterThanOrEqual(QUERY_PERSIST_MAX_AGE);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
/** How long persisted query data is kept (and restored) — 24 h. */
|
||||
export const QUERY_PERSIST_MAX_AGE = 24 * 60 * 60 * 1000;
|
||||
/** gcTime must be ≥ the persister's maxAge, or GC'd queries vanish from the persisted cache. */
|
||||
export const QUERY_GC_TIME = QUERY_PERSIST_MAX_AGE;
|
||||
@@ -206,6 +206,7 @@ export async function validateServerUrl(url: string): Promise<ValidationResult>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
let isReachable = true;
|
||||
let lastNetworkState: Network.NetworkState | null = null;
|
||||
const reachabilityListeners: Array<(reachable: boolean) => void> = [];
|
||||
|
||||
function notifyReachability() {
|
||||
@@ -218,14 +219,28 @@ function setReachable(nextReachable: boolean) {
|
||||
if (isReachable === nextReachable) return;
|
||||
isReachable = nextReachable;
|
||||
notifyReachability();
|
||||
syncOnlineState();
|
||||
}
|
||||
|
||||
function isDeviceOnline(state: Network.NetworkState): boolean {
|
||||
return !!state.isConnected && state.isInternetReachable !== false;
|
||||
}
|
||||
|
||||
function syncOnlineState(state: Network.NetworkState) {
|
||||
onlineManager.setOnline(isDeviceOnline(state) || isReachable);
|
||||
function syncOnlineState(state: Network.NetworkState | null = lastNetworkState) {
|
||||
if (state) lastNetworkState = state;
|
||||
const deviceOnline = lastNetworkState ? isDeviceOnline(lastNetworkState) : true;
|
||||
onlineManager.setOnline(deviceOnline || isReachable);
|
||||
}
|
||||
|
||||
// While the server is unreachable, queries are paused and never hit serverFetch, so probe the
|
||||
// health endpoint directly; a success flips reachability (and the online flag) back.
|
||||
function probeServerIfUnreachable() {
|
||||
if (isReachable) return;
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 5000);
|
||||
serverFetch(`${getServerUrl()}/api/health`, { method: "GET", signal: controller.signal })
|
||||
.catch(() => {})
|
||||
.finally(() => clearTimeout(timer));
|
||||
}
|
||||
|
||||
function syncAppFocus(state: AppStateStatus) {
|
||||
@@ -252,9 +267,6 @@ export async function serverFetch(input: RequestInfo | URL, init?: RequestInit):
|
||||
try {
|
||||
const response = await fetch(input, init);
|
||||
setReachable(true);
|
||||
if (!onlineManager.isOnline()) {
|
||||
onlineManager.setOnline(true);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (isNetworkError(error)) {
|
||||
@@ -280,12 +292,16 @@ export function startReachabilityMonitor(): () => void {
|
||||
syncAppFocus(AppState.currentState);
|
||||
void Network.getNetworkStateAsync().then(syncOnlineState);
|
||||
|
||||
const networkSubscription = Network.addNetworkStateListener(syncOnlineState);
|
||||
const networkSubscription = Network.addNetworkStateListener((state) => {
|
||||
syncOnlineState(state);
|
||||
probeServerIfUnreachable();
|
||||
});
|
||||
|
||||
const appStateSubscription = AppState.addEventListener("change", (nextState) => {
|
||||
syncAppFocus(nextState);
|
||||
if (nextState === "active") {
|
||||
void Network.getNetworkStateAsync().then(syncOnlineState);
|
||||
probeServerIfUnreachable();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user