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