@@ -6,12 +6,14 @@ import Foundation
6
6
7
7
class AuthManager : IterableAuthManagerProtocol {
8
8
init ( delegate: IterableAuthDelegate ? ,
9
+ authRetryPolicy: RetryPolicy ,
9
10
expirationRefreshPeriod: TimeInterval ,
10
11
localStorage: LocalStorageProtocol ,
11
12
dateProvider: DateProviderProtocol ) {
12
13
ITBInfo ( )
13
14
14
15
self . delegate = delegate
16
+ self . authRetryPolicy = authRetryPolicy
15
17
self . localStorage = localStorage
16
18
self . dateProvider = dateProvider
17
19
self . expirationRefreshPeriod = expirationRefreshPeriod
@@ -35,26 +37,44 @@ class AuthManager: IterableAuthManagerProtocol {
35
37
hasFailedPriorAuth = false
36
38
}
37
39
38
- func requestNewAuthToken( hasFailedPriorAuth: Bool = false , onSuccess: AuthTokenRetrievalHandler ? = nil ) {
40
+ func requestNewAuthToken( hasFailedPriorAuth: Bool = false ,
41
+ onSuccess: AuthTokenRetrievalHandler ? = nil ,
42
+ shouldIgnoreRetryPolicy: Bool ) {
39
43
ITBInfo ( )
40
44
41
- guard !pendingAuth else {
42
- return
43
- }
44
-
45
- guard !self . hasFailedPriorAuth || !hasFailedPriorAuth else {
45
+ if shouldPauseRetry ( shouldIgnoreRetryPolicy) || pendingAuth || hasFailedAuth ( hasFailedPriorAuth) {
46
46
return
47
47
}
48
48
49
49
self . hasFailedPriorAuth = hasFailedPriorAuth
50
-
51
50
pendingAuth = true
52
51
52
+ if shouldUseLastValidToken ( shouldIgnoreRetryPolicy) {
53
+ // if some JWT retry had valid token it will not fetch the auth token again from developer function
54
+ onAuthTokenReceived ( retrievedAuthToken: authToken, onSuccess: onSuccess)
55
+ return
56
+ }
57
+
53
58
delegate? . onAuthTokenRequested { [ weak self] retrievedAuthToken in
59
+ self ? . pendingAuth = false
60
+ self ? . retryCount+= 1
54
61
self ? . onAuthTokenReceived ( retrievedAuthToken: retrievedAuthToken, onSuccess: onSuccess)
55
62
}
56
63
}
57
64
65
+ private func hasFailedAuth( _ hasFailedPriorAuth: Bool ) -> Bool {
66
+ return self . hasFailedPriorAuth && hasFailedPriorAuth
67
+ }
68
+
69
+ private func shouldPauseRetry( _ shouldIgnoreRetryPolicy: Bool ) -> Bool {
70
+ return ( !shouldIgnoreRetryPolicy && pauseAuthRetry) ||
71
+ ( retryCount >= authRetryPolicy. maxRetry && !shouldIgnoreRetryPolicy)
72
+ }
73
+
74
+ private func shouldUseLastValidToken( _ shouldIgnoreRetryPolicy: Bool ) -> Bool {
75
+ return isLastAuthTokenValid && !shouldIgnoreRetryPolicy
76
+ }
77
+
58
78
func setNewToken( _ newToken: String ) {
59
79
ITBInfo ( )
60
80
@@ -69,6 +89,7 @@ class AuthManager: IterableAuthManagerProtocol {
69
89
storeAuthToken ( )
70
90
71
91
clearRefreshTimer ( )
92
+ isLastAuthTokenValid = false
72
93
}
73
94
74
95
// MARK: - Private/Internal
@@ -79,11 +100,39 @@ class AuthManager: IterableAuthManagerProtocol {
79
100
private var pendingAuth : Bool = false
80
101
private var hasFailedPriorAuth : Bool = false
81
102
103
+ private var authRetryPolicy : RetryPolicy
104
+ private var retryCount : Int = 0
105
+ private var isLastAuthTokenValid : Bool = false
106
+ private var pauseAuthRetry : Bool = false
107
+ private var isTimerScheduled : Bool = false
108
+
82
109
private weak var delegate : IterableAuthDelegate ?
83
110
private let expirationRefreshPeriod : TimeInterval
84
111
private var localStorage : LocalStorageProtocol
85
112
private let dateProvider : DateProviderProtocol
86
113
114
+ func pauseAuthRetries( _ pauseAuthRetry: Bool ) {
115
+ self . pauseAuthRetry = pauseAuthRetry
116
+ resetRetryCount ( )
117
+ }
118
+
119
+ func setIsLastAuthTokenValid( _ isValid: Bool ) {
120
+ isLastAuthTokenValid = isValid
121
+ }
122
+
123
+ func getNextRetryInterval( ) -> Double {
124
+ var nextRetryInterval = Double ( authRetryPolicy. retryInterval)
125
+ if authRetryPolicy. retryBackoff == . exponential {
126
+ nextRetryInterval = Double ( nextRetryInterval) * pow( Const . exponentialFactor, Double ( retryCount - 1 ) )
127
+ }
128
+
129
+ return nextRetryInterval
130
+ }
131
+
132
+ private func resetRetryCount( ) {
133
+ retryCount = 0
134
+ }
135
+
87
136
private func storeAuthToken( ) {
88
137
localStorage. authToken = authToken
89
138
}
@@ -93,67 +142,83 @@ class AuthManager: IterableAuthManagerProtocol {
93
142
94
143
authToken = localStorage. authToken
95
144
96
- queueAuthTokenExpirationRefresh ( authToken)
145
+ _ = queueAuthTokenExpirationRefresh ( authToken)
97
146
}
98
147
99
148
private func onAuthTokenReceived( retrievedAuthToken: String ? , onSuccess: AuthTokenRetrievalHandler ? = nil ) {
100
149
ITBInfo ( )
101
150
102
151
pendingAuth = false
103
152
104
- guard retrievedAuthToken != nil else {
105
- delegate? . onTokenRegistrationFailed ( " auth token was nil, scheduling auth token retrieval in 10 seconds " )
106
-
107
- /// by default, schedule a refresh for 10s
108
- scheduleAuthTokenRefreshTimer ( 10 )
109
-
110
- return
153
+ if retrievedAuthToken != nil {
154
+ let isRefreshQueued = queueAuthTokenExpirationRefresh ( retrievedAuthToken, onSuccess: onSuccess)
155
+ if !isRefreshQueued {
156
+ onSuccess ? ( authToken)
157
+ }
158
+ } else {
159
+ handleAuthFailure ( failedAuthToken: nil , reason: . authTokenNull)
160
+ scheduleAuthTokenRefreshTimer ( interval: getNextRetryInterval ( ) , successCallback: onSuccess)
111
161
}
112
162
113
163
authToken = retrievedAuthToken
114
164
115
165
storeAuthToken ( )
116
-
117
- queueAuthTokenExpirationRefresh ( authToken)
118
-
119
- onSuccess ? ( authToken)
120
166
}
121
167
122
- private func queueAuthTokenExpirationRefresh( _ authToken: String ? ) {
168
+ func handleAuthFailure( failedAuthToken: String ? , reason: AuthFailureReason ) {
169
+ delegate? . onAuthFailure ( AuthFailure ( userKey: IterableUtil . getEmailOrUserId ( ) , failedAuthToken: failedAuthToken, failedRequestTime: IterableUtil . secondsFromEpoch ( for: dateProvider. currentDate) , failureReason: reason) )
170
+ }
171
+
172
+ private func queueAuthTokenExpirationRefresh( _ authToken: String ? , onSuccess: AuthTokenRetrievalHandler ? = nil ) -> Bool {
123
173
ITBInfo ( )
124
174
125
175
clearRefreshTimer ( )
126
176
127
177
guard let authToken = authToken, let expirationDate = AuthManager . decodeExpirationDateFromAuthToken ( authToken) else {
128
- delegate ? . onTokenRegistrationFailed ( " auth token was nil or could not decode an expiration date, scheduling auth token retrieval in 10 seconds " )
178
+ handleAuthFailure ( failedAuthToken : authToken , reason : . authTokenPayloadInvalid )
129
179
130
180
/// schedule a default timer of 10 seconds if we fall into this case
131
- scheduleAuthTokenRefreshTimer ( 10 )
181
+ scheduleAuthTokenRefreshTimer ( interval : getNextRetryInterval ( ) , successCallback : onSuccess )
132
182
133
- return
183
+ return true
134
184
}
135
185
136
186
let timeIntervalToRefresh = TimeInterval ( expirationDate) - dateProvider. currentDate. timeIntervalSince1970 - expirationRefreshPeriod
137
-
138
- scheduleAuthTokenRefreshTimer ( timeIntervalToRefresh)
187
+ if timeIntervalToRefresh > 0 {
188
+ scheduleAuthTokenRefreshTimer ( interval: timeIntervalToRefresh, isScheduledRefresh: true , successCallback: onSuccess)
189
+ return true
190
+ }
191
+ return false
139
192
}
140
193
141
- private func scheduleAuthTokenRefreshTimer( _ interval: TimeInterval ) {
194
+ func scheduleAuthTokenRefreshTimer( interval: TimeInterval , isScheduledRefresh : Bool = false , successCallback : AuthTokenRetrievalHandler ? = nil ) {
142
195
ITBInfo ( )
196
+ if shouldSkipTokenRefresh ( isScheduledRefresh: isScheduledRefresh) {
197
+ // we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work
198
+ return
199
+ }
143
200
144
201
expirationRefreshTimer = Timer . scheduledTimer ( withTimeInterval: interval, repeats: false ) { [ weak self] _ in
202
+ self ? . isTimerScheduled = false
145
203
if self ? . localStorage. email != nil || self ? . localStorage. userId != nil {
146
- self ? . requestNewAuthToken ( hasFailedPriorAuth: false )
204
+ self ? . requestNewAuthToken ( hasFailedPriorAuth: false , onSuccess : successCallback , shouldIgnoreRetryPolicy : isScheduledRefresh )
147
205
} else {
148
206
ITBDebug ( " Email or userId is not available. Skipping token refresh " )
149
207
}
150
208
}
209
+
210
+ isTimerScheduled = true
211
+ }
212
+
213
+ private func shouldSkipTokenRefresh( isScheduledRefresh: Bool ) -> Bool {
214
+ return ( pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled
151
215
}
152
216
153
217
private func clearRefreshTimer( ) {
154
218
ITBInfo ( )
155
219
156
220
expirationRefreshTimer? . invalidate ( )
221
+ isTimerScheduled = false
157
222
expirationRefreshTimer = nil
158
223
}
159
224
0 commit comments