-
-
Save MrLoh/1ae9e48ceb595207ecb3cfdb9849c083 to your computer and use it in GitHub Desktop.
// @flow | |
import { ApolloClient } from 'apollo-client'; | |
import { ApolloLink } from 'apollo-link'; | |
import { HttpLink } from 'apollo-link-http'; | |
import { RetryLink } from 'apollo-link-retry'; | |
import { AuthLink } from './link-auth'; | |
import cache from './cache'; | |
const retryLink = new RetryLink(); | |
const authLink = new AuthLink(); | |
const httpLink = new HttpLink({ uri: CINURU_API }); | |
const link = ApolloLink.from([reduxLoggerLink, retryLink, authLink, httpLink]); | |
// construct client | |
const client = new ApolloClient({ link, cache }); | |
// inject client dependencies | |
authLink.injectClient(client); | |
export default client; |
// @flow | |
import { ApolloLink, Observable } from 'apollo-link'; | |
import type { ApolloClient } from 'apollo-client'; | |
import type { Operation, NextLink } from 'apollo-link'; | |
import { getToken, refreshToken } from '../services/auth'; | |
export class AuthLink extends ApolloLink { | |
tokenRefreshingPromise: Promise<boolean> | null; | |
injectClient = (client: ApolloClient): void => { | |
this.client = client; | |
}; | |
refreshToken = (): Promise<boolean> => { | |
if (!this.tokenRefreshingPromise) this.tokenRefreshingPromise = refreshToken(this.client); | |
return this.tokenRefreshingPromise; | |
}; | |
setTokenHeader = (operation: Operation): void => { | |
const token = getToken(); | |
if (token) operation.setContext({ headers: { authorization: `Bearer ${token}` } }); | |
}; | |
request(operation: Operation, forward: NextLink) { | |
// set token in header | |
this.setTokenHeader(operation); | |
// try refreshing token once if it has expired | |
return new Observable(observer => { | |
let subscription, innerSubscription; | |
try { | |
subscription = forward(operation).subscribe({ | |
next: observer.next.bind(observer), | |
complete: observer.complete.bind(observer), | |
error: netowrkError => { | |
if (netowrkError.statusCode === 401) { | |
this.refreshToken().then(success => { | |
if (success) { | |
// set new token and retry operation | |
this.setTokenHeader(operation); | |
innerSubscription = forward(operation).subscribe(observer); | |
} else { | |
// throw error | |
observer.error(new Error('jwt refresh failed')); | |
} | |
}); | |
} else { | |
observer.error(netowrkError); | |
} | |
}, | |
}); | |
} catch (e) { | |
observer.error(e); | |
} | |
return () => { | |
if (subscription) subscription.unsubscribe(); | |
if (innerSubscription) innerSubscription.unsubscribe(); | |
}; | |
}); | |
} | |
} |
I have a little different implementation of that, and I just curios, why we need this construction? Can we just create Promise every time? I was thinking, that's probably needed to prevent redundant token refreshes?
refreshToken = (): Promise<boolean> => {
if (!this.tokenRefreshingPromise) this.tokenRefreshingPromise = refreshToken(this.client);
return this.tokenRefreshingPromise;
};
do you mind sharing the import { getToken, refreshToken } from '../services/auth' file? cheers
@MrLoh error handler for subscription is not getting called
you never set the this.tokenRefreshingPromise to null after calling refreshToken. So does this mean it will only refreshToken once during application lifetime?
you never set the this.tokenRefreshingPromise to null after calling refreshToken. So does this mean it will only refreshToken once during application lifetime?
Yes it does. Solution is to reset the tokenRefreshingPromise in the observer return function, along with the two subscriptions.
if (this.tokenRefreshingPromise) this.tokenRefreshingPromise = null
FWIW I got this working nicely and appreciate the pattern. Bit nervous about updating apollo-link now though.
How one can access the Response Headers when netowrkError.statusCode === 401? For instance, the response contains the WWW-Authenticate header that indicates how to proceed with token refresh.