fix: network reset navigation drift

This commit is contained in:
devthejo 2026-03-08 00:38:52 +01:00
parent 47928ce9f2
commit 8a25474770
No known key found for this signature in database
GPG key ID: 00CCA7A92B1D5351
5 changed files with 73 additions and 16 deletions

View file

@ -128,7 +128,7 @@ export default function useLatestWithSubscription(
// Some devices keep the WS transport "connected" after a lock/unlock, but the
// per-operation subscription stops delivering. Trigger a controlled resubscribe.
const FOREGROUND_KICK_MIN_INACTIVE_MS = 3_000;
const FOREGROUND_KICK_MIN_INACTIVE_MS = 30_000;
const FOREGROUND_KICK_MIN_INTERVAL_MS = 15_000;
if (
@ -239,6 +239,15 @@ export default function useLatestWithSubscription(
if (age < livenessStaleMs) return;
const now = Date.now();
const becameInactiveAt = lastBecameInactiveAtRef.current;
const inactiveWindowMs = becameInactiveAt ? now - becameInactiveAt : null;
if (
typeof inactiveWindowMs === "number" &&
inactiveWindowMs < livenessStaleMs + 15_000
) {
return;
}
if (now - lastLivenessKickAtRef.current < livenessStaleMs) return;
lastLivenessKickAtRef.current = now;
@ -276,7 +285,7 @@ export default function useLatestWithSubscription(
// Escalation policy for repeated consecutive stale kicks.
if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD &&
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD + 2 &&
now - lastReloadAtRef.current >= MIN_ESCALATION_INTERVAL_MS
) {
const lastRecovery = wsLastRecoveryDateRef.current
@ -310,7 +319,7 @@ export default function useLatestWithSubscription(
// ignore
}
networkActions.triggerReload();
networkActions.triggerReload("transport");
} else if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_WS_RESTART &&
now - lastWsRestartAtRef.current >= MIN_ESCALATION_INTERVAL_MS

View file

@ -131,7 +131,7 @@ export default function useStreamQueryWithSubscription(
// Some devices keep the WS transport "connected" after a lock/unlock, but the
// per-operation subscription stops delivering. Trigger a controlled resubscribe.
const FOREGROUND_KICK_MIN_INACTIVE_MS = 3_000;
const FOREGROUND_KICK_MIN_INACTIVE_MS = 30_000;
const FOREGROUND_KICK_MIN_INTERVAL_MS = 15_000;
if (
@ -281,6 +281,15 @@ export default function useStreamQueryWithSubscription(
});
}
// Avoid spamming resubscribe triggers.
const becameInactiveAt = lastBecameInactiveAtRef.current;
const inactiveWindowMs = becameInactiveAt ? now - becameInactiveAt : null;
if (
typeof inactiveWindowMs === "number" &&
inactiveWindowMs < livenessStaleMs + 15_000
) {
return;
}
if (now - lastLivenessKickAtRef.current < livenessStaleMs) return;
lastLivenessKickAtRef.current = now;
@ -318,7 +327,7 @@ export default function useStreamQueryWithSubscription(
// Escalation policy for repeated consecutive stale kicks.
if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD &&
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD + 2 &&
now - lastReloadAtRef.current >= MIN_ESCALATION_INTERVAL_MS
) {
const lastRecovery = wsLastRecoveryDateRef.current
@ -352,7 +361,7 @@ export default function useStreamQueryWithSubscription(
// ignore
}
networkActions.triggerReload();
networkActions.triggerReload("transport");
} else if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_WS_RESTART &&
now - lastWsRestartAtRef.current >= MIN_ESCALATION_INTERVAL_MS

View file

@ -20,6 +20,7 @@ import getRetryMaxAttempts from "./getRetryMaxAttemps";
import { createLogger } from "~/lib/logger";
import { NETWORK_SCOPES } from "~/lib/logger/scopes";
import createCache from "./cache";
const { useNetworkState, networkActions } = store;
@ -28,18 +29,23 @@ const networkProvidersLogger = createLogger({
feature: "NetworkProviders",
});
const sharedApolloCache = createCache();
const initializeNewApolloClient = (reload) => {
if (reload) {
const { apolloClient } = network;
apolloClient.stop();
if (apolloClient.cache !== sharedApolloCache) {
apolloClient.clearStore();
}
}
network.apolloClient = createApolloClient({
store,
GRAPHQL_URL: env.GRAPHQL_URL,
GRAPHQL_WS_URL: env.GRAPHQL_WS_URL,
getRetryMaxAttempts,
cache: sharedApolloCache,
});
};
initializeNewApolloClient();
@ -51,34 +57,60 @@ network.oaFilesKy = oaFilesKy;
export default function NetworkProviders({ children }) {
const [key, setKey] = useState(0);
const [transportClient, setTransportClient] = useState(() => network.apolloClient);
const networkState = useNetworkState(["initialized", "triggerReload"]);
const networkState = useNetworkState([
"initialized",
"triggerReload",
"reloadKind",
"transportGeneration",
]);
useEffect(() => {
if (networkState.triggerReload) {
networkProvidersLogger.debug("Network triggerReload received", {
reloadKind: networkState.reloadKind,
reloadId: store.getAuthState()?.reloadId,
hasUserToken: !!store.getAuthState()?.userToken,
});
const isFullReload = networkState.reloadKind !== "transport";
initializeNewApolloClient(true);
if (isFullReload) {
setTransportClient(network.apolloClient);
setKey((prevKey) => prevKey + 1);
} else {
setTransportClient(network.apolloClient);
networkProvidersLogger.debug("Network transport recovered in place", {
reloadId: store.getAuthState()?.reloadId,
hasUserToken: !!store.getAuthState()?.userToken,
transportGeneration: networkState.transportGeneration,
});
networkActions.onReload();
}
}, [networkState.triggerReload]);
}
}, [
networkState.triggerReload,
networkState.reloadKind,
networkState.transportGeneration,
]);
useEffect(() => {
if (key > 0) {
networkProvidersLogger.debug("Network reloaded", {
reloadKind: networkState.reloadKind,
reloadId: store.getAuthState()?.reloadId,
hasUserToken: !!store.getAuthState()?.userToken,
});
networkActions.onReload();
}
}, [key]);
}, [key, networkState.reloadKind]);
if (!networkState.initialized) {
return <Loader />;
}
const providers = [[ApolloProvider, { client: network.apolloClient }]];
const providers = [[ApolloProvider, { client: transportClient }]];
return (
<ComposeComponents key={key} components={providers}>

View file

@ -19,6 +19,7 @@ if (__DEV__ || process.env.NODE_ENV !== "production") {
}
export default function createApolloClient(options) {
const cache = options.cache || createCache();
const errorLink = createErrorLink(options);
const authLink = createAuthLink(options);
const cancelLink = createCancelLink();
@ -50,8 +51,6 @@ export default function createApolloClient(options) {
httpLink: httpChain,
});
const cache = createCache();
const apolloClient = new ApolloClient({
cache,
// connectToDevTools: true, // Enable dev tools for better debugging

View file

@ -9,20 +9,28 @@ export default createAtom(({ merge, get }) => {
wsLastHeartbeatDate: null,
wsLastRecoveryDate: null,
triggerReload: false,
reloadKind: null,
initialized: true,
hasInternetConnection: true,
transportGeneration: 0,
},
actions: {
triggerReload: () => {
triggerReload: (reloadKind = "full") => {
merge({
initialized: false,
triggerReload: true,
reloadKind,
initialized: reloadKind === "transport" ? true : false,
transportGeneration:
reloadKind === "transport"
? get("transportGeneration") + 1
: get("transportGeneration"),
});
},
onReload: () => {
merge({
initialized: true,
triggerReload: false,
reloadKind: null,
});
},
WSConnected: () => {