Skip to content

Instantly share code, notes, and snippets.

@Brettm12345
Created September 12, 2021 18:21
Show Gist options
  • Save Brettm12345/78a070c95fff0c0dceebe4c117707b0e to your computer and use it in GitHub Desktop.
Save Brettm12345/78a070c95fff0c0dceebe4c117707b0e to your computer and use it in GitHub Desktop.
Apollo Provider with Auth and FP-TS
import {
ApolloLink,
ApolloProvider,
ApolloClient,
InMemoryCache,
HttpLink,
split,
FetchResult,
} from '@apollo/client';
import {BatchHttpLink} from '@apollo/client/link/batch-http';
import {getMainDefinition, Observable} from '@apollo/client/utilities';
import {WebSocketLink} from '@apollo/client/link/ws';
import {onError} from '@apollo/client/link/error';
import * as O from 'fp-ts/lib/Option';
import {flow} from 'fp-ts/lib/function';
import React, {FC} from 'react';
import {isValidToken} from '../contexts/AuthContext';
import {RefreshTokenDocument} from '../generated/graphql';
import {GraphQLClient} from 'graphql-request';
const AuthApolloProvider: FC = ({children}) => {
const token =
typeof window !== 'undefined'
? window.localStorage.getItem('accessToken')
: null;
const refreshToken =
typeof window !== 'undefined'
? window.localStorage.getItem('refreshToken')
: null;
const getHeadersFromAccessToken = flow(
O.fromNullable,
O.chain((token: string) => (isValidToken(token) ? O.some(token) : O.none)),
O.map((token: string) => ({
Authorization: `Bearer ${token}`,
})),
O.toNullable
);
const graphqlRequestClient = new GraphQLClient(
'https://hasura.yougotbud.com/v1/graphql'
);
const httpLink = new BatchHttpLink({
uri: 'https://hasura.yougotbud.com/v1/graphql',
headers: getHeadersFromAccessToken(token),
batchMax: 10,
batchInterval: 10,
});
const errorLink = onError(
// @ts-ignore
({graphQLErrors, operation, forward, networkError, response}) => {
// User access token has expired
if (
graphQLErrors &&
graphQLErrors[0]?.extensions?.code === 'invalid-jwt'
) {
// We assume we have both tokens needed to run the async request
if (refreshToken) {
// Let's refresh token through async request
return new Observable<FetchResult>(observer => {
graphqlRequestClient
.request(RefreshTokenDocument, {token: refreshToken})
.then(refreshResponse => {
localStorage.setItem(
'accessToken',
refreshResponse.refresh.token
);
localStorage.setItem(
'refreshToken',
refreshResponse.refresh.refresh_token
);
operation.setContext(({headers = {}}) => ({
headers: {
// Re-add old headers
...headers,
// Switch out old access token for new one
Authorization:
`Bearer ${refreshResponse.refresh.token}` || null,
},
}));
})
.then(() => {
const subscriber = {
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
};
// Retry last failed request
forward(operation).subscribe(subscriber);
})
.catch(error => {
// No refresh or client token available, we force user to login
observer.error(error);
});
});
}
}
}
);
const wsLink =
typeof window !== 'undefined'
? new WebSocketLink({
uri: 'wss://hasura.yougotbud.com/v1/graphql',
options: {
reconnect: true,
lazy: true,
connectionParams: () => ({
headers: getHeadersFromAccessToken(token),
}),
},
})
: null;
const splitLink =
typeof window !== 'undefined'
? split(
({query}) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
)
: httpLink;
const client = new ApolloClient({
link: ApolloLink.from([errorLink, splitLink]),
cache: new InMemoryCache(),
headers: getHeadersFromAccessToken(token),
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
};
export default AuthApolloProvider;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment