diff --git a/Sources/MessagingPush/Config/MessagingPushConfigBuilder.swift b/Sources/MessagingPush/Config/MessagingPushConfigBuilder.swift index 31e9e1c4d..32fe747e2 100644 --- a/Sources/MessagingPush/Config/MessagingPushConfigBuilder.swift +++ b/Sources/MessagingPush/Config/MessagingPushConfigBuilder.swift @@ -33,6 +33,7 @@ public class MessagingPushConfigBuilder { // configuration options for MessagingPushConfigOptions private let cdpApiKey: String + private var region: Region = .US private var autoFetchDeviceToken: Bool = true private var autoTrackPushEvents: Bool = true private var showPushAppInForeground: Bool = true @@ -54,6 +55,17 @@ public class MessagingPushConfigBuilder { self.cdpApiKey = cdpApiKey } + /// Configures the region for NotificationServiceExtension for metric tracking + @discardableResult + @available(iOS, unavailable) + @available(visionOS, unavailable) + @available(iOSApplicationExtension, introduced: 13.0) + @available(visionOSApplicationExtension, introduced: 1.0) + public func region(_ region: Region) -> MessagingPushConfigBuilder { + self.region = region + return self + } + /// Configures the log level for NotificationServiceExtension, allowing customization of SDK log /// verbosity to help setup and debugging @discardableResult @@ -94,6 +106,7 @@ public class MessagingPushConfigBuilder { let configOptions = MessagingPushConfigOptions( logLevel: logLevel, cdpApiKey: cdpApiKey, + region: region, autoFetchDeviceToken: autoFetchDeviceToken, autoTrackPushEvents: autoTrackPushEvents, showPushAppInForeground: showPushAppInForeground @@ -106,6 +119,7 @@ public class MessagingPushConfigBuilder { public extension MessagingPushConfigBuilder { /// Constants used to map each of the options in MessagingPushConfigOptions. enum Keys: String { + case region case autoFetchDeviceToken case autoTrackPushEvents case showPushAppInForeground @@ -116,6 +130,9 @@ public extension MessagingPushConfigBuilder { static func build(from dictionary: [String: Any]) -> MessagingPushConfigOptions { let builder = MessagingPushConfigBuilder() + if let region = dictionary[Keys.region.rawValue] as? String { + builder.region = Region.getRegion(from: region) + } if let autoFetchDeviceToken = dictionary[Keys.autoFetchDeviceToken.rawValue] as? Bool { builder.autoFetchDeviceToken(autoFetchDeviceToken) } diff --git a/Sources/MessagingPush/Config/MessagingPushConfigOptions.swift b/Sources/MessagingPush/Config/MessagingPushConfigOptions.swift index c55f763f8..166809af9 100644 --- a/Sources/MessagingPush/Config/MessagingPushConfigOptions.swift +++ b/Sources/MessagingPush/Config/MessagingPushConfigOptions.swift @@ -6,6 +6,7 @@ import CioInternalCommon public struct MessagingPushConfigOptions { public let logLevel: CioLogLevel public let cdpApiKey: String + public let region: Region public let autoFetchDeviceToken: Bool public let autoTrackPushEvents: Bool public let showPushAppInForeground: Bool diff --git a/Sources/MessagingPush/RichPush/RichPushDeliveryTracker.swift b/Sources/MessagingPush/RichPush/RichPushDeliveryTracker.swift index 734ab450f..62c5428d0 100644 --- a/Sources/MessagingPush/RichPush/RichPushDeliveryTracker.swift +++ b/Sources/MessagingPush/RichPush/RichPushDeliveryTracker.swift @@ -5,10 +5,12 @@ import Foundation class RichPushDeliveryTracker { let httpClient: HttpClient let logger: Logger + let region: Region init(httpClient: HttpClient, logger: Logger) { self.httpClient = httpClient self.logger = logger + self.region = MessagingPush.moduleConfig.region } func trackMetric(token: String, event: Metric, deliveryId: String, timestamp: String? = nil, onComplete: @escaping (Result) -> Void) { @@ -25,7 +27,7 @@ class RichPushDeliveryTracker { let endpoint: CIOApiEndpoint = .trackPushMetricsCdp guard let httpParams = HttpRequestParams( endpoint: endpoint, - baseUrl: RichPushHttpClient.defaultAPIHost, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: region), headers: nil, body: try? JSONSerialization.data(withJSONObject: properties) ) else { diff --git a/Sources/MessagingPush/RichPush/RichPushHttpClient.swift b/Sources/MessagingPush/RichPush/RichPushHttpClient.swift index 3abed3e6e..2516fb7ee 100644 --- a/Sources/MessagingPush/RichPush/RichPushHttpClient.swift +++ b/Sources/MessagingPush/RichPush/RichPushHttpClient.swift @@ -15,6 +15,8 @@ public class RichPushHttpClient: HttpClient { [cioApiSession, publicSession] } + private let region: Region + public func request(_ params: CioInternalCommon.HttpRequestParams, onComplete: @escaping (Result) -> Void) { httpRequestRunner .request( @@ -57,7 +59,7 @@ public class RichPushHttpClient: HttpClient { } func getSessionForRequest(url: URL) -> URLSession { - let cioApiHostname = URL(string: Self.defaultAPIHost)!.host + let cioApiHostname = URL(string: Self.getDefaultApiHost(region: region))!.host let requestHostname = url.host let isRequestToCIOApi = cioApiHostname == requestHostname @@ -123,7 +125,7 @@ public class RichPushHttpClient: HttpClient { self.httpRequestRunner = httpRequestRunner self.jsonAdapter = jsonAdapter self.logger = logger - + self.region = MessagingPush.moduleConfig.region self.publicSession = Self.getBasicSession() self.cioApiSession = Self.getCIOApiSession( key: MessagingPush.moduleConfig.cdpApiKey, @@ -137,7 +139,14 @@ public class RichPushHttpClient: HttpClient { } extension RichPushHttpClient { - public static let defaultAPIHost = "https://cdp.customer.io/v1" + static func getDefaultApiHost(region: Region) -> String { + switch region { + case .US: + "https://cdp.customer.io/v1" + case .EU: + "https://cdp-eu.customer.io/v1" + } + } static func authorizationHeaderForCdpApiKey(_ key: String) -> String { var returnHeader = "\(key):" diff --git a/Tests/MessagingPush/Config/MessagingPushConfigBuilderTest.swift b/Tests/MessagingPush/Config/MessagingPushConfigBuilderTest.swift index b58ceb2df..fcb42de88 100644 --- a/Tests/MessagingPush/Config/MessagingPushConfigBuilderTest.swift +++ b/Tests/MessagingPush/Config/MessagingPushConfigBuilderTest.swift @@ -1,3 +1,4 @@ +@testable import CioInternalCommon @testable import CioMessagingPush import SharedTests import XCTest @@ -41,7 +42,8 @@ class MessagingPushConfigBuilderTest: UnitTest { let givenDict: [String: Any] = [ "autoFetchDeviceToken": givenAutoFetchDeviceToken, "autoTrackPushEvents": givenAutoTrackPushEvents, - "showPushAppInForeground": givenShowPushAppInForeground + "showPushAppInForeground": givenShowPushAppInForeground, + "region": "EU" ] let config = MessagingPushConfigBuilder.build(from: givenDict) @@ -49,6 +51,7 @@ class MessagingPushConfigBuilderTest: UnitTest { XCTAssertEqual(config.autoFetchDeviceToken, givenAutoFetchDeviceToken) XCTAssertEqual(config.autoTrackPushEvents, givenAutoTrackPushEvents) XCTAssertEqual(config.showPushAppInForeground, givenShowPushAppInForeground) + XCTAssertEqual(config.region, Region.EU) } func test_initializeFromDictionaryWithIncorrectKeys_expectDefaultValues() { @@ -83,5 +86,6 @@ extension MessagingPushConfigBuilderTest { XCTAssertTrue(config.autoFetchDeviceToken, file: file, line: line) XCTAssertTrue(config.autoTrackPushEvents, file: file, line: line) XCTAssertTrue(config.showPushAppInForeground, file: file, line: line) + XCTAssertEqual(config.region, Region.US, file: file, line: line) } } diff --git a/Tests/MessagingPush/HttpClientTest.swift b/Tests/MessagingPush/HttpClientTest.swift index 2dafba6a4..a5233f865 100644 --- a/Tests/MessagingPush/HttpClientTest.swift +++ b/Tests/MessagingPush/HttpClientTest.swift @@ -16,8 +16,11 @@ class HttpClientTest: UnitTest { private let url = URL(string: "https://customer.io")! - override func setUp() { - super.setUp() + override func setUp( + enableLogs: Bool = false, + modifyModuleConfig: ((MessagingPushConfigBuilder) -> Void)? = nil + ) { + super.setUp(enableLogs: enableLogs, modifyModuleConfig: modifyModuleConfig) client = RichPushHttpClient( jsonAdapter: jsonAdapter, @@ -45,7 +48,7 @@ class HttpClientTest: UnitTest { let expectComplete = expectation(description: "Expect to complete") let params = HttpRequestParams( endpoint: .trackPushMetricsCdp, - baseUrl: RichPushHttpClient.defaultAPIHost, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: Region.US), headers: nil, body: nil )! @@ -71,7 +74,7 @@ class HttpClientTest: UnitTest { let expectComplete = expectation(description: "Expect to complete") let params = HttpRequestParams( endpoint: .trackPushMetricsCdp, - baseUrl: RichPushHttpClient.defaultAPIHost, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: Region.US), headers: nil, body: nil )! @@ -95,7 +98,7 @@ class HttpClientTest: UnitTest { let expectComplete = expectation(description: "Expect to complete") let params = HttpRequestParams( endpoint: .trackPushMetricsCdp, - baseUrl: RichPushHttpClient.defaultAPIHost, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: Region.US), headers: nil, body: nil )! @@ -111,6 +114,41 @@ class HttpClientTest: UnitTest { waitForExpectations() } + @available(iOS, unavailable) + @available(visionOS, unavailable) + @available(iOSApplicationExtension, introduced: 13.0) + @available(visionOSApplicationExtension, introduced: 1.0) + func test_request_givenEuRegion_expectRequestToBeMade() { + super.setUp(modifyModuleConfig: { config in + config.region(Region.EU) + }) + + let expected = #"{ "message": "Success!" }"#.data! + + mockRequestResponse { + (body: expected, response: HTTPURLResponse(url: self.url, statusCode: 200, httpVersion: nil, headerFields: nil), failure: nil) + } + + let expectComplete = expectation(description: "Expect to complete") + let params = HttpRequestParams( + endpoint: .trackPushMetricsCdp, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: messagingPushConfigOptions.region), + headers: nil, + body: nil + )! + client.request(params) { result in + XCTAssertTrue(self.requestRunnerMock.requestCalled) + + XCTAssertNil(result.error) + + XCTAssertEqual(result.success, expected) + + expectComplete.fulfill() + } + + waitForExpectations() + } + func test_request_givenSuccessfulResponse_expectGetResponseBody() { let expected = #"{ "message": "Success!" }"#.data! @@ -121,7 +159,7 @@ class HttpClientTest: UnitTest { let expectComplete = expectation(description: "Expect to complete") let params = HttpRequestParams( endpoint: .trackPushMetricsCdp, - baseUrl: RichPushHttpClient.defaultAPIHost, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: messagingPushConfigOptions.region), headers: nil, body: nil )! @@ -151,7 +189,28 @@ class HttpClientTest: UnitTest { func test_getSessionForRequest_givenCIOApiEndpoint_expectGetCIOApiSession() { let cioApiEndpointUrl = HttpRequestParams( endpoint: .trackPushMetricsCdp, - baseUrl: RichPushHttpClient.defaultAPIHost, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: Region.US), + headers: nil, + body: nil + )!.url + + let actualSession = client.getSessionForRequest(url: cioApiEndpointUrl) + + let containsAuthorizationHeader = actualSession.configuration.httpAdditionalHeaders!["Authorization"] != nil + XCTAssertTrue(containsAuthorizationHeader) + } + + @available(iOS, unavailable) + @available(visionOS, unavailable) + @available(iOSApplicationExtension, introduced: 13.0) + @available(visionOSApplicationExtension, introduced: 1.0) + func test_getSessionForRequest_givenCIOEUApiEndpoint_expectGetCIOApiSession() { + setUp(modifyModuleConfig: { config in + config.region(.EU) + }) + let cioApiEndpointUrl = HttpRequestParams( + endpoint: .trackPushMetricsCdp, + baseUrl: RichPushHttpClient.getDefaultApiHost(region: Region.EU), headers: nil, body: nil )!.url @@ -162,6 +221,11 @@ class HttpClientTest: UnitTest { XCTAssertTrue(containsAuthorizationHeader) } + func test_getDefaultApiHost_givenRegion_expectCorrectApiHost() { + XCTAssertEqual(RichPushHttpClient.getDefaultApiHost(region: .US), "https://cdp.customer.io/v1") + XCTAssertEqual(RichPushHttpClient.getDefaultApiHost(region: .EU), "https://cdp-eu.customer.io/v1") + } + func test_getSessionForRequest_givenCIOAssetLibraryEndpoint_expectPublicSession() { let actualSession = client.getSessionForRequest(url: URL(string: "https://storage.googleapis.com/cio-asset-manager-standalone/1670599791846_frederick_adoption_day.jpg")!)