11package com.cornellappdev.uplift.data.auth
22
3+ import android.util.Log
34import com.apollographql.apollo.ApolloClient
45import com.apollographql.apollo.api.ApolloRequest
56import com.apollographql.apollo.api.ApolloResponse
@@ -12,6 +13,8 @@ import kotlinx.coroutines.flow.Flow
1213import kotlinx.coroutines.flow.emitAll
1314import kotlinx.coroutines.flow.first
1415import kotlinx.coroutines.flow.flow
16+ import kotlinx.coroutines.sync.Mutex
17+ import kotlinx.coroutines.sync.withLock
1518import javax.inject.Inject
1619import javax.inject.Named
1720import javax.inject.Singleton
@@ -35,38 +38,60 @@ class ApolloAuthInterceptor @Inject constructor(
3538 private val sessionManager : SessionManager ,
3639 @Named(" refresh" ) private val refreshClient : ApolloClient
3740) : ApolloInterceptor {
41+ private val mutex = Mutex ()
42+
3843 override fun <D : Operation .Data > intercept (
3944 request : ApolloRequest <D >,
4045 chain : ApolloInterceptorChain
4146 ): Flow <ApolloResponse <D >> = flow {
4247 val response = chain.proceed(request).first()
4348
4449 val retryCount = request.executionContext[RetryContext ]?.retryCount ? : 0
50+
4551 // TODO: replace string check with explicit error codes if backend implements
4652 if (response.errors?.any { it.message.contains(" Signature has expired" ) } == true && retryCount < 1 ) {
4753 val refreshToken = tokenManager.getRefreshToken()
4854 if (refreshToken != null ) {
49- try {
50- val mutationResponse = refreshClient.mutation( RefreshAccessTokenMutation ())
51- .addHttpHeader( " Authorization " , " Bearer $refreshToken " )
52- .execute( )
55+ val newAccessToken = mutex.withLock {
56+ // Check if another request already refreshed the token while we were waiting for the lock
57+ val currentAccessToken = tokenManager.getAccessToken( )
58+ val requestToken = request.httpHeaders?.find { it.name == " Authorization " }?.value?.substringAfter( " Bearer " )
5359
54- val newAccessToken = mutationResponse.data?.refreshAccessToken?.newAccessToken
55- if (newAccessToken != null ) {
56- tokenManager.saveTokens(newAccessToken, refreshToken)
57- // Retry the request with the new token
58- val newRequest = request.newBuilder()
59- .addExecutionContext(RetryContext (retryCount + 1 ))
60- .build()
61- emitAll(chain.proceed(newRequest))
62- return @flow
60+ if (currentAccessToken != null && currentAccessToken != requestToken) {
61+ currentAccessToken
6362 } else {
64- sessionManager.logout()
63+ try {
64+ val mutationResponse = refreshClient.mutation(RefreshAccessTokenMutation ())
65+ .addHttpHeader(" Authorization" , " Bearer $refreshToken " )
66+ .execute()
67+
68+ val refreshedToken = mutationResponse.data?.refreshAccessToken?.newAccessToken
69+ if (refreshedToken != null ) {
70+ tokenManager.saveTokens(refreshedToken, refreshToken)
71+ refreshedToken
72+ } else {
73+ Log .e(" ApolloAuthInterceptor" , " Refresh token mutation returned null access token" )
74+ sessionManager.logout()
75+ null
76+ }
77+ } catch (e: Exception ) {
78+ Log .e(" ApolloAuthInterceptor" , " Token refresh failed with exception" , e)
79+ sessionManager.logout()
80+ null
81+ }
6582 }
66- } catch (e: Exception ) {
67- sessionManager.logout()
83+ }
84+
85+ if (newAccessToken != null ) {
86+ // Retry the request with the new token
87+ val newRequest = request.newBuilder()
88+ .addExecutionContext(RetryContext (retryCount + 1 ))
89+ .build()
90+ emitAll(chain.proceed(newRequest))
91+ return @flow
6892 }
6993 } else {
94+ Log .d(" ApolloAuthInterceptor" , " No refresh token available, logging out" )
7095 sessionManager.logout()
7196 }
7297 }
0 commit comments