-
Notifications
You must be signed in to change notification settings - Fork 3.9k
fix: only treat a reachability confirmation as a recovery when the app was offline #96504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
588f327
6b10b1b
588423a
10b4dcf
469a80f
c35a98b
954a414
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,8 @@ let unsubscribeNetInfo: (() => void) | null = null; | |
| let prevIsInternetReachable: boolean | null | undefined; | ||
| let isPoorConnectionSimulated: boolean | undefined; | ||
| let networkTimeSkew = 0; | ||
| let suppressNextReachabilityRestored = false; | ||
| let pendingReachabilityRecovery = false; | ||
| let configuredReachabilityUrl: string | undefined; | ||
|
|
||
| // Subscriber sets | ||
| const listeners = new Set<() => void>(); | ||
|
|
@@ -150,6 +151,17 @@ function setSustainedFailures(active: boolean) { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * The debug paths clear their hard stop right away, so when the refreshed Ping confirms | ||
| * reachability the app already looks online and the listener would ignore it. The token | ||
| * allows that one recovery. Resetting prev makes the listener see a transition again. | ||
| */ | ||
| function armReachabilityRecovery() { | ||
| prevIsInternetReachable = null; | ||
| pendingReachabilityRecovery = true; | ||
| NetInfo.refresh(); | ||
| } | ||
|
|
||
| /** | ||
| * Called when shouldForceOffline changes in Onyx (debug tool). | ||
| */ | ||
|
|
@@ -159,12 +171,7 @@ function setForceOffline(force: boolean) { | |
| updateState(); | ||
|
|
||
| if (!force) { | ||
| // Reset so the NetInfo listener sees a genuine transition (e.g. null→true) | ||
| // and fires onReachabilityRestored(). Without this, prevIsInternetReachable | ||
| // is already true (we track real state during force-offline) so the listener | ||
| // sees true→true and skips reconnect. | ||
| prevIsInternetReachable = null; | ||
| NetInfo.refresh(); | ||
| armReachabilityRecovery(); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -186,8 +193,7 @@ function setFailAllRequests(failAll: boolean) { | |
| resetFailureCounters(); | ||
| updateState(); | ||
|
|
||
| prevIsInternetReachable = null; | ||
| NetInfo.refresh(); | ||
| armReachabilityRecovery(); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -247,9 +253,7 @@ function simulatePoorConnection(shouldSimulate: boolean) { | |
| simulatedOffline = false; | ||
| updateState(); | ||
|
|
||
| // Reset so NetInfo listener sees a transition and fires onReachabilityRestored(). | ||
| prevIsInternetReachable = null; | ||
| NetInfo.refresh(); | ||
| armReachabilityRecovery(); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -270,32 +274,25 @@ function setRandomNetworkStatus(initialCall = false) { | |
|
|
||
| // --- NetInfo configuration and subscription --- | ||
|
|
||
| function buildReachabilityUrl(): string { | ||
| return `${getCommandURL({command: 'Ping'})}accountID=${accountID ?? 'unknown'}`; | ||
| } | ||
|
|
||
| /** | ||
| * Configure NetInfo with the reachability URL and subscribe to state changes. | ||
| * Must unsubscribe before calling configure() — configure tears down NetInfo internal state. | ||
| */ | ||
| function configureAndSubscribe() { | ||
| // Treat this as a reconfigure (not an initial subscription) when there's already a listener. | ||
| // Reconfigure tears down NetInfo internal state, so the new subscription emits a synthetic | ||
| // null→true transition that would look like a recovery — suppress the next would-be recovery | ||
| // until reachability settles. Initial subscription is left untouched so boot behavior is | ||
| // unchanged (prev=undefined boot guard already covers it). | ||
| // Skip suppression when prev was already false: the app was genuinely offline before | ||
| // reconfigure, so the next true is a real recovery we must not drop (otherwise | ||
| // internetUnreachable stays set and the app is stuck offline until a new outage cycle). | ||
| const isReconfigure = unsubscribeNetInfo !== null; | ||
| if (unsubscribeNetInfo) { | ||
| unsubscribeNetInfo(); | ||
| unsubscribeNetInfo = null; | ||
| } | ||
|
|
||
| if (isReconfigure && prevIsInternetReachable !== false) { | ||
| suppressNextReachabilityRestored = true; | ||
| } | ||
| configuredReachabilityUrl = buildReachabilityUrl(); | ||
|
|
||
| if (!CONFIG.IS_USING_LOCAL_WEB) { | ||
| NetInfo.configure({ | ||
| reachabilityUrl: `${getCommandURL({command: 'Ping'})}accountID=${accountID ?? 'unknown'}`, | ||
| reachabilityUrl: configuredReachabilityUrl, | ||
| reachabilityMethod: 'GET', | ||
| reachabilityTest: (response) => { | ||
| if (!response.ok) { | ||
|
|
@@ -329,24 +326,18 @@ function configureAndSubscribe() { | |
| setInternetUnreachable(true); | ||
| } | ||
|
|
||
| // Treat false→true and null→true as genuine recovery. Both mean NetInfo previously | ||
| // lost reachability (false = confirmed unreachable, null = lost track during outage) | ||
| // and has now confirmed it's back. Only block undefined→true — that's the initial | ||
| // NetInfo event on subscribe which delivers current state, not a recovery. Firing | ||
| // onReachabilityRestored() on boot would duplicate openApp()/reconnectApp(). | ||
| if (!shouldForceOffline && state.isInternetReachable === true && prevIsInternetReachable !== true && prevIsInternetReachable !== undefined) { | ||
| if (suppressNextReachabilityRestored) { | ||
| Log.info(`[NetworkState] Suppressing recovery on first stable state after reconfigure (${prevIsInternetReachable}→true)`); | ||
| } else { | ||
| // Treat a confirmed-reachable event as a recovery only when the app was actually offline | ||
| // or a debug path asked for one. Everything else (boot, re-subscription, refresh() re-emits) | ||
| // is a re-read of an unchanged network and must not fire reconnectApp. | ||
| if (!shouldForceOffline && state.isInternetReachable === true && prevIsInternetReachable !== true) { | ||
| if (getIsOffline() || pendingReachabilityRecovery) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the offline period was caused only by Useful? React with 👍 / 👎. |
||
| pendingReachabilityRecovery = false; | ||
| Log.info(`[NetworkState] Internet reachability restored (${prevIsInternetReachable}→true)`); | ||
| onReachabilityRestored(); | ||
| } else { | ||
| Log.info(`[NetworkState] Ignoring reachability re-confirmation (${prevIsInternetReachable}→true) since the app was never offline`); | ||
| } | ||
| } | ||
| // End the post-reconfigure suppression window once reachability settles into a definitive | ||
| // state. Null/undefined are transient and should not end the window. | ||
| if (state.isInternetReachable === true || state.isInternetReachable === false) { | ||
| suppressNextReachabilityRestored = false; | ||
| } | ||
| prevIsInternetReachable = state.isInternetReachable; | ||
| }); | ||
| } | ||
|
|
@@ -374,15 +365,19 @@ Onyx.connectWithoutView({ | |
| }, | ||
| }); | ||
|
|
||
| // Re-target the reachability ping when the in-app staging-server toggle flips at runtime. | ||
| // queueMicrotask defers configureAndSubscribe so ApiUtils' Onyx callback for the same key — | ||
| // registered later and therefore firing later on the same tick — has already updated | ||
| // shouldUseStagingServer before we re-sample getApiRoot(). Removing the defer causes | ||
| // configureAndSubscribe to read the previous toggle state and invert the URL on every flip. | ||
| // Re-target the reachability ping when the staging-server toggle flips at runtime. | ||
| // queueMicrotask waits for ApiUtils' callback on the same key, which owns the flag behind | ||
| // getApiRoot(). Skip the rebuild when the URL is unchanged: rebuilding tears down NetInfo | ||
| // state and fires extra Pings, and the raw toggle can flip without changing the URL. | ||
| Onyx.connectWithoutView({ | ||
| key: ONYXKEYS.SHOULD_USE_STAGING_SERVER, | ||
| callback: () => { | ||
| queueMicrotask(configureAndSubscribe); | ||
| queueMicrotask(() => { | ||
| if (buildReachabilityUrl() === configuredReachabilityUrl) { | ||
| return; | ||
| } | ||
| configureAndSubscribe(); | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.