Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
117
fork

Configure Feed

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

at d42a2808ba53a049fc38f559feeddaa5a335f93f 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}