From 57be2ea15b5bea019abf95a590640d688b7a8633 Mon Sep 17 00:00:00 2001
From: dan <dan.abramov@gmail.com>
Date: Tue, 13 Aug 2024 18:51:49 +0100
Subject: Don't kick to login screen on network error (#4911)

* Don't kick the user on network errors

* Track online status for RQ

* Use health endpoint

* Update test with new behavior

* Only poll while offline

* Handle races between the check and network events

* Reduce the poll kickoff interval

* Don't cache partially fetched pinned feeds

This isn't a new issue but it's more prominent with the offline handling. We're currently silently caching pinned infos that failed to fetch. This avoids showing a big spinner on failure but it also kills all feeds which is very confusing. If the request to get feed gens fails, let's fail the whole query.

Then it can be retried.
---
 src/lib/react-query.tsx | 67 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 66 insertions(+), 1 deletion(-)

(limited to 'src/lib/react-query.tsx')

diff --git a/src/lib/react-query.tsx b/src/lib/react-query.tsx
index be507216a..5abfccd7f 100644
--- a/src/lib/react-query.tsx
+++ b/src/lib/react-query.tsx
@@ -2,18 +2,83 @@ import React, {useRef, useState} from 'react'
 import {AppState, AppStateStatus} from 'react-native'
 import AsyncStorage from '@react-native-async-storage/async-storage'
 import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
-import {focusManager, QueryClient} from '@tanstack/react-query'
+import {focusManager, onlineManager, QueryClient} from '@tanstack/react-query'
 import {
   PersistQueryClientProvider,
   PersistQueryClientProviderProps,
 } from '@tanstack/react-query-persist-client'
 
 import {isNative} from '#/platform/detection'
+import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events'
 
 // any query keys in this array will be persisted to AsyncStorage
 export const labelersDetailedInfoQueryKeyRoot = 'labelers-detailed-info'
 const STORED_CACHE_QUERY_KEY_ROOTS = [labelersDetailedInfoQueryKeyRoot]
 
+async function checkIsOnline(): Promise<boolean> {
+  try {
+    const controller = new AbortController()
+    setTimeout(() => {
+      controller.abort()
+    }, 15e3)
+    const res = await fetch('https://public.api.bsky.app/xrpc/_health', {
+      cache: 'no-store',
+      signal: controller.signal,
+    })
+    const json = await res.json()
+    if (json.version) {
+      return true
+    } else {
+      return false
+    }
+  } catch (e) {
+    return false
+  }
+}
+
+let receivedNetworkLost = false
+let receivedNetworkConfirmed = false
+let isNetworkStateUnclear = false
+
+listenNetworkLost(() => {
+  receivedNetworkLost = true
+  onlineManager.setOnline(false)
+})
+
+listenNetworkConfirmed(() => {
+  receivedNetworkConfirmed = true
+  onlineManager.setOnline(true)
+})
+
+let checkPromise: Promise<void> | undefined
+function checkIsOnlineIfNeeded() {
+  if (checkPromise) {
+    return
+  }
+  receivedNetworkLost = false
+  receivedNetworkConfirmed = false
+  checkPromise = checkIsOnline().then(nextIsOnline => {
+    checkPromise = undefined
+    if (nextIsOnline && receivedNetworkLost) {
+      isNetworkStateUnclear = true
+    }
+    if (!nextIsOnline && receivedNetworkConfirmed) {
+      isNetworkStateUnclear = true
+    }
+    if (!isNetworkStateUnclear) {
+      onlineManager.setOnline(nextIsOnline)
+    }
+  })
+}
+
+setInterval(() => {
+  if (AppState.currentState === 'active') {
+    if (!onlineManager.isOnline() || isNetworkStateUnclear) {
+      checkIsOnlineIfNeeded()
+    }
+  }
+}, 2000)
+
 focusManager.setEventListener(onFocus => {
   if (isNative) {
     const subscription = AppState.addEventListener(
-- 
cgit 1.4.1

