import React from 'react'; import ReactDOM from 'react-dom'; import axios from 'axios'; import createAuthRefreshInterceptor from 'axios-auth-refresh'; import { ApolloProvider } from '@apollo/react-hooks'; import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { HttpLink } from 'apollo-link-http'; import { onError } from 'apollo-link-error'; import { ApolloLink, Observable, fromPromise } from 'apollo-link'; import { getAccessToken, getNewToken, setAccessToken } from 'shared/utils/accessToken'; import App from './App'; // https://able.bio/AnasT/apollo-graphql-async-access-token-refresh--470t1c8 let forward$; let isRefreshing = false; let pendingRequests: any = []; const refreshAuthLogic = (failedRequest: any) => axios.post('http://localhost:3333/auth/refresh_token', {}, { withCredentials: true }).then(tokenRefreshResponse => { setAccessToken(tokenRefreshResponse.data.accessToken); failedRequest.response.config.headers.Authorization = `Bearer ${tokenRefreshResponse.data.accessToken}`; return Promise.resolve(); }); createAuthRefreshInterceptor(axios, refreshAuthLogic); const resolvePendingRequests = () => { pendingRequests.map((callback: any) => callback()); pendingRequests = []; }; const resolvePromise = (resolve: () => void) => { pendingRequests.push(() => resolve()); }; const resetPendingRequests = () => { pendingRequests = []; }; const setRefreshing = (newVal: boolean) => { isRefreshing = newVal; }; const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => { if (graphQLErrors) { for (const err of graphQLErrors) { if (err.extensions && err.extensions.code) { switch (err.extensions.code) { case 'UNAUTHENTICATED': if (!isRefreshing) { setRefreshing(true); forward$ = fromPromise( getNewToken() .then((response: any) => { setAccessToken(response.accessToken); resolvePendingRequests(); return response.accessToken; }) .catch(() => { resetPendingRequests(); // TODO // Handle token refresh errors e.g clear stored tokens, redirect to login, ... return undefined; }) .finally(() => { setRefreshing(false); }), ).filter(value => Boolean(value)); } else { forward$ = fromPromise(new Promise(resolvePromise)); } return forward$.flatMap(() => forward(operation)); default: // pass } } } } if (networkError) { console.log(`[Network error]: ${networkError}`); } return undefined; }); const requestLink = new ApolloLink( (operation, forward) => new Observable((observer: any) => { let handle: any; Promise.resolve(operation) .then((op: any) => { const accessToken = getAccessToken(); if (accessToken) { op.setContext({ headers: { Authorization: `Bearer ${accessToken}`, }, }); } }) .then(() => { handle = forward(operation).subscribe({ next: observer.next.bind(observer), error: observer.error.bind(observer), complete: observer.complete.bind(observer), }); }) .catch(observer.error.bind(observer)); return () => { if (handle) { handle.unsubscribe(); } }; }), ); const client = new ApolloClient({ link: ApolloLink.from([ onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) { graphQLErrors.forEach(({ message, locations, path }) => console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`), ); } if (networkError) { console.log(`[Network error]: ${networkError}`); } }), errorLink, requestLink, new HttpLink({ uri: 'http://localhost:3333/graphql', credentials: 'same-origin', }), ]), cache: new InMemoryCache(), }); ReactDOM.render( , document.getElementById('root'), );