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 }