diff --git a/js/src/apollo/utils.ts b/js/src/apollo/utils.ts index d47680ba5..84ba969e9 100644 --- a/js/src/apollo/utils.ts +++ b/js/src/apollo/utils.ts @@ -104,6 +104,11 @@ export async function refreshAccessToken( const refreshToken = localStorage.getItem(AUTH_REFRESH_TOKEN); + if (!refreshToken) { + console.debug("Refresh token not found"); + return false; + } + console.log("Refreshing access token."); try { @@ -118,6 +123,7 @@ export async function refreshAccessToken( return true; } catch (err) { + console.debug("Failed to refresh token"); return false; } } diff --git a/js/src/types/apollo.ts b/js/src/types/apollo.ts deleted file mode 100644 index da8b84b34..000000000 --- a/js/src/types/apollo.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ServerParseError } from "@apollo/client/link/http"; -import { ServerError } from "@apollo/client/link/utils"; - -function isServerError( - err: Error | ServerError | ServerParseError | undefined -): err is ServerError { - return !!err && (err as ServerError).statusCode !== undefined; -} - -export { isServerError }; diff --git a/js/src/utils/auth.ts b/js/src/utils/auth.ts index 2feb49244..4c61a87a5 100644 --- a/js/src/utils/auth.ts +++ b/js/src/utils/auth.ts @@ -95,14 +95,17 @@ export async function initializeCurrentActor( } export async function logout( - apollo: ApolloClient + apollo: ApolloClient, + performServerLogout = true ): Promise { - await apollo.mutate({ - mutation: LOGOUT, - variables: { - refreshToken: localStorage.getItem(AUTH_REFRESH_TOKEN), - }, - }); + if (performServerLogout) { + await apollo.mutate({ + mutation: LOGOUT, + variables: { + refreshToken: localStorage.getItem(AUTH_REFRESH_TOKEN), + }, + }); + } await apollo.mutate({ mutation: UPDATE_CURRENT_USER_CLIENT, diff --git a/js/src/vue-apollo.ts b/js/src/vue-apollo.ts index 9b1a7714c..abfa694f6 100644 --- a/js/src/vue-apollo.ts +++ b/js/src/vue-apollo.ts @@ -11,8 +11,8 @@ import { NormalizedCacheObject, split, } from "@apollo/client/core"; +import { RetryLink } from "@apollo/client/link/retry"; import buildCurrentUserResolver from "@/apollo/user"; -import { isServerError } from "@/types/apollo"; import { AUTH_ACCESS_TOKEN } from "@/constants"; import { logout } from "@/utils/auth"; import { Socket as PhoenixSocket } from "phoenix"; @@ -61,9 +61,17 @@ const authMiddleware = new ApolloLink((operation, forward) => { return null; }); +const customFetch = async (uri: string, options: any) => { + const response = await fetch(uri, options); + if (response.status >= 400) { + return Promise.reject(response.status); + } + return response; +}; + const uploadLink = createLink({ uri: httpEndpoint, - fetch, + fetch: customFetch, }); const phoenixSocket = new PhoenixSocket(wsEndpoint, { @@ -97,17 +105,35 @@ const resolvePendingRequests = () => { pendingRequests = []; }; +const isAuthError = (graphQLError: GraphQLError | undefined) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return graphQLError && [403, 401].includes(graphQLError.status_code); +}; + const errorLink = onError( ({ graphQLErrors, networkError, forward, operation }) => { - if (isServerError(networkError) && networkError?.statusCode === 401) { + console.debug("We have an apollo error", [graphQLErrors, networkError]); + if ( + graphQLErrors?.some((graphQLError) => isAuthError(graphQLError)) || + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + networkError === 401 + ) { + console.debug("It's a authorization error (statusCode 401)"); let forwardOperation; if (!isRefreshing) { + console.debug("Setting isRefreshing to true"); isRefreshing = true; forwardOperation = fromPromise( refreshAccessToken(apolloClient) - .then(() => { + .then((res) => { + if (res !== true) { + // failed to refresh the token + throw "Failed to refresh the token"; + } resolvePendingRequests(); const context = operation.getContext(); @@ -121,9 +147,11 @@ const errorLink = onError( }); return true; }) - .catch(() => { + .catch((e) => { + console.debug("Something failed, let's logout", e); pendingRequests = []; - logout(apolloClient); + // don't perform a logout since we don't have any working access/refresh tokens + logout(apolloClient, false); return; }) .finally(() => { @@ -161,7 +189,12 @@ const errorLink = onError( } ); -const fullLink = authMiddleware.concat(errorLink).concat(link); +const retryLink = new RetryLink(); + +const fullLink = authMiddleware + .concat(retryLink) + .concat(errorLink) + .concat(link); const cache = new InMemoryCache({ addTypename: true,