@@ -2,10 +2,9 @@ package be.appwise.networking.interceptors
22
33import be.appwise.networking.NetworkConstants
44import be.appwise.networking.Networking
5- import be.appwise.networking.NetworkingUtil
5+ import be.appwise.networking.NetworkingUtil.responseCount
66import be.appwise.networking.model.AccessToken
77import com.orhanobut.logger.Logger
8- import kotlinx.coroutines.runBlocking
98import okhttp3.Authenticator
109import okhttp3.Request
1110import okhttp3.Response
@@ -16,77 +15,95 @@ class Authenticator(
1615) : Authenticator {
1716 private var callsWithoutToken = 0
1817
19- override fun authenticate (route : Route ? , response : Response ): Request ? {
20- val request = response.request
21- Logger .t(" Authenticator" ).d(" Authenticate id: request: $request " )
18+ private fun extractBearerToken (authHeader : String? ): String? {
19+ return authHeader?.takeIf { it.startsWith(" Bearer " , ignoreCase = true ) }
20+ ?.substring(" Bearer " .length)
21+ }
2222
23+ override fun authenticate (route : Route ? , response : Response ): Request ? {
24+ val originalRequest = response.request
25+ val requestUrl = originalRequest.url
26+ Logger .t(" Authenticator" ).d(" Authenticate triggered for $requestUrl " )
2327
24- // Get the current AccessToken
25- val accessToken = Networking .getAccessToken()?.access_token
28+ val failedToken = extractBearerToken(
29+ originalRequest.header(NetworkConstants .HEADER_KEY_AUTHORIZATION )
30+ )
2631
27- Logger .t(" Authenticator" ).d(" Accesstoken for $request token: $accessToken " )
2832
29- if (accessToken == null || accessToken.isEmpty ()) {
33+ if (failedToken.isNullOrEmpty ()) {
3034 // just try the call without
3135
3236 // TODO: there is still something wrong with the way we handle this...
3337 // Because the client is only created once in the application's lifecycle the counter is persisted...
3438 callsWithoutToken++
35- return if (callsWithoutToken >= 2 ) null else request
39+ return if (callsWithoutToken >= 2 ) null else originalRequest
3640 }
3741
3842 // This block tries to refresh the token and then retry the call with the new token
3943 // the 'synchronized' means that when other calls try to refresh the token as well they will wait until the first refresh happened
4044 //
4145 // Look at http://tutorials.jenkov.com/java-concurrency/synchronized.html for more information
4246 synchronized(this ) {
43- // Logger.t("Authenticator").d("Synchronized, request: $request")
47+ val threadId = Thread .currentThread().id
48+ Logger .t(" Authenticator" ).d(" Synchronized [$threadId ] Sync block entered for: $requestUrl " )
4449
45- // Get the current AccessToken, this might be different as the previous one because of the 'synchronized' method
46- val newToken = Networking .getAccessToken()
47-
48- Logger .t(" Authenticator" ).d(" Synchronized, request: $request , newtoken: ${newToken?.access_token} " )
50+ val currentToken = Networking .getAccessToken()
51+ val currentAccessToken = currentToken?.access_token
4952
5053 // Check if the request was previously made as an authenticated request
51- if (response.request.header(NetworkConstants .HEADER_KEY_AUTHORIZATION ) != null ) {
52- // If the token has changed since the request was made, use the new token
53- if (newToken?.access_token != accessToken) {
54- Logger .t(" Authenticator" ).d(" Same tokens" )
55-
56- return request.newBuilder()
57- .header(
58- NetworkConstants .HEADER_KEY_AUTHORIZATION ,
59- " ${newToken?.token_type} ${newToken?.access_token} "
60- )
61- .build()
62- }
63-
64- // In case this is the first time this line gets hit refresh the AccessToken
65- val updatedToken = onRefreshToken(newToken.refresh_token ? : " " )
66-
67- Logger .t(" Authenticator" ).d(" Updated token: ${updatedToken?.access_token} " )
68-
69- if (updatedToken == null || NetworkingUtil .responseCount(response) >= 2 ) {
70- // refresh failed , maybe you can logout user
71- // returning null is critical here , because if you do not return null
72- // it will try to refresh token continuously like 1000 times.
73- // also you can try 2-3-4 times by depending you before logging out your user
74- Networking .logout()
75- } else {
76- // Refreshing the token worked, save the new one
77- Networking .saveAccessToken(updatedToken)
78-
79- // Retry the request with the new token
80- return request.newBuilder()
81- .header(
82- NetworkConstants .HEADER_KEY_AUTHORIZATION ,
83- " ${updatedToken.token_type} ${updatedToken.access_token} "
84- )
85- .build()
86- }
54+ if (response.request.header(NetworkConstants .HEADER_KEY_AUTHORIZATION ) == null ) return null
55+
56+ // If the token has changed since the request was made, use the new token
57+ if (currentAccessToken != failedToken) {
58+ Logger .t(" Authenticator" ).i(" [$threadId ] Token already refreshed by another thread ($requestUrl ). Current: $currentAccessToken , Failed with: $failedToken . Retrying with current token." )
59+
60+ return originalRequest.newBuilder()
61+ .header(
62+ NetworkConstants .HEADER_KEY_AUTHORIZATION ,
63+ " ${currentToken?.token_type} ${currentToken?.access_token} "
64+ )
65+ .build()
8766 }
88- }
8967
90- return null
68+ Logger .t(" Authenticator" ).i(" [$threadId ] Token needs refresh ($requestUrl ). Token that failed: $failedToken " )
69+
70+ // check response count
71+ if (responseCount(response) > 1 ) {
72+ Logger .t(" Authenticator" ).w(" [$threadId ] Retry limit (1 refresh attempt) reached for $requestUrl . Aborting refresh and logging out." )
73+ Networking .logout()
74+ return null
75+ }
76+
77+ // check if we have a refresh token
78+ val refreshToken = currentToken.refresh_token
79+ if (refreshToken.isNullOrBlank()) {
80+ Logger .t(" Authenticator" ).e(" [$threadId ] No valid refresh token available for $requestUrl . Cannot refresh. Logging out." )
81+ Networking .logout()
82+ return null
83+ }
84+
85+ val updatedToken = onRefreshToken(refreshToken)
86+
87+ if (updatedToken == null ) {
88+ // refresh failed , maybe you can logout user
89+ // returning null is critical here , because if you do not return null
90+ // it will try to refresh token continuously like 1000 times.
91+ // also you can try 2-3-4 times by depending you before logging out your user
92+ Logger .t(" Authenticator" ).e(" [$threadId ] onRefreshToken failed for $requestUrl . Logging out." )
93+ Networking .logout()
94+ return null
95+ } else {
96+ Logger .t(" Authenticator" ).i(" [$threadId ] Refresh SUCCESS for $requestUrl . Saving new token: ${updatedToken.access_token} . Retrying request." )
97+ Networking .saveAccessToken(updatedToken)
98+
99+ // Retry the request with the new token
100+ return originalRequest.newBuilder()
101+ .header(
102+ NetworkConstants .HEADER_KEY_AUTHORIZATION ,
103+ " ${updatedToken.token_type} ${updatedToken.access_token} "
104+ )
105+ .build()
106+ }
107+ }
91108 }
92109}
0 commit comments