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 // Some devices keep the WS transport "connected" after a lock/unlock, but the
// per-operation subscription stops delivering. Trigger a controlled resubscribe. // 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; const FOREGROUND_KICK_MIN_INTERVAL_MS = 15_000;
if ( if (
@ -239,6 +239,15 @@ export default function useLatestWithSubscription(
if (age < livenessStaleMs) return; if (age < livenessStaleMs) return;
const now = Date.now(); 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; if (now - lastLivenessKickAtRef.current < livenessStaleMs) return;
lastLivenessKickAtRef.current = now; lastLivenessKickAtRef.current = now;
@ -276,7 +285,7 @@ export default function useLatestWithSubscription(
// Escalation policy for repeated consecutive stale kicks. // Escalation policy for repeated consecutive stale kicks.
if ( if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD && consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD + 2 &&
now - lastReloadAtRef.current >= MIN_ESCALATION_INTERVAL_MS now - lastReloadAtRef.current >= MIN_ESCALATION_INTERVAL_MS
) { ) {
const lastRecovery = wsLastRecoveryDateRef.current const lastRecovery = wsLastRecoveryDateRef.current
@ -310,7 +319,7 @@ export default function useLatestWithSubscription(
// ignore // ignore
} }
networkActions.triggerReload(); networkActions.triggerReload("transport");
} else if ( } else if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_WS_RESTART && consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_WS_RESTART &&
now - lastWsRestartAtRef.current >= MIN_ESCALATION_INTERVAL_MS 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 // Some devices keep the WS transport "connected" after a lock/unlock, but the
// per-operation subscription stops delivering. Trigger a controlled resubscribe. // 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; const FOREGROUND_KICK_MIN_INTERVAL_MS = 15_000;
if ( if (
@ -281,6 +281,15 @@ export default function useStreamQueryWithSubscription(
}); });
} }
// Avoid spamming resubscribe triggers. // 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; if (now - lastLivenessKickAtRef.current < livenessStaleMs) return;
lastLivenessKickAtRef.current = now; lastLivenessKickAtRef.current = now;
@ -318,7 +327,7 @@ export default function useStreamQueryWithSubscription(
// Escalation policy for repeated consecutive stale kicks. // Escalation policy for repeated consecutive stale kicks.
if ( if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD && consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_RELOAD + 2 &&
now - lastReloadAtRef.current >= MIN_ESCALATION_INTERVAL_MS now - lastReloadAtRef.current >= MIN_ESCALATION_INTERVAL_MS
) { ) {
const lastRecovery = wsLastRecoveryDateRef.current const lastRecovery = wsLastRecoveryDateRef.current
@ -352,7 +361,7 @@ export default function useStreamQueryWithSubscription(
// ignore // ignore
} }
networkActions.triggerReload(); networkActions.triggerReload("transport");
} else if ( } else if (
consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_WS_RESTART && consecutiveStaleKicksRef.current >= STALE_KICKS_BEFORE_WS_RESTART &&
now - lastWsRestartAtRef.current >= MIN_ESCALATION_INTERVAL_MS now - lastWsRestartAtRef.current >= MIN_ESCALATION_INTERVAL_MS

View file

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

View file

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

View file

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