diff --git a/sample-apps/inbox-customization/inbox-customization/Mock Data/DataManager.swift b/sample-apps/inbox-customization/inbox-customization/Mock Data/DataManager.swift index ee781deb8..aa3bcdcba 100644 --- a/sample-apps/inbox-customization/inbox-customization/Mock Data/DataManager.swift +++ b/sample-apps/inbox-customization/inbox-customization/Mock Data/DataManager.swift @@ -48,7 +48,7 @@ final class DataManager { } struct DemoDependencyContainer: DependencyContainerProtocol { - func createInAppFetcher(apiClient _: ApiClientProtocol) -> InAppFetcherProtocol { + func createInAppFetcher(apiClient _: ApiClientProtocol, authManager _: IterableAuthManagerProtocol?) -> InAppFetcherProtocol { inAppFetcher } diff --git a/swift-sdk/Internal/InternalIterableAPI.swift b/swift-sdk/Internal/InternalIterableAPI.swift index 5a61dce35..6469095d4 100644 --- a/swift-sdk/Internal/InternalIterableAPI.swift +++ b/swift-sdk/Internal/InternalIterableAPI.swift @@ -87,7 +87,8 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider { self.dependencyContainer.createInAppManager(config: self.config, apiClient: self.apiClient, requestHandler: self.requestHandler, - deviceMetadata: deviceMetadata) + deviceMetadata: deviceMetadata, + authManager: self.authManager) }() lazy var authManager: IterableAuthManagerProtocol = { diff --git a/swift-sdk/Internal/Utilities/DependencyContainer.swift b/swift-sdk/Internal/Utilities/DependencyContainer.swift index da124ebf7..6676d5400 100644 --- a/swift-sdk/Internal/Utilities/DependencyContainer.swift +++ b/swift-sdk/Internal/Utilities/DependencyContainer.swift @@ -5,8 +5,8 @@ import Foundation struct DependencyContainer: DependencyContainerProtocol { - func createInAppFetcher(apiClient: ApiClientProtocol) -> InAppFetcherProtocol { - InAppFetcher(apiClient: apiClient) + func createInAppFetcher(apiClient: ApiClientProtocol, authManager: IterableAuthManagerProtocol?) -> InAppFetcherProtocol { + InAppFetcher(apiClient: apiClient, authManager: authManager) } let dateProvider: DateProviderProtocol = SystemDateProvider() diff --git a/swift-sdk/Internal/Utilities/DependencyContainerProtocol.swift b/swift-sdk/Internal/Utilities/DependencyContainerProtocol.swift index 073811d20..b86a9aee0 100644 --- a/swift-sdk/Internal/Utilities/DependencyContainerProtocol.swift +++ b/swift-sdk/Internal/Utilities/DependencyContainerProtocol.swift @@ -16,7 +16,7 @@ protocol DependencyContainerProtocol: RedirectNetworkSessionProvider { var notificationCenter: NotificationCenterProtocol { get } var apnsTypeChecker: APNSTypeCheckerProtocol { get } - func createInAppFetcher(apiClient: ApiClientProtocol) -> InAppFetcherProtocol + func createInAppFetcher(apiClient: ApiClientProtocol, authManager: IterableAuthManagerProtocol?) -> InAppFetcherProtocol func createPersistenceContextProvider() -> IterablePersistenceContextProvider? func createRequestHandler(apiKey: String, config: IterableConfig, @@ -32,10 +32,11 @@ extension DependencyContainerProtocol { func createInAppManager(config: IterableConfig, apiClient: ApiClientProtocol, requestHandler: RequestHandlerProtocol, - deviceMetadata: DeviceMetadata) -> IterableInternalInAppManagerProtocol { + deviceMetadata: DeviceMetadata, + authManager: IterableAuthManagerProtocol?) -> IterableInternalInAppManagerProtocol { InAppManager(requestHandler: requestHandler, deviceMetadata: deviceMetadata, - fetcher: createInAppFetcher(apiClient: apiClient), + fetcher: createInAppFetcher(apiClient: apiClient, authManager: authManager), displayer: inAppDisplayer, persister: inAppPersister, inAppDelegate: config.inAppDelegate, diff --git a/swift-sdk/Internal/in-app/InAppHelper.swift b/swift-sdk/Internal/in-app/InAppHelper.swift index da2150ca1..de9f8c768 100644 --- a/swift-sdk/Internal/in-app/InAppHelper.swift +++ b/swift-sdk/Internal/in-app/InAppHelper.swift @@ -8,8 +8,18 @@ import UIKit /// All classes/structs are internal. struct InAppHelper { - static func getInAppMessagesFromServer(apiClient: ApiClientProtocol, number: Int) -> Pending<[IterableInAppMessage], SendRequestError> { - apiClient.getInAppMessages(NSNumber(value: number)).map { + static func getInAppMessagesFromServer(apiClient: ApiClientProtocol, + authManager: IterableAuthManagerProtocol?, + number: Int, + successHandler onSuccess: OnSuccessHandler? = nil, + failureHandler onFailure: OnFailureHandler? = nil) -> Pending<[IterableInAppMessage], SendRequestError> { + + RequestProcessorUtil.sendRequest(requestProvider: { apiClient.getInAppMessages(NSNumber(value: number)) }, + successHandler: onSuccess, + failureHandler: onFailure, + authManager: authManager, + requestIdentifier: "getInAppMessages") + .map { InAppMessageParser.parse(payload: $0).compactMap { parseResult in process(parseResult: parseResult, apiClient: apiClient) } diff --git a/swift-sdk/Internal/in-app/InAppInternal.swift b/swift-sdk/Internal/in-app/InAppInternal.swift index 4fc718209..21b001223 100644 --- a/swift-sdk/Internal/in-app/InAppInternal.swift +++ b/swift-sdk/Internal/in-app/InAppInternal.swift @@ -26,9 +26,10 @@ struct IterableInAppMessageMetadata { } class InAppFetcher: InAppFetcherProtocol { - init(apiClient: ApiClientProtocol) { + init(apiClient: ApiClientProtocol, authManager: IterableAuthManagerProtocol?) { ITBInfo() self.apiClient = apiClient + self.authManager = authManager } deinit { @@ -43,12 +44,15 @@ class InAppFetcher: InAppFetcherProtocol { return Fulfill(error: IterableError.general(description: "Invalid state: expected InternalApi")) } - return InAppHelper.getInAppMessagesFromServer(apiClient: apiClient, number: numMessages).mapFailure { $0 } + return InAppHelper.getInAppMessagesFromServer(apiClient: apiClient, + authManager: authManager, + number: numMessages).mapFailure { $0 } } // MARK: - Private/Internal private weak var apiClient: ApiClientProtocol? + private let authManager: IterableAuthManagerProtocol? private let numMessages = 100 } diff --git a/tests/common/CommonExtensions.swift b/tests/common/CommonExtensions.swift index bbc87b1fd..48d0af553 100644 --- a/tests/common/CommonExtensions.swift +++ b/tests/common/CommonExtensions.swift @@ -121,7 +121,7 @@ class MockDependencyContainer: DependencyContainerProtocol { ITBInfo() } - func createInAppFetcher(apiClient _: ApiClientProtocol) -> InAppFetcherProtocol { + func createInAppFetcher(apiClient _: ApiClientProtocol, authManager _: IterableAuthManagerProtocol?) -> InAppFetcherProtocol { inAppFetcher } diff --git a/tests/common/MockAuthManager.swift b/tests/common/MockAuthManager.swift index 76bc00c2f..782e86b88 100644 --- a/tests/common/MockAuthManager.swift +++ b/tests/common/MockAuthManager.swift @@ -10,11 +10,20 @@ import Foundation class MockAuthManager: IterableAuthManagerProtocol { + var token = "AuthToken" + var shouldRetry = true var retryWasRequested = false + var isLastAuthTokenValid = false + var pauseAuthRetries = false + var handleAuthFailureCalled = false + var getNextRetryIntervalCalled = false + var failedAuthCount = 0 func handleAuthFailure(failedAuthToken: String?, reason: IterableSDK.AuthFailureReason) { - + failedAuthCount += 1 + handleAuthFailureCalled = true + print("AuthManager handleAuthFailure with reason: \(reason.rawValue) and token: \(String(describing: failedAuthToken))") } func requestNewAuthToken(hasFailedPriorAuth: Bool, onSuccess: ((String?) -> Void)?, shouldIgnoreRetryPolicy: Bool) { @@ -31,32 +40,37 @@ class MockAuthManager: IterableAuthManagerProtocol { } func scheduleAuthTokenRefreshTimer(interval: TimeInterval, isScheduledRefresh: Bool, successCallback: IterableSDK.AuthTokenRetrievalHandler?) { - requestNewAuthToken(hasFailedPriorAuth: false, onSuccess: successCallback, shouldIgnoreRetryPolicy: true) + requestNewAuthToken(hasFailedPriorAuth: false, onSuccess: { newToken in + guard let newToken else { return } + self.setNewToken(newToken) + successCallback?(newToken) + }, shouldIgnoreRetryPolicy: true) } func pauseAuthRetries(_ pauseAuthRetry: Bool) { - + pauseAuthRetries = pauseAuthRetry } func setIsLastAuthTokenValid(_ isValid: Bool) { - + isLastAuthTokenValid = isValid } func getNextRetryInterval() -> Double { + getNextRetryIntervalCalled = true return 0 } func getAuthToken() -> String? { - return "AuthToken" + token } func resetFailedAuthCount() { - + failedAuthCount = 0 } func setNewToken(_ newToken: String) { - + token = newToken } func logoutUser() { diff --git a/tests/endpoint-tests/E2EDependencyContainer.swift b/tests/endpoint-tests/E2EDependencyContainer.swift index b7dd83ef4..9b2f6aab7 100644 --- a/tests/endpoint-tests/E2EDependencyContainer.swift +++ b/tests/endpoint-tests/E2EDependencyContainer.swift @@ -19,8 +19,8 @@ class E2EDependencyContainer: DependencyContainerProtocol { let notificationCenter: NotificationCenterProtocol let apnsTypeChecker: APNSTypeCheckerProtocol - func createInAppFetcher(apiClient: ApiClientProtocol) -> InAppFetcherProtocol { - InAppFetcher(apiClient: apiClient) + func createInAppFetcher(apiClient: ApiClientProtocol, authManager: IterableAuthManagerProtocol?) -> InAppFetcherProtocol { + InAppFetcher(apiClient: apiClient, authManager: authManager) } init(dateProvider: DateProviderProtocol = SystemDateProvider(), diff --git a/tests/unit-tests/InAppHelperTests.swift b/tests/unit-tests/InAppHelperTests.swift index 77a7467b6..810b4cc2f 100644 --- a/tests/unit-tests/InAppHelperTests.swift +++ b/tests/unit-tests/InAppHelperTests.swift @@ -6,6 +6,30 @@ import XCTest @testable import IterableSDK +private final class RetryingApiClient: BlankApiClient { + var numOfMessages = 3 + var callCount = -1 + var lastCountRequested: NSNumber? + var failureReason = "jwt token is expired" + weak var authManager: MockAuthManager? + + override func getInAppMessages(_ count: NSNumber) -> Pending { + callCount += 1 + lastCountRequested = count + + if callCount == 0 { + return Fulfill(value: TestInAppPayloadGenerator.createPayloadWithUrl(numMessages: numOfMessages)) + } + + if authManager?.retryWasRequested == true { + return Fulfill(value: TestInAppPayloadGenerator.createPayloadWithUrl(numMessages: numOfMessages)) + } + return Fulfill(error: SendRequestError(reason: failureReason, + httpStatusCode: 401, + iterableCode: JsonValue.Code.invalidJwtPayload)) + } +} + class InAppHelperTests: XCTestCase { func testGetInAppMessagesWithNoError() { class MyApiClient: BlankApiClient { @@ -15,7 +39,7 @@ class InAppHelperTests: XCTestCase { } } - InAppHelper.getInAppMessagesFromServer(apiClient: MyApiClient(), number: 10).onSuccess { messages in + InAppHelper.getInAppMessagesFromServer(apiClient: MyApiClient(), authManager: nil, number: 10).onSuccess { messages in XCTAssertEqual(messages.count, 3) XCTAssertEqual(messages[0].messageId, "message1") XCTAssertEqual(messages[1].messageId, "message2") @@ -69,14 +93,53 @@ class InAppHelperTests: XCTestCase { } } - InAppHelper.getInAppMessagesFromServer(apiClient: MyApiClient(expectation: expectation1), number: 10).onSuccess { messages in + InAppHelper.getInAppMessagesFromServer(apiClient: MyApiClient(expectation: expectation1), authManager: nil, number: 10).onSuccess { messages in XCTAssertEqual(messages.count, 1) XCTAssertEqual(messages[0].messageId, "message1") } wait(for: [expectation1], timeout: testExpectationTimeout) } - + + func testGetInAppMessagesRetriesAfterJWT401() { + let authManager = MockAuthManager() + authManager.shouldRetry = true + + let apiClient = RetryingApiClient() + apiClient.authManager = authManager + + InAppHelper.getInAppMessagesFromServer(apiClient: apiClient, + authManager: authManager, + number: apiClient.numOfMessages).onSuccess { messages in + print(messages) + }.onError { error in + XCTFail("expected success, got error: \(error)") + } + + InAppHelper.getInAppMessagesFromServer(apiClient: apiClient, + authManager: authManager, + number: apiClient.numOfMessages, + successHandler: { data in + XCTAssertTrue(authManager.handleAuthFailureCalled) + XCTAssertTrue(authManager.getNextRetryIntervalCalled) + + XCTAssertTrue(authManager.retryWasRequested) + XCTAssertEqual(authManager.getAuthToken(), "newAuthToken") + }).onSuccess { messages in + print(messages) + }.onError { error in + XCTFail("expected success, got error: \(error)") + } + + InAppHelper.getInAppMessagesFromServer(apiClient: apiClient, + authManager: authManager, + number: apiClient.numOfMessages).onSuccess { messages in + XCTAssertTrue(authManager.retryWasRequested) + }.onError { error in + XCTFail("expected success, got error: \(error)") + } + } + func testParseURL() { let urlWithNoScheme = URL(string: "blah")! XCTAssertNil(InAppHelper.parse(inAppUrl: urlWithNoScheme))