Skip to content

Commit b90f1bd

Browse files
authored
Merge pull request #772 from Iterable/JWT_Improvement_Part1
Jwt improvement part1
2 parents 832bcc0 + 4bd7f14 commit b90f1bd

26 files changed

+283
-128
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ build2/
1818
# needed integration testing
1919
env_vars.sh
2020
CI.swift
21+
*.swiftmodule
22+
*.timestamp
23+
*.modulemap
24+
*.pcm
25+
.build/arm64-apple-macosx/debug/IterableSDK.build/DerivedSources/resource_bundle_accessor.swift
26+
.build/arm64-apple-macosx/debug/index/db/v13/p25195--4de704/lock.mdb
27+
.build/arm64-apple-macosx/debug/index/db/v13/p25195--4de704/data.mdb
28+
.build/workspace-state.json
29+
*.mdb

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
All notable changes to this project will be documented in this file.
44
This project adheres to [Semantic Versioning](http://semver.org/).
55

6+
## [6.5.3]
7+
### Changed
8+
- Deprecated support for iOS 10 and iOS 11.
9+
610
## [6.5.2]
711
### Fixed
812
- The Privacy Manifest has been relocated to the resources folder within the SDK. This adjustment facilitates the inclusion of the SDK's privacy manifest in the generation process.

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ GEM
281281

282282
PLATFORMS
283283
arm64-darwin-21
284+
arm64-darwin-22
284285
x86_64-darwin-20
285286

286287
DEPENDENCIES

Iterable-iOS-AppExtensions.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22
s.name = "Iterable-iOS-AppExtensions"
33
s.module_name = "IterableAppExtensions"
4-
s.version = "6.5.2"
4+
s.version = "6.5.3"
55
s.summary = "App Extensions for Iterable SDK"
66

77
s.description = <<-DESC

Iterable-iOS-SDK.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22
s.name = "Iterable-iOS-SDK"
33
s.module_name = "IterableSDK"
4-
s.version = "6.5.2"
4+
s.version = "6.5.3"
55
s.summary = "Iterable's official SDK for iOS"
66

77
s.description = <<-DESC
@@ -12,7 +12,7 @@ Pod::Spec.new do |s|
1212
s.license = "MIT"
1313
s.author = { "Jay Kim" => "[email protected]" }
1414

15-
s.platform = :ios, "10.0"
15+
s.platform = :ios, "12.0"
1616
s.source = { :git => "https://github.com/Iterable/swift-sdk.git", :tag => s.version }
1717
s.source_files = "swift-sdk/**/*.{h,m,swift}"
1818
s.exclude_files = "swift-sdk/swiftui/**"

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@
401401
ACFF42B02465B4AE00FDF10D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFF42AF2465B4AE00FDF10D /* AppDelegate.swift */; };
402402
BA2BB8192BADD5A500EA0229 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = BA2BB8182BADD5A500EA0229 /* PrivacyInfo.xcprivacy */; };
403403
BA2BB81A2BADD5A500EA0229 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = BA2BB8182BADD5A500EA0229 /* PrivacyInfo.xcprivacy */; };
404+
E9003E012BF4DF15004AB45B /* RetryPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9003E002BF4DF15004AB45B /* RetryPolicy.swift */; };
404405
E9BF47962B46D5DC0033DB69 /* IterableEmbeddedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9BF47952B46D5DC0033DB69 /* IterableEmbeddedView.swift */; };
405406
E9BF47982B46DEB30033DB69 /* IterableEmbeddedView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9BF47972B46DEB30033DB69 /* IterableEmbeddedView.xib */; };
406407
/* End PBXBuildFile section */
@@ -805,6 +806,7 @@
805806
ACFF42AE24656ECF00FDF10D /* ui-tests-app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ui-tests-app.entitlements"; sourceTree = "<group>"; };
806807
ACFF42AF2465B4AE00FDF10D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
807808
BA2BB8182BADD5A500EA0229 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
809+
E9003E002BF4DF15004AB45B /* RetryPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryPolicy.swift; sourceTree = "<group>"; };
808810
E9BF47952B46D5DC0033DB69 /* IterableEmbeddedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IterableEmbeddedView.swift; sourceTree = "<group>"; };
809811
E9BF47972B46DEB30033DB69 /* IterableEmbeddedView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IterableEmbeddedView.xib; sourceTree = "<group>"; };
810812
/* End PBXFileReference section */
@@ -1063,6 +1065,7 @@
10631065
AC3C10F8213F46A900A9B839 /* IterableLogging.swift */,
10641066
ACA8D1A221910C66001B1332 /* IterableMessaging.swift */,
10651067
AC78F0E6253D7F09006378A5 /* IterablePushNotificationMetadata.swift */,
1068+
E9003E002BF4DF15004AB45B /* RetryPolicy.swift */,
10661069
);
10671070
path = "swift-sdk";
10681071
sourceTree = "<group>";
@@ -2089,6 +2092,7 @@
20892092
AC72A0D220CF4D12004D7997 /* IterableUtil.swift in Sources */,
20902093
AC32E16821DD55B900BD4F83 /* OrderedDictionary.swift in Sources */,
20912094
ACF406232507BC72005FD775 /* NetworkMonitor.swift in Sources */,
2095+
E9003E012BF4DF15004AB45B /* RetryPolicy.swift in Sources */,
20922096
AC1712892416AEF400F2BB0E /* WebViewProtocol.swift in Sources */,
20932097
AC3C10F9213F46A900A9B839 /* IterableLogging.swift in Sources */,
20942098
55DD207F26A0D83800773CC7 /* IterableAuthManagerProtocol.swift in Sources */,

swift-sdk.xcodeproj/xcshareddata/xcschemes/swift-sdk.xcscheme

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,18 @@
211211
ReferencedContainer = "container:swift-sdk.xcodeproj">
212212
</BuildableReference>
213213
<SkippedTests>
214+
<Test
215+
Identifier = "NetworkConnectivityManagerTests/testConnectivityChange()">
216+
</Test>
217+
<Test
218+
Identifier = "NetworkConnectivityManagerTests/testPollingNetworkMonitor()">
219+
</Test>
214220
<Test
215221
Identifier = "RequestHandlerTests/testFeatureFlagTurnOnOfflineMode()">
216222
</Test>
223+
<Test
224+
Identifier = "TaskRunnerTests/testResumeWhenNetworkIsBackOnline()">
225+
</Test>
217226
</SkippedTests>
218227
</TestableReference>
219228
</Testables>

swift-sdk/Constants.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ enum Const {
1515

1616
static let deepLinkRegex = "/a/[a-zA-Z0-9]+"
1717
static let href = "href"
18+
static let exponentialFactor = 2.0
1819

1920
enum Http {
2021
static let GET = "GET"
@@ -286,6 +287,8 @@ enum JsonValue {
286287
enum Code {
287288
static let badApiKey = "BadApiKey"
288289
static let invalidJwtPayload = "InvalidJwtPayload"
290+
static let badAuthorizationHeader = "BadAuthorizationHeader"
291+
static let jwtUserIdentifiersMismatched = "JwtUserIdentifiersMismatched"
289292
}
290293
}
291294

swift-sdk/Internal/AuthManager.swift

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import Foundation
66

77
class AuthManager: IterableAuthManagerProtocol {
88
init(delegate: IterableAuthDelegate?,
9+
authRetryPolicy: RetryPolicy,
910
expirationRefreshPeriod: TimeInterval,
1011
localStorage: LocalStorageProtocol,
1112
dateProvider: DateProviderProtocol) {
1213
ITBInfo()
1314

1415
self.delegate = delegate
16+
self.authRetryPolicy = authRetryPolicy
1517
self.localStorage = localStorage
1618
self.dateProvider = dateProvider
1719
self.expirationRefreshPeriod = expirationRefreshPeriod
@@ -35,26 +37,44 @@ class AuthManager: IterableAuthManagerProtocol {
3537
hasFailedPriorAuth = false
3638
}
3739

38-
func requestNewAuthToken(hasFailedPriorAuth: Bool = false, onSuccess: AuthTokenRetrievalHandler? = nil) {
40+
func requestNewAuthToken(hasFailedPriorAuth: Bool = false,
41+
onSuccess: AuthTokenRetrievalHandler? = nil,
42+
shouldIgnoreRetryPolicy: Bool) {
3943
ITBInfo()
4044

41-
guard !pendingAuth else {
42-
return
43-
}
44-
45-
guard !self.hasFailedPriorAuth || !hasFailedPriorAuth else {
45+
if shouldPauseRetry(shouldIgnoreRetryPolicy) || pendingAuth || hasFailedAuth(hasFailedPriorAuth) {
4646
return
4747
}
4848

4949
self.hasFailedPriorAuth = hasFailedPriorAuth
50-
5150
pendingAuth = true
5251

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+
5358
delegate?.onAuthTokenRequested { [weak self] retrievedAuthToken in
59+
self?.pendingAuth = false
60+
self?.retryCount+=1
5461
self?.onAuthTokenReceived(retrievedAuthToken: retrievedAuthToken, onSuccess: onSuccess)
5562
}
5663
}
5764

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+
5878
func setNewToken(_ newToken: String) {
5979
ITBInfo()
6080

@@ -69,6 +89,7 @@ class AuthManager: IterableAuthManagerProtocol {
6989
storeAuthToken()
7090

7191
clearRefreshTimer()
92+
isLastAuthTokenValid = false
7293
}
7394

7495
// MARK: - Private/Internal
@@ -79,11 +100,39 @@ class AuthManager: IterableAuthManagerProtocol {
79100
private var pendingAuth: Bool = false
80101
private var hasFailedPriorAuth: Bool = false
81102

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+
82109
private weak var delegate: IterableAuthDelegate?
83110
private let expirationRefreshPeriod: TimeInterval
84111
private var localStorage: LocalStorageProtocol
85112
private let dateProvider: DateProviderProtocol
86113

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+
87136
private func storeAuthToken() {
88137
localStorage.authToken = authToken
89138
}
@@ -93,33 +142,32 @@ class AuthManager: IterableAuthManagerProtocol {
93142

94143
authToken = localStorage.authToken
95144

96-
queueAuthTokenExpirationRefresh(authToken)
145+
_ = queueAuthTokenExpirationRefresh(authToken)
97146
}
98147

99148
private func onAuthTokenReceived(retrievedAuthToken: String?, onSuccess: AuthTokenRetrievalHandler? = nil) {
100149
ITBInfo()
101150

102151
pendingAuth = false
103152

104-
guard retrievedAuthToken != nil else {
105-
delegate?.onTokenRegistrationFailed("auth token was nil, scheduling auth token retrieval in 10 seconds")
153+
if retrievedAuthToken != nil {
154+
let isRefreshQueued = queueAuthTokenExpirationRefresh(retrievedAuthToken, onSuccess: onSuccess)
155+
if !isRefreshQueued {
156+
onSuccess?(authToken)
157+
}
158+
} else {
159+
delegate?.onTokenRegistrationFailed("auth token was nil, scheduling auth token retrieval in \(getNextRetryInterval()) seconds")
106160

107161
/// by default, schedule a refresh for 10s
108-
scheduleAuthTokenRefreshTimer(10)
109-
110-
return
162+
scheduleAuthTokenRefreshTimer(interval: getNextRetryInterval(), successCallback: onSuccess)
111163
}
112164

113165
authToken = retrievedAuthToken
114166

115167
storeAuthToken()
116-
117-
queueAuthTokenExpirationRefresh(authToken)
118-
119-
onSuccess?(authToken)
120168
}
121169

122-
private func queueAuthTokenExpirationRefresh(_ authToken: String?) {
170+
private func queueAuthTokenExpirationRefresh(_ authToken: String?, onSuccess: AuthTokenRetrievalHandler? = nil) -> Bool {
123171
ITBInfo()
124172

125173
clearRefreshTimer()
@@ -128,32 +176,47 @@ class AuthManager: IterableAuthManagerProtocol {
128176
delegate?.onTokenRegistrationFailed("auth token was nil or could not decode an expiration date, scheduling auth token retrieval in 10 seconds")
129177

130178
/// schedule a default timer of 10 seconds if we fall into this case
131-
scheduleAuthTokenRefreshTimer(10)
179+
scheduleAuthTokenRefreshTimer(interval: getNextRetryInterval(), successCallback: onSuccess)
132180

133-
return
181+
return true
134182
}
135183

136184
let timeIntervalToRefresh = TimeInterval(expirationDate) - dateProvider.currentDate.timeIntervalSince1970 - expirationRefreshPeriod
137-
138-
scheduleAuthTokenRefreshTimer(timeIntervalToRefresh)
185+
if timeIntervalToRefresh > 0 {
186+
scheduleAuthTokenRefreshTimer(interval: timeIntervalToRefresh, isScheduledRefresh: true, successCallback: onSuccess)
187+
return true
188+
}
189+
return false
139190
}
140191

141-
private func scheduleAuthTokenRefreshTimer(_ interval: TimeInterval) {
192+
func scheduleAuthTokenRefreshTimer(interval: TimeInterval, isScheduledRefresh: Bool = false, successCallback: AuthTokenRetrievalHandler? = nil) {
142193
ITBInfo()
194+
if shouldSkipTokenRefresh(isScheduledRefresh: isScheduledRefresh) {
195+
// we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work
196+
return
197+
}
143198

144199
expirationRefreshTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
200+
self?.isTimerScheduled = false
145201
if self?.localStorage.email != nil || self?.localStorage.userId != nil {
146-
self?.requestNewAuthToken(hasFailedPriorAuth: false)
202+
self?.requestNewAuthToken(hasFailedPriorAuth: false, onSuccess: successCallback, shouldIgnoreRetryPolicy: isScheduledRefresh)
147203
} else {
148204
ITBDebug("Email or userId is not available. Skipping token refresh")
149205
}
150206
}
207+
208+
isTimerScheduled = true
209+
}
210+
211+
private func shouldSkipTokenRefresh(isScheduledRefresh: Bool) -> Bool {
212+
return (pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled
151213
}
152214

153215
private func clearRefreshTimer() {
154216
ITBInfo()
155217

156218
expirationRefreshTimer?.invalidate()
219+
isTimerScheduled = false
157220
expirationRefreshTimer = nil
158221
}
159222

swift-sdk/Internal/DependencyContainerProtocol.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ extension DependencyContainerProtocol {
5151

5252
func createAuthManager(config: IterableConfig) -> IterableAuthManagerProtocol {
5353
AuthManager(delegate: config.authDelegate,
54+
authRetryPolicy: config.retryPolicy,
5455
expirationRefreshPeriod: config.expiringAuthTokenRefreshPeriod,
5556
localStorage: localStorage,
5657
dateProvider: dateProvider)

0 commit comments

Comments
 (0)