import { ApolloClient, InMemoryCache, HttpLink, split, from } from '@apollo/client'; import { getMainDefinition } from '@apollo/client/utilities'; import { setContext } from '@apollo/client/link/context'; import { onError, ErrorResponse } from '@apollo/client/link/error'; import { persistCache, AsyncStorageWrapper } from 'apollo3-cache-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { config, metrics } from 'config'; import DebounceLink from 'apollo-link-debounce'; import apolloLogger from 'apollo-link-logger'; import { RetryLink } from '@apollo/client/link/retry'; import { getAccessToken, getNewAccessToken } from 'utils/auth'; import { doAlert } from 'utils/alert'; import QueueLink from 'apollo-link-queue'; import SerializingLink from 'apollo-link-serialize'; import { WebSocketLink } from './WebSocketLink'; import * as typePolicies from './policies'; const { graphql } = config; const { APOLLO } = metrics; const retryLink = new RetryLink({ delay: { initial: 500, max: 2000, jitter: true, }, attempts: { max: 5, retryIf: (error) => !!error, }, }); const serializeLink = new SerializingLink(); export const offlineQueueLink = new QueueLink(); const wsLink = new WebSocketLink(graphql.socketURL); const debounceLink = new DebounceLink(APOLLO.DEBOUNCE_TIMEOUT); const httpLink = new HttpLink({ uri: graphql.url, }); const authLink = setContext(async (_, { headers }) => { const accessToken = await getAccessToken(); return { headers: { ...headers, Authorization: accessToken, }, }; }); const httpLinkWithAuth = authLink.concat(httpLink); const errorLink = onError((response: ErrorResponse) => { const { graphQLErrors, forward, operation } = response; if (graphQLErrors) { const { message } = graphQLErrors[0]; const code = graphQLErrors[0]?.extensions?.code; const errorHandling = async () => { switch (code) { case 'UNAUTHENTICATED': const token = await getNewAccessToken(); if (token) { const oldHeaders = operation.getContext().headers; operation.setContext({ headers: { ...oldHeaders, Authorization: token, }, }); return forward(operation); } else { break; } default: doAlert({ variant: 'error', message: message, }); } return null; }; errorHandling(); } }); const link = split( ({ query }) => { const definition = getMainDefinition(query); const isSubscription = definition.kind === 'OperationDefinition' && definition.operation === 'subscription'; return isSubscription; }, wsLink, httpLinkWithAuth ); /** * * @returns */ export async function createApolloClient() { const cache = new InMemoryCache({ typePolicies, }); await persistCache({ cache, storage: new AsyncStorageWrapper(AsyncStorage), }); const client = new ApolloClient({ link: from([ offlineQueueLink, serializeLink, apolloLogger, debounceLink, errorLink, retryLink, link, ]), cache, defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network', }, }, }); return client; }