# All files / atlas/src/components Atlas.tsx

26.32% Statements 10/38
7.14% Branches 1/14
11.11% Functions 1/9
26.32% Lines 10/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137

    2x

    2x

    2x

    2x
    2x
    2x

    2x

    2x

    2x

    2x

     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);