import React from 'react' ; import axios from 'axios' ; import { SnackbarKey , SnackbarProvider } from 'notistack' ; import { Provider } from 'react-redux' ; import { combineReducers , createStore , applyMiddleware } from 'redux' ; import { persistStore , persistReducer } from 'redux-persist' ; import storage from 'redux-persist/lib/storage' ; import { PersistGate } from 'redux-persist/integration/react' ; import { createLogger } from 'redux-logger' ; import { HttpLink , InMemoryCache , ApolloClient } from 'apollo-client-preset' ; import { ApolloLink , fromPromise } from 'apollo-link' ; import { onError } from 'apollo-link-error' ; import { ApolloProvider } from 'react-apollo' ; import './style.scss' ; import Routes from './routes' ; import { HTTP_BACKEND_URL , ACCESS_TOKEN_STORAGE_KEY , ID_TOKEN_STORAGE_KEY , TOKEN_URL } from './constants' ; import { refreshToken , removeTokens } from 'oauth/tokenManager' ; import { ISimpleAction } from 'types' ; import { logout as logoutAction } from 'services/user/actions' ; import fhirReducer from './services/fhir' ; import exploredTableReducer from './services/exploredTable/reducer' ; import batchListReducer from './services/batchList/reducer' ; import scriptListReducer from './services/scripts/reducer' ; import recommendedColumns from './services/recommendedColumns/reducer' ; import selectedNodeReducer from './services/selectedNode/reducer' ; import resourceAttributesReducer from 'services/resourceAttributes/reducer' ; import userReducer from './services/user/reducer' ; import mimic from './components/mimic/reducer' ; import { Button } from '@material-ui/core' ; import { Icon } from '@blueprintjs/core' ; const middlewares = [ function thunkMiddleware ( { dispatch , getState } ) { return function ( next ) { return function ( action ) { return typeof action === 'function' ? action ( dispatch , getState ) : next ( action ) ; } ; } ; } ] ; if ( process . env . NODE_ENV === 'development' ) { middlewares . push ( createLogger ( { } ) ) ; } const dataReducer = combineReducers ( { recommendedColumns } ) ; const viewReducer = combineReducers ( { mimic } ) ; const mainReducer = combineReducers ( { data : dataReducer , exploredTable : exploredTableReducer , fhir : fhirReducer , batchList : batchListReducer , scriptList : scriptListReducer , selectedNode : selectedNodeReducer , resourceAttributes : resourceAttributesReducer , views : viewReducer , user : userReducer } ) ; const persistConfig = { key : 'root' , storage } ; const persistedReducer = persistReducer ( persistConfig , mainReducer ) ; const finalCreateStore = applyMiddleware ( ... middlewares ) ( createStore ) ; const store = finalCreateStore ( persistedReducer ) ; const persistor = persistStore ( store ) ; const redirectToLogin = ( ) => { removeTokens ( ) ; store . dispatch ( logoutAction ( ) as ISimpleAction ) ; } ; axios . interceptors . request . use ( config => { if ( ! config . headers . Authorization || ! config . headers . Authorization . startsWith ( 'Bearer' ) ) { const accessToken = localStorage . getItem ( ACCESS_TOKEN_STORAGE_KEY ) ; config . headers . Authorization = `Bearer ${ accessToken }` ; } return config ; } ) ; axios . interceptors . response . use ( response => { return response ; } , async ( error ) => { const originalRequest = error . config ; if ( error . response . status === 401 && originalRequest . url . startsWith ( TOKEN_URL ) ) { redirectToLogin ( ) ; return Promise . reject ( error ) ; } if ( error . response . status === 401 && ! originalRequest . _retry ) { originalRequest . _retry = true ; const success = await refreshToken ( ) ; if ( ! success ) { redirectToLogin ( ) ; return Promise . reject ( error ) ; } return axios ( originalRequest ) ; } return Promise . reject ( error ) ; } ) ; const httpLink = new HttpLink ( { uri : HTTP_BACKEND_URL , fetch : fetch } ) ; const middlewareLink = new ApolloLink ( ( operation , forward ) => { const accessToken = localStorage . getItem ( ACCESS_TOKEN_STORAGE_KEY ) ; const idToken = localStorage . getItem ( ID_TOKEN_STORAGE_KEY ) ; operation . setContext ( { headers : { Authorization : accessToken ? `Bearer ${ accessToken }` : '' , IdToken : idToken ? idToken : '' } } ) ; return forward ( operation ) ; } ) ; const httpLinkAuth = middlewareLink . concat ( httpLink ) ; const errorLink = onError ( ( { graphQLErrors , networkError } ) => { if ( graphQLErrors ) graphQLErrors . map ( ( { message , locations , path } ) => console . log ( `[GraphQL error]: Message: ${ message }, Location: ${ locations }, Path: ${ path }` ) ) ; if ( networkError ) console . log ( `[Network error]: ${ networkError }` ) ; } ) ; const tokenExpiredLink = onError ( ( { graphQLErrors } ) => { if ( graphQLErrors ) { for ( const err of graphQLErrors ) { if ( err . statusCode === 401 ) { redirectToLogin ( ) ; } } } } ) ; const afterwareLink = onError ( ( { graphQLErrors , operation , forward } ) => { if ( graphQLErrors ) { for ( const err of graphQLErrors ) { if ( err . statusCode === 401 ) { return fromPromise ( refreshToken ( ) ) . flatMap ( ( ) => { const oldHeaders = operation . getContext ( ) . headers ; operation . setContext ( { headers : { ... oldHeaders , Authorization : `Bearer ${ localStorage . getItem ( ACCESS_TOKEN_STORAGE_KEY ) }` } } ) ; return forward ( operation ) ; } ) ; } } } } ) ; const links = [ ] ; if ( process . env . NODE_ENV === 'development' ) { links . push ( errorLink ) ; } links . push ( tokenExpiredLink ) ; links . push ( afterwareLink ) ; links . push ( httpLinkAuth ) ; export const client = new ApolloClient ( { cache : new InMemoryCache ( ) , connectToDevTools : true , link : ApolloLink . from ( links ) } ) ; const notistackRef = React . createRef < SnackbarProvider > ( ) ; const onClickDismiss = ( key ) => ( ) => { notistackRef . current ! . closeSnackbar ( key ) ; } ; const App = ( ) : React . ReactElement => ( < Provider store = { store } > < ApolloProvider client = { client } > < PersistGate loading = { null } persistor = { persistor } > < SnackbarProvider ref = { notistackRef } action = { key => ( < Button onClick = { onClickDismiss ( key ) } > < Icon icon = "cross" / > < /Button>)}> < Routes / > < /SnackbarProvider> < /PersistGate> < /ApolloProvider> < /Provider>); export default App ;