149 lines
3.9 KiB
TypeScript
149 lines
3.9 KiB
TypeScript
import type { InMemoryCacheConfig } from '@apollo/client';
|
|
import {
|
|
ApolloClient,
|
|
from,
|
|
split,
|
|
ApolloLink,
|
|
HttpLink,
|
|
InMemoryCache,
|
|
} from '@apollo/client';
|
|
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
|
|
import { getMainDefinition } from '@apollo/client/utilities';
|
|
import { createClient as createWSClient } from 'graphql-ws';
|
|
import { onError } from '@apollo/client/link/error';
|
|
import { RetryLink } from '@apollo/client/link/retry';
|
|
import ApolloLinkTimeout from 'apollo-link-timeout';
|
|
import { localLoggerFactory } from '@vegaprotocol/utils';
|
|
import { useHeaderStore } from './header-store';
|
|
|
|
const isBrowser = typeof window !== 'undefined';
|
|
|
|
const NOT_FOUND = 'NotFound';
|
|
|
|
export type ClientOptions = {
|
|
url?: string;
|
|
cacheConfig?: InMemoryCacheConfig;
|
|
retry?: boolean;
|
|
connectToDevTools?: boolean;
|
|
connectToHeaderStore?: boolean;
|
|
};
|
|
|
|
export function createClient({
|
|
url,
|
|
cacheConfig,
|
|
retry = true,
|
|
connectToDevTools = true,
|
|
connectToHeaderStore = true,
|
|
}: ClientOptions) {
|
|
if (!url) {
|
|
throw new Error('url must be passed into createClient!');
|
|
}
|
|
const urlHTTP = new URL(url);
|
|
const urlWS = new URL(url);
|
|
// Replace http with ws, preserving if its a secure connection eg. https => wss
|
|
urlWS.protocol = urlWS.protocol.replace('http', 'ws');
|
|
|
|
const noOpLink = new ApolloLink((operation, forward) => {
|
|
return forward(operation);
|
|
});
|
|
|
|
const timeoutLink = new ApolloLinkTimeout(10000);
|
|
const enlargedTimeoutLink = new ApolloLinkTimeout(100000);
|
|
|
|
const headerLink = connectToHeaderStore
|
|
? new ApolloLink((operation, forward) => {
|
|
return forward(operation).map((response) => {
|
|
const context = operation.getContext();
|
|
const r = context['response'];
|
|
const blockHeight = r?.headers.get('x-block-height');
|
|
const timestamp = r?.headers.get('x-block-timestamp');
|
|
if (blockHeight && timestamp) {
|
|
const state = useHeaderStore.getState();
|
|
const urlState = state[r.url];
|
|
if (
|
|
!urlState?.blockHeight ||
|
|
urlState.blockHeight !== blockHeight
|
|
) {
|
|
useHeaderStore.setState({
|
|
...state,
|
|
[r.url]: {
|
|
blockHeight: Number(blockHeight),
|
|
timestamp: new Date(Number(timestamp.slice(0, -6))),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
return response;
|
|
});
|
|
})
|
|
: noOpLink;
|
|
|
|
const retryLink = retry
|
|
? new RetryLink({
|
|
delay: {
|
|
initial: 300,
|
|
max: 10000,
|
|
jitter: true,
|
|
},
|
|
})
|
|
: noOpLink;
|
|
|
|
const httpLink = new HttpLink({
|
|
uri: urlHTTP.href,
|
|
credentials: 'same-origin',
|
|
});
|
|
|
|
const wsLink = isBrowser
|
|
? new GraphQLWsLink(
|
|
createWSClient({
|
|
url: urlWS.href,
|
|
})
|
|
)
|
|
: noOpLink;
|
|
|
|
const splitLink = isBrowser
|
|
? split(
|
|
({ query }) => {
|
|
const definition = getMainDefinition(query);
|
|
return (
|
|
definition.kind === 'OperationDefinition' &&
|
|
definition.operation === 'subscription'
|
|
);
|
|
},
|
|
wsLink,
|
|
httpLink
|
|
)
|
|
: httpLink;
|
|
|
|
const errorLink = onError(({ graphQLErrors, networkError }) => {
|
|
if (graphQLErrors) {
|
|
graphQLErrors.forEach((e) => {
|
|
if (e.extensions && e.extensions['type'] !== NOT_FOUND) {
|
|
localLoggerFactory({ application: 'apollo-client' }).error(e);
|
|
}
|
|
});
|
|
}
|
|
if (networkError) {
|
|
localLoggerFactory({ application: 'apollo-client' }).error(networkError);
|
|
}
|
|
});
|
|
|
|
const composedTimeoutLink = split(
|
|
({ getContext }) => Boolean(getContext()['isEnlargedTimeout']),
|
|
enlargedTimeoutLink,
|
|
timeoutLink
|
|
);
|
|
|
|
return new ApolloClient({
|
|
link: from([
|
|
errorLink,
|
|
composedTimeoutLink,
|
|
retryLink,
|
|
headerLink,
|
|
splitLink,
|
|
]),
|
|
cache: new InMemoryCache(cacheConfig),
|
|
connectToDevTools,
|
|
});
|
|
}
|