import {
RacingSansOne_400Regular
} from '@expo-google-fonts/racing-sans-one';
import { NavigationContainer } from '@react-navigation/native';
import axios, { AxiosRequestConfig } from 'axios';
import { useFonts } from 'expo-font';
import { getItemAsync } from 'expo-secure-store';
import { observer } from 'mobx-react';
import React, { useEffect, useRef, useState } from 'react';
import { AppState } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Splash } from '../components/Splash';
import { API_URL, reportAxiosError } from '../globals';
import { useAuth } from '../hooks/useAuth';
import UnauthorizedNavigator from '../navigation/UnauthorizedNavigator';
import AuthorizedNavigator from '../navigation/AuthorizedNavigator';
import { authStore } from '../stores/AuthStore';
export enum TokenState {
CheckingToken,
ValidToken,
InvalidToken
}
const queryClient = new QueryClient();
/**
* Sub-root component of the app. Contains all global providers (NavigationContainer and SafeAreaProvider for React Navigation, QueryClientProvider for react-query) and is responsible for restricting unauthenticated users to the Intro screen by listening to {@link AuthStore}'s accessToken value.
* @component
*/
const Atlas : React.FC = () => {
/**
* Ref that keeps track of the app's state (opened or closed)
*/
const appState = useRef(AppState.currentState);
/**
* Flag that is switched on when the app is checking for tokens in the keystore and in memory. When true, "Logging you in.." and a spinner will be displayed to the user.
*/
const [checkingToken, setCheckingToken] = useState<boolean>(true);
const { refreshAccessToken } = useAuth();
const [fontsLoaded, error] = useFonts({
RacingSansOne_400Regular
});
/**
* Checks if there is an access token available in {@link AuthStore}, then checks if that access token is valid by calling the API.
* If the response is valid, the access token will be stored in memory, otherwise the user will be directed to intro screen.
*/
const checkToken = async () => {
// check both the mobx store and secure storage for the token
let currentAccessToken = authStore.accessToken;
if (!currentAccessToken) {
currentAccessToken = await getItemAsync('accessToken');
}
if (currentAccessToken) {
// check to see if the token is valid by making test call
const requestConfig: AxiosRequestConfig = {
method: 'GET',
url: API_URL + "/api/me/",
headers: { "Authorization": "Bearer " + currentAccessToken }
};
try {
await axios(requestConfig);
await authStore.setAccessTokenAsync(currentAccessToken);
} catch (error) {
// check if access token can be refreshed
if (error.response.status == 401) {
try {
await refreshAccessToken();
// update authorization header w/ new token
await axios({...requestConfig, headers: { "Authorization": "Bearer " + authStore.accessToken }});
} catch (error) {
await authStore.setAccessTokenAsync(null);
}
}
// something went wrong with the api call, log error and delete access token
reportAxiosError('Something went wrong when retrieving an access token', error)
await authStore.setAccessTokenAsync(null);
}
}
else {
// no access token was found, user will be taken to login
await authStore.setAccessTokenAsync(null);
}
setCheckingToken(false);
}
useEffect(() => {
/**
* useEffect hook that is responsible for registering an appState "change" handler that will call {@linkcode checkToken} each time the app is opened or closed on the device.
* @memberOf Atlas
*/
function registerAppStateChangeHandler() {
AppState.addEventListener("change", (appState: string) => {
if (appState == 'active') {
checkToken();
}
});
return () => {
AppState.removeEventListener("change", (appState: string) => {
if (appState == 'active') {
checkToken();
}
});
};
}
registerAppStateChangeHandler();
}, []);
useEffect(() => {
/**
* Calls {@linkcode checkToken} when a change to the access token stored in {@link AuthStore} is detected.
* @memberOf Atlas
*/
const checkTokenOnAccessTokenChange = () => {
checkToken()
}
checkTokenOnAccessTokenChange()
}, [authStore.accessToken]);
return (
<SafeAreaProvider>
<NavigationContainer>
{checkingToken ? <Splash/> :
<QueryClientProvider client={queryClient}>
{authStore.accessToken ? <AuthorizedNavigator /> : <UnauthorizedNavigator /> }
</QueryClientProvider> }
</NavigationContainer>
</SafeAreaProvider>
);
}
export default observer(Atlas);
Source