mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at ruby-v 5.8 kB view raw
1import React, {useRef, useState} from 'react' 2import {AppState, AppStateStatus} from 'react-native' 3import AsyncStorage from '@react-native-async-storage/async-storage' 4import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' 5import {focusManager, onlineManager, QueryClient} from '@tanstack/react-query' 6import { 7 PersistQueryClientProvider, 8 PersistQueryClientProviderProps, 9} from '@tanstack/react-query-persist-client' 10 11import {isNative} from '#/platform/detection' 12import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events' 13 14// any query keys in this array will be persisted to AsyncStorage 15export const labelersDetailedInfoQueryKeyRoot = 'labelers-detailed-info' 16const STORED_CACHE_QUERY_KEY_ROOTS = [labelersDetailedInfoQueryKeyRoot] 17 18async function checkIsOnline(): Promise<boolean> { 19 try { 20 const controller = new AbortController() 21 setTimeout(() => { 22 controller.abort() 23 }, 15e3) 24 const res = await fetch('https://public.api.bsky.app/xrpc/_health', { 25 cache: 'no-store', 26 signal: controller.signal, 27 }) 28 const json = await res.json() 29 if (json.version) { 30 return true 31 } else { 32 return false 33 } 34 } catch (e) { 35 return false 36 } 37} 38 39let receivedNetworkLost = false 40let receivedNetworkConfirmed = false 41let isNetworkStateUnclear = false 42 43listenNetworkLost(() => { 44 receivedNetworkLost = true 45 onlineManager.setOnline(false) 46}) 47 48listenNetworkConfirmed(() => { 49 receivedNetworkConfirmed = true 50 onlineManager.setOnline(true) 51}) 52 53let checkPromise: Promise<void> | undefined 54function checkIsOnlineIfNeeded() { 55 if (checkPromise) { 56 return 57 } 58 receivedNetworkLost = false 59 receivedNetworkConfirmed = false 60 checkPromise = checkIsOnline().then(nextIsOnline => { 61 checkPromise = undefined 62 if (nextIsOnline && receivedNetworkLost) { 63 isNetworkStateUnclear = true 64 } 65 if (!nextIsOnline && receivedNetworkConfirmed) { 66 isNetworkStateUnclear = true 67 } 68 if (!isNetworkStateUnclear) { 69 onlineManager.setOnline(nextIsOnline) 70 } 71 }) 72} 73 74setInterval(() => { 75 if (AppState.currentState === 'active') { 76 if (!onlineManager.isOnline() || isNetworkStateUnclear) { 77 checkIsOnlineIfNeeded() 78 } 79 } 80}, 2000) 81 82focusManager.setEventListener(onFocus => { 83 if (isNative) { 84 const subscription = AppState.addEventListener( 85 'change', 86 (status: AppStateStatus) => { 87 focusManager.setFocused(status === 'active') 88 }, 89 ) 90 91 return () => subscription.remove() 92 } else if (typeof window !== 'undefined' && window.addEventListener) { 93 // these handlers are a bit redundant but focus catches when the browser window 94 // is blurred/focused while visibilitychange seems to only handle when the 95 // window minimizes (both of them catch tab changes) 96 // there's no harm to redundant fires because refetchOnWindowFocus is only 97 // used with queries that employ stale data times 98 const handler = () => onFocus() 99 window.addEventListener('focus', handler, false) 100 window.addEventListener('visibilitychange', handler, false) 101 return () => { 102 window.removeEventListener('visibilitychange', handler) 103 window.removeEventListener('focus', handler) 104 } 105 } 106}) 107 108const createQueryClient = () => 109 new QueryClient({ 110 defaultOptions: { 111 queries: { 112 // NOTE 113 // refetchOnWindowFocus breaks some UIs (like feeds) 114 // so we only selectively want to enable this 115 // -prf 116 refetchOnWindowFocus: false, 117 // Structural sharing between responses makes it impossible to rely on 118 // "first seen" timestamps on objects to determine if they're fresh. 119 // Disable this optimization so that we can rely on "first seen" timestamps. 120 structuralSharing: false, 121 // We don't want to retry queries by default, because in most cases we 122 // want to fail early and show a response to the user. There are 123 // exceptions, and those can be made on a per-query basis. For others, we 124 // should give users controls to retry. 125 retry: false, 126 }, 127 }, 128 }) 129 130const dehydrateOptions: PersistQueryClientProviderProps['persistOptions']['dehydrateOptions'] = 131 { 132 shouldDehydrateMutation: (_: any) => false, 133 shouldDehydrateQuery: query => { 134 return STORED_CACHE_QUERY_KEY_ROOTS.includes(String(query.queryKey[0])) 135 }, 136 } 137 138export function QueryProvider({ 139 children, 140 currentDid, 141}: { 142 children: React.ReactNode 143 currentDid: string | undefined 144}) { 145 return ( 146 <QueryProviderInner 147 // Enforce we never reuse cache between users. 148 // These two props MUST stay in sync. 149 key={currentDid} 150 currentDid={currentDid}> 151 {children} 152 </QueryProviderInner> 153 ) 154} 155 156function QueryProviderInner({ 157 children, 158 currentDid, 159}: { 160 children: React.ReactNode 161 currentDid: string | undefined 162}) { 163 const initialDid = useRef(currentDid) 164 if (currentDid !== initialDid.current) { 165 throw Error( 166 'Something is very wrong. Expected did to be stable due to key above.', 167 ) 168 } 169 // We create the query client here so that it's scoped to a specific DID. 170 // Do not move the query client creation outside of this component. 171 const [queryClient, _setQueryClient] = useState(() => createQueryClient()) 172 const [persistOptions, _setPersistOptions] = useState(() => { 173 const asyncPersister = createAsyncStoragePersister({ 174 storage: AsyncStorage, 175 key: 'queryClient-' + (currentDid ?? 'logged-out'), 176 }) 177 return { 178 persister: asyncPersister, 179 dehydrateOptions, 180 } 181 }) 182 return ( 183 <PersistQueryClientProvider 184 client={queryClient} 185 persistOptions={persistOptions}> 186 {children} 187 </PersistQueryClientProvider> 188 ) 189}