Refactor refreshing token

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-07-27 19:55:17 +02:00
parent cb42c3fd51
commit a7753e6a5c
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773

View File

@ -6,9 +6,9 @@ import {
ApolloClient, ApolloClient,
ApolloLink, ApolloLink,
defaultDataIdFromObject, defaultDataIdFromObject,
fromPromise,
InMemoryCache, InMemoryCache,
NormalizedCacheObject, NormalizedCacheObject,
Observable,
split, split,
} from "@apollo/client/core"; } from "@apollo/client/core";
import buildCurrentUserResolver from "@/apollo/user"; import buildCurrentUserResolver from "@/apollo/user";
@ -31,8 +31,8 @@ import { GraphQLError } from "graphql";
// Install the vue plugin // Install the vue plugin
Vue.use(VueApollo); Vue.use(VueApollo);
let refreshingTokenPromise: Promise<boolean> | undefined; let isRefreshing = false;
let alreadyRefreshedToken = false; let pendingRequests: any[] = [];
// Endpoints // Endpoints
const httpServer = GRAPHQL_API_ENDPOINT || "http://localhost:4000"; const httpServer = GRAPHQL_API_ENDPOINT || "http://localhost:4000";
@ -92,19 +92,23 @@ const link = split(
uploadLink uploadLink
); );
const resolvePendingRequests = () => {
pendingRequests.map((callback) => callback());
pendingRequests = [];
};
const errorLink = onError( const errorLink = onError(
({ graphQLErrors, networkError, forward, operation }) => { ({ graphQLErrors, networkError, forward, operation }) => {
if ( if (isServerError(networkError) && networkError?.statusCode === 401) {
isServerError(networkError) && let forwardOperation;
networkError?.statusCode === 401 &&
!alreadyRefreshedToken
) {
if (!refreshingTokenPromise)
refreshingTokenPromise = refreshAccessToken(apolloClient);
return promiseToObservable(refreshingTokenPromise).flatMap(() => { if (!isRefreshing) {
refreshingTokenPromise = undefined; isRefreshing = true;
alreadyRefreshedToken = true;
forwardOperation = fromPromise(
refreshAccessToken(apolloClient)
.then(() => {
resolvePendingRequests();
const context = operation.getContext(); const context = operation.getContext();
const oldHeaders = context.headers; const oldHeaders = context.headers;
@ -115,9 +119,28 @@ const errorLink = onError(
authorization: generateTokenHeader(), authorization: generateTokenHeader(),
}, },
}); });
return true;
})
.catch(() => {
pendingRequests = [];
logout(apolloClient);
return;
})
.finally(() => {
isRefreshing = false;
})
).filter((value) => Boolean(value));
} else {
forwardOperation = fromPromise(
new Promise((resolve) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
pendingRequests.push(() => resolve());
})
);
}
return forward(operation); return forwardOperation.flatMap(() => forward(operation));
});
} }
if (graphQLErrors) { if (graphQLErrors) {
@ -171,41 +194,3 @@ export default new VueApollo({
console.error(error); console.error(error);
}, },
}); });
// Thanks: https://github.com/apollographql/apollo-link/issues/747#issuecomment-502676676
const promiseToObservable = <T>(promise: Promise<T>) =>
new Observable<T>((subscriber) => {
promise.then(
(value) => {
if (subscriber.closed) {
return;
}
subscriber.next(value);
subscriber.complete();
},
(err) => {
console.error("Cannot refresh token.", err);
subscriber.error(err);
logout(apolloClient);
}
);
});
// Manually call this when user log in
// export function onLogin(apolloClient) {
// if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
// }
// Manually call this when user log out
// export async function onLogout() {
// if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
// We don't reset store because we rely on currentUser & currentActor
// which are in the cache (even null). Maybe try to rerun cache init after resetStore?
// try {
// await apolloClient.resetStore();
// } catch (e) {
// // eslint-disable-next-line no-console
// console.log('%cError on cache reset (logout)', 'color: orange;', e.message);
// }
// }