---
# System prepended metadata

title: 'ErrorLink refresh logic - Thurs, Fri 12th-13th Nov, 2020'
tags: [C+, errorLink, restlink, programmingjournal, '2020', refreshtoken]

---

# ErrorLink refresh logic - Thurs, Fri 12th-13th Nov, 2020

I had a problem in C+ where when multiple requests fire and all fail at once due to a 401 error. This would then kick off that many calls to refresh the auth token, which although worked for the first few, would still error with the later calls, meaning the user would be logged out as that's what I coded to happen in my [Error Link](https://www.apollographql.com/docs/link/links/error/).

So, a better solution is whenever there is a 401 error (meaning token has expired), it should pause any subsequent requests, "save" them, get a fresh token, then retry all those requests.

Due to more a combination of unreleated dev env trouble, this took longer than expected!

```typescript=
/* eslint-disable no-console */
import { onError } from '@apollo/client/link/error'
import crashlytics from '@react-native-firebase/crashlytics'
import { Observable, Operation } from '@apollo/client'
import Securestorage from 'secure-storage'
import env from 'react-native-ultimate-config'

import { getNewTokens } from 'blah'
import { ApiDataResponse } from 'blah.types'

import { logOut } from 'blah'

let pendingRequests: Operation[] = []
let isRefreshing = false

const refreshTokens = async (): Promise<ApiDataResponse> => {
    const refreshToken = await Securestorage.get('refresh_token')
    const newTokens = await getNewTokens(
        env.CLIENT_ID,
        refreshToken!,
        env.CLIENT_SECRET,
        `${env.IDENTITY_URL}/access-token`
    )
    await Promise.all([
        Securestorage.set('access_token', newTokens.access_token),
        Securestorage.set('refresh_token', newTokens.refresh_token),
    ])
    return newTokens
}

export const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
    if (graphQLErrors) {
        graphQLErrors.map(({ message, locations, path }) => {
            const graphQLError = `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
            crashlytics().log(graphQLError)
            return console.error(graphQLError)
        })
    }

    if (networkError && 'statusCode' in networkError && networkError.statusCode === 401) {
        pendingRequests.push(operation)

        // prevent further requests if refreshing access token is in progress
        if (!isRefreshing) {
            isRefreshing = true

            return new Observable((observer) => {
                refreshTokens()
                    .then(({ access_token }) => {
                        for (const operation of pendingRequests) {
                            // replace auth headers with new token
                            operation.setContext(({ headers = {} }: { headers: object }) => ({
                                headers: {
                                    ...headers,
                                    authorization: `Bearer ${access_token}`,
                                },
                            }))

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

                        isRefreshing = false
                        // remove batched requests
                        pendingRequests = []
                    })
                    .catch((error) => {
                        crashlytics().recordError(error)
                        console.warn('Something went wrong with Identity refresh: ', error)
                        logOut('error')
                    })
            })
        }
    }

    if (networkError) {
        const netError = `[Network error]: ${networkError}`
        crashlytics().log(netError)
        console.log(netError)
    }

    return undefined
})

```

My issue now is that tests that tests using the `MockedProvider` now seem to be complaining about missing rest link properties? I need to look into how to mock it!

###### tags: `programmingjournal` `2020` `C+` `errorLink` `refreshtoken` `restlink`