1import { 2 cacheExchange, 3 Client, 4 CombinedError as GraphqlError, 5 createClient as createUrqlClient, 6 dedupExchange, 7 fetchExchange, 8 OperationContext, 9 OperationResult, 10 PromisifiedSource, 11 TypedDocumentNode, 12} from '@urql/core'; 13import { retryExchange } from '@urql/exchange-retry'; 14import { DocumentNode } from 'graphql'; 15import fetch from 'node-fetch'; 16 17import * as Log from '../../log'; 18import { getExpoApiBaseUrl } from '../endpoint'; 19import UserSettings from '../user/UserSettings'; 20 21type AccessTokenHeaders = { 22 authorization: string; 23}; 24 25type SessionHeaders = { 26 'expo-session': string; 27}; 28 29export const graphqlClient = createUrqlClient({ 30 url: getExpoApiBaseUrl() + '/graphql', 31 exchanges: [ 32 dedupExchange, 33 cacheExchange, 34 retryExchange({ 35 maxDelayMs: 4000, 36 retryIf: (err) => 37 !!(err && (err.networkError || err.graphQLErrors.some((e) => e?.extensions?.isTransient))), 38 }), 39 fetchExchange, 40 ], 41 // @ts-ignore Type 'typeof fetch' is not assignable to type '(input: RequestInfo, init?: RequestInit | undefined) => Promise<Response>'. 42 fetch, 43 fetchOptions: (): { headers?: AccessTokenHeaders | SessionHeaders } => { 44 const token = UserSettings.getAccessToken(); 45 if (token) { 46 return { 47 headers: { 48 authorization: `Bearer ${token}`, 49 }, 50 }; 51 } 52 const sessionSecret = UserSettings.getSession()?.sessionSecret; 53 if (sessionSecret) { 54 return { 55 headers: { 56 'expo-session': sessionSecret, 57 }, 58 }; 59 } 60 return {}; 61 }, 62}) as StricterClient; 63 64/* Please specify additionalTypenames in your Graphql queries */ 65export interface StricterClient extends Client { 66 // eslint-disable-next-line @typescript-eslint/ban-types 67 query<Data = any, Variables extends object = {}>( 68 query: DocumentNode | TypedDocumentNode<Data, Variables> | string, 69 variables: Variables | undefined, 70 context: Partial<OperationContext> & { additionalTypenames: string[] } 71 ): PromisifiedSource<OperationResult<Data, Variables>>; 72} 73 74export async function withErrorHandlingAsync<T>(promise: Promise<OperationResult<T>>): Promise<T> { 75 const { data, error } = await promise; 76 77 if (error) { 78 if (error.graphQLErrors.some((e) => e?.extensions?.isTransient)) { 79 Log.error(`We've encountered a transient error, please try again shortly.`); 80 } 81 throw error; 82 } 83 84 // Check for a malformed response. This only checks the root query's existence. It doesn't affect 85 // returning responses with an empty result set. 86 if (!data) { 87 throw new Error('Returned query result data is null!'); 88 } 89 90 return data; 91} 92 93export { GraphqlError }; 94