Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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