import {
focusManager,
QueryClient,
QueryClientProvider,
QueryKey,
usePrefetchQuery,
} from '@tanstack/react-query'
import React, { useContext, useEffect, useRef } from 'react'
import { ViewProps } from 'react-native'
import { ApiClient, useApiClient } from '../hooks/use_api_client'
import { useAppState } from '../hooks/use_app_state'
import { appGrantsQueryOptions } from '../hooks/use_chat_permissions'
import { featuresQueryOptions } from '../hooks/use_features'
import { useJoltForegroundPing } from '../hooks/use_jolt'
import { RequestQueryKey } from '../hooks/use_suspense_api'
import { emitAuthRefresh } from '../utils/auth_events'
import { ResponseError } from '../utils/response_error'
import { ChatContext, ChatContextValue } from './chat_context'
let apiClient: ApiClient | undefined
const defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {
if (!apiClient) {
throw new Error('No token present')
}
const [url, data, headers, app = 'chat'] = queryKey as RequestQueryKey
return apiClient[app].get({ url, data, headers })
}
const configuredRetry = (failureCount: number, error: any) => {
if (is429Error(error)) return false
return failureCount < 3
}
const is429Error = (error: any): boolean => {
return error instanceof ResponseError && error.status === 429
}
export const chatQueryClient = new QueryClient({
defaultOptions: {
queries: {
queryFn: defaultQueryFn,
retry: configuredRetry,
},
},
})
export function ApiProvider({ children }: ViewProps) {
const { token, env, session } = useContext(ChatContext)
const sessionChanged = useSessionChanged({ token, env })
const client = useApiClient()
useEffect(() => {
if (!sessionChanged) return
emitAuthRefresh()
if (chatQueryClient.isFetching()) {
chatQueryClient.cancelQueries().then(() => chatQueryClient.invalidateQueries())
return
}
chatQueryClient.clear()
}, [sessionChanged])
useEffect(() => {
apiClient = client
}, [client])
return (
{session.isAuthenticated && }
{children}
)
}
// Component to prefetch queries when the app is focused
// This needs to live in the provider so that it can access the api client
// and the chat query client
const PrefetchQueries = () => {
const client = useApiClient()
usePrefetchQuery(appGrantsQueryOptions(client))
usePrefetchQuery(featuresQueryOptions(client))
return null
}
const JoltForegroundPing = () => {
useJoltForegroundPing()
return null
}
export function useFocusManager() {
const appState = useAppState()
useEffect(() => {
focusManager.setFocused(appState === 'active')
}, [appState])
return appState
}
function useSessionChanged(value: Pick): boolean {
const { token: newToken, env: newEnv } = value
const { token: prevToken, env: prevEnv } = usePrevious(value)
const isInitialLogin = !prevToken && !!newToken
if (isInitialLogin) return false
const tokenChanged = Boolean(
prevToken?.access_token && newToken?.access_token !== prevToken?.access_token
)
const envChanged = Boolean(prevEnv && newEnv !== prevEnv)
return tokenChanged || envChanged
}
function usePrevious(value: T): T {
const ref = useRef(value)
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}