sometimes the onError
callback from @apollo/client/link/error
needs to be an async function, for example, to access the AsyncStorage
from React Native, that is my case. So there is a way to pass an async callback to onError
?
@crisf.codes can you share some example code that uses AsyncStorage
and shows when the onError
callback needs to be async?
@hwillson for example there are two cases in which I would like to use asyncstorage
, either there is a 401 response and I want to remove the tokens from storage or I want to refresh the token from storage and I have to fetch another token from the server, that is an async operation.
Remove tokens from storage with 401 response from server
import {setContext} from '@apollo/client/link/context';
import {ErrorResponse, onError} from '@apollo/client/link/error';
import {isJwtError, JWTError} from './errors';
import {getAuthToken, removeTokens} from './utils';
interface ResponseError extends ErrorResponse {
networkError?: Error & {
statusCode?: number;
bodyText?: string;
};
}
export const invalidateTokenLink = onError((error: ResponseError) => {
if (
(error.networkError && error.networkError.statusCode === 401) ||
error.graphQLErrors?.some(isJwtError)
) {
if (error.graphQLErrors[0].extensions.code !== JWTError.expired) {
// this process is async, since it uses asyncstorage to remove the tokens. but imagine I want to
// refresh the tokens, it is still an asynchronous process.
removeTokens();
}
}
});
Got it, thanks @crisf.codes. For your specific case, a workaround some people use is to leverage the setContext
link, which is Promise
friendly. You can see an example of this (using AsyncStorage
) here:
https://github.com/apollographql/apollo-link/issues/190#issuecomment-341875215
...
const httpLink = new HttpLink({ uri: '/graphql' });
let token;
const withToken = setContext(async request => {
if (!token) {
token = await AsyncStorage.getItem('token');
}
return {
headers: {
authorization: token
}
};
});
const resetToken = onError(({ networkError }) => {
if (networkError && networkError.statusCode === 401) {
// remove cached token on 401 from the server
token = undefined;
}
});
const authFlowLink = withToken.concat(resetToken);
const link = authFlowLink.concat(httpLink);
...
If you would like to see this supported in onError
, would you mind opening a feature request? Thanks!
@hwillson thanks to point to this, although this is a possible solution for simple cases, it does not apply to more complex cases, for example what if I need to perform other asynchronous processes such as updating a user’s token if the server’s token has expired. How can I implement this flow?
Take as an example these steps, which are frequently performed if I need to refresh the user token:.
- Send some request with auth token as header.
- If the token’s expired, the server sends back a
401
error code. - Retrieve from storage(Asyncsotage) the refresh token that was stored in login proccess.
- Send the refresh token to the server to generate a new pair of tokens.
- Store new pair of tokens in Asyncstorage.
In some way I need to apply async login in onError
method…
have you found an answer for this?
I need to handle this exact problem too - I don’t see any way around this still. Unless there is some way to run the setContext
in the link chain after the onError
link. I guess then you would need a setContext
link before and after the onError
link.