Skip to content

Commit 6fe861a

Browse files
Handle redirect in mock network session.
1 parent a49299f commit 6fe861a

File tree

8 files changed

+249
-87
lines changed

8 files changed

+249
-87
lines changed

swift-sdk/Constants.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ enum Const {
7878
static let online = "Online"
7979
static let offline = "Offline"
8080
}
81+
82+
enum CookieName {
83+
static let campaignId = "iterableEmailCampaignId"
84+
static let templateId = "iterableTemplateId"
85+
static let messageId = "iterableMessageId"
86+
}
87+
88+
enum HttpHeader {
89+
static let location = "Location"
90+
static let setCookie = "Set-Cookie"
91+
}
8192
}
8293

8394
enum JsonKey {

swift-sdk/Internal/DependencyContainer.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import Foundation
66
import UIKit
77

8-
protocol DependencyContainerProtocol {
8+
protocol DependencyContainerProtocol: RedirectNetworkSessionProvider {
99
var dateProvider: DateProviderProtocol { get }
1010
var networkSession: NetworkSessionProtocol { get }
1111
var notificationStateProvider: NotificationStateProviderProtocol { get }
@@ -106,6 +106,10 @@ extension DependencyContainerProtocol {
106106
CoreDataPersistenceContextProvider(dateProvider: dateProvider)
107107
}
108108

109+
func createRedirectNetworkSession(delegate: RedirectNetworkSessionDelegate) -> NetworkSessionProtocol {
110+
RedirectNetworkSession(delegate: delegate)
111+
}
112+
109113
private func createTaskScheduler(persistenceContextProvider: IterablePersistenceContextProvider,
110114
healthMonitor: HealthMonitor) -> IterableTaskScheduler {
111115
IterableTaskScheduler(persistenceContextProvider: persistenceContextProvider,

swift-sdk/Internal/InternalIterableAPI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
541541
localStorage = dependencyContainer.localStorage
542542
inAppDisplayer = dependencyContainer.inAppDisplayer
543543
urlOpener = dependencyContainer.urlOpener
544-
deepLinkManager = IterableDeepLinkManager()
544+
deepLinkManager = IterableDeepLinkManager(redirectNetworkSessionProvider: dependencyContainer)
545545
}
546546

547547
func start() -> Pending<Bool, Error> {

swift-sdk/Internal/IterableDeepLinkManager.swift

Lines changed: 14 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import Foundation
66

77
class IterableDeepLinkManager: NSObject {
8+
init(redirectNetworkSessionProvider: RedirectNetworkSessionProvider) {
9+
self.redirectNetworkSessionProvider = redirectNetworkSessionProvider
10+
}
811
/// Handles a Universal Link
912
/// For Iterable links, it will track the click and retrieve the original URL,
1013
/// pass it to `IterableURLDelegate` for handling
@@ -63,7 +66,7 @@ class IterableDeepLinkManager: NSObject {
6366
deepLinkMessageId = nil
6467

6568
if isIterableDeepLink(appLinkURL.absoluteString) {
66-
let trackAndRedirectTask = redirectUrlSession.dataTask(with: appLinkURL) { [unowned self] _, _, error in
69+
redirectUrlSession.makeDataRequest(with: appLinkURL) { [unowned self] _, _, error in
6770
if let error = error {
6871
ITBError("error: \(error.localizedDescription)")
6972
fulfill.resolve(with: (nil, nil))
@@ -77,8 +80,6 @@ class IterableDeepLinkManager: NSObject {
7780
}
7881
}
7982
}
80-
81-
trackAndRedirectTask.resume()
8283
} else {
8384
fulfill.resolve(with: (appLinkURL, nil))
8485
}
@@ -94,59 +95,23 @@ class IterableDeepLinkManager: NSObject {
9495
return regex.firstMatch(in: urlString, options: [], range: NSMakeRange(0, urlString.count)) != nil
9596
}
9697

97-
private lazy var redirectUrlSession: URLSession = {
98-
URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
98+
private lazy var redirectUrlSession: NetworkSessionProtocol = {
99+
redirectNetworkSessionProvider.createRedirectNetworkSession(delegate: self)
99100
}()
101+
100102

103+
private var redirectNetworkSessionProvider: RedirectNetworkSessionProvider
101104
private var deepLinkLocation: URL?
102105
private var deepLinkCampaignId: NSNumber?
103106
private var deepLinkTemplateId: NSNumber?
104107
private var deepLinkMessageId: String?
105108
}
106109

107-
extension IterableDeepLinkManager: URLSessionDelegate, URLSessionTaskDelegate {
108-
/**
109-
Delegate handler when a redirect occurs. Stores a reference to the redirect url and does not execute the redirect.
110-
- parameters:
111-
- session: the session
112-
- task: the task
113-
- response: the redirectResponse
114-
- request: the request
115-
- completionHandler: the completionHandler
116-
*/
117-
public func urlSession(_: URLSession,
118-
task _: URLSessionTask,
119-
willPerformHTTPRedirection response: HTTPURLResponse,
120-
newRequest request: URLRequest,
121-
completionHandler: @escaping (URLRequest?) -> Void) {
122-
deepLinkLocation = request.url
123-
124-
guard let headerFields = response.allHeaderFields as? [String: String] else {
125-
return
126-
}
127-
128-
guard let url = response.url else {
129-
return
130-
}
131-
132-
for cookie in HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url) {
133-
if cookie.name == "iterableEmailCampaignId" {
134-
deepLinkCampaignId = number(fromString: cookie.value)
135-
} else if cookie.name == "iterableTemplateId" {
136-
deepLinkTemplateId = number(fromString: cookie.value)
137-
} else if cookie.name == "iterableMessageId" {
138-
deepLinkMessageId = cookie.value
139-
}
140-
}
141-
142-
completionHandler(nil)
143-
}
144-
145-
private func number(fromString str: String) -> NSNumber {
146-
if let intValue = Int(str) {
147-
return NSNumber(value: intValue)
148-
}
149-
150-
return NSNumber(value: 0)
110+
extension IterableDeepLinkManager: RedirectNetworkSessionDelegate {
111+
func onRedirect(deeplinkLocation: URL?, campaignId: NSNumber?, templateId: NSNumber?, messageId: String?) {
112+
self.deepLinkLocation = deeplinkLocation
113+
self.deepLinkCampaignId = campaignId
114+
self.deepLinkTemplateId = templateId
115+
self.deepLinkMessageId = messageId
151116
}
152117
}

swift-sdk/Internal/NetworkHelper.swift

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,85 @@ extension URLSession: NetworkSessionProtocol {
6464
}
6565
}
6666

67+
protocol RedirectNetworkSessionDelegate: AnyObject {
68+
func onRedirect(deeplinkLocation: URL?, campaignId: NSNumber?, templateId: NSNumber?, messageId: String?)
69+
}
70+
71+
protocol RedirectNetworkSessionProvider {
72+
func createRedirectNetworkSession(delegate: RedirectNetworkSessionDelegate) -> NetworkSessionProtocol
73+
}
74+
75+
class RedirectNetworkSession: NSObject, NetworkSessionProtocol {
76+
func makeRequest(_ request: URLRequest, completionHandler: @escaping CompletionHandler) {
77+
networkSession.makeRequest(request, completionHandler: completionHandler)
78+
}
79+
80+
func makeDataRequest(with url: URL, completionHandler: @escaping CompletionHandler) {
81+
networkSession.makeDataRequest(with: url, completionHandler: completionHandler)
82+
}
83+
84+
func createDataTask(with url: URL, completionHandler: @escaping CompletionHandler) -> DataTaskProtocol {
85+
networkSession.createDataTask(with: url, completionHandler: completionHandler)
86+
}
87+
88+
private lazy var networkSession: NetworkSessionProtocol = {
89+
URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
90+
} ()
91+
92+
init(delegate: RedirectNetworkSessionDelegate?) {
93+
self.delegate = delegate
94+
}
95+
96+
private weak var delegate: RedirectNetworkSessionDelegate?
97+
}
98+
99+
extension RedirectNetworkSession: URLSessionDelegate, URLSessionTaskDelegate {
100+
internal func urlSession(_: URLSession,
101+
task _: URLSessionTask,
102+
willPerformHTTPRedirection response: HTTPURLResponse,
103+
newRequest request: URLRequest,
104+
completionHandler: @escaping (URLRequest?) -> Void) {
105+
var deepLinkLocation: URL? = nil
106+
var campaignId: NSNumber? = nil
107+
var templateId: NSNumber? = nil
108+
var messageId: String? = nil
109+
110+
deepLinkLocation = request.url
111+
112+
guard let headerFields = response.allHeaderFields as? [String: String] else {
113+
delegate?.onRedirect(deeplinkLocation: deepLinkLocation, campaignId: campaignId, templateId: templateId, messageId: messageId)
114+
return
115+
}
116+
117+
guard let url = response.url else {
118+
delegate?.onRedirect(deeplinkLocation: deepLinkLocation, campaignId: campaignId, templateId: templateId, messageId: messageId)
119+
return
120+
}
121+
122+
for cookie in HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url) {
123+
if cookie.name == Const.CookieName.campaignId {
124+
campaignId = number(fromString: cookie.value)
125+
} else if cookie.name == Const.CookieName.templateId {
126+
templateId = number(fromString: cookie.value)
127+
} else if cookie.name == Const.CookieName.messageId {
128+
messageId = cookie.value
129+
}
130+
}
131+
132+
delegate?.onRedirect(deeplinkLocation: deepLinkLocation, campaignId: campaignId, templateId: templateId, messageId: messageId)
133+
completionHandler(nil)
134+
}
135+
136+
private func number(fromString str: String) -> NSNumber {
137+
if let intValue = Int(str) {
138+
return NSNumber(value: intValue)
139+
}
140+
141+
return NSNumber(value: 0)
142+
}
143+
144+
}
145+
67146
struct NetworkHelper {
68147
static func getData(fromUrl url: URL, usingSession networkSession: NetworkSessionProtocol) -> Pending<Data, Error> {
69148
let fulfill = Fulfill<Data, Error>()

tests/common/CommonExtensions.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,74 @@ class MockDependencyContainer: DependencyContainerProtocol {
136136
return CoreDataPersistenceContextProvider(dateProvider: dateProvider)
137137
}
138138
}
139+
140+
func createRedirectNetworkSession(delegate: RedirectNetworkSessionDelegate) -> NetworkSessionProtocol {
141+
MockRedirectNetworkSession(networkSession: networkSession, redirectDelegate: delegate)
142+
}
143+
}
144+
145+
struct MockRedirectNetworkSession: NetworkSessionProtocol {
146+
init(networkSession: NetworkSessionProtocol, redirectDelegate: RedirectNetworkSessionDelegate) {
147+
self.networkSession = Self.createRedirectNetworkSession(fromNetworkSession: networkSession, redirectDelegate: redirectDelegate)
148+
}
149+
150+
func makeRequest(_ request: URLRequest, completionHandler: @escaping CompletionHandler) {
151+
networkSession.makeRequest(request, completionHandler: completionHandler)
152+
}
153+
154+
func makeDataRequest(with url: URL, completionHandler: @escaping CompletionHandler) {
155+
networkSession.makeDataRequest(with: url, completionHandler: completionHandler)
156+
}
157+
158+
func createDataTask(with url: URL, completionHandler: @escaping CompletionHandler) -> DataTaskProtocol {
159+
networkSession.createDataTask(with: url, completionHandler: completionHandler)
160+
}
161+
162+
private static func createRedirectNetworkSession(fromNetworkSession networkSession: NetworkSessionProtocol,
163+
redirectDelegate: RedirectNetworkSessionDelegate) -> NetworkSessionProtocol {
164+
let redirectSession = MockNetworkSession()
165+
let callback: (URL) -> MockNetworkSession.MockResponse? = { [weak redirectDelegate] url in
166+
if let response = (networkSession as? MockNetworkSession)?.responseCallback?(url) {
167+
let headerFields = response.headerFields!
168+
let location = headerFields[Const.HttpHeader.location]
169+
let deeplinkLocation = location.map { URL(string: $0) } ?? nil
170+
let (campaignId, templateId, messageId) = Self.getIterableValues(fromCookies: HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url))
171+
redirectDelegate?.onRedirect(deeplinkLocation: deeplinkLocation,
172+
campaignId: campaignId,
173+
templateId: templateId,
174+
messageId: messageId)
175+
return response
176+
} else {
177+
redirectDelegate?.onRedirect(deeplinkLocation: url, campaignId: nil, templateId: nil, messageId: nil)
178+
return nil
179+
}
180+
}
181+
redirectSession.responseCallback = callback
182+
return redirectSession
183+
}
184+
185+
private static func getIterableValues(fromCookies cookies: [HTTPCookie]) -> (campaignId: NSNumber?, templateId: NSNumber?, messageId: String?) {
186+
let values: (campaignId: NSNumber?, templateId: NSNumber?, messageId: String?) = (nil, nil, nil)
187+
return cookies.reduce(into: values) { result, cookie in
188+
if cookie.name == Const.CookieName.campaignId {
189+
result.campaignId = number(fromString: cookie.value)
190+
} else if cookie.name == Const.CookieName.templateId {
191+
result.templateId = number(fromString: cookie.value)
192+
} else if cookie.name == Const.CookieName.messageId {
193+
result.messageId = cookie.value
194+
}
195+
}
196+
}
197+
198+
private static func number(fromString str: String) -> NSNumber {
199+
if let intValue = Int(str) {
200+
return NSNumber(value: intValue)
201+
}
202+
203+
return NSNumber(value: 0)
204+
}
205+
206+
private let networkSession: NetworkSessionProtocol
139207
}
140208

141209
extension IterableAPI {

tests/common/CommonMocks.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,11 @@ class MockNetworkSession: NetworkSessionProtocol {
229229
self.init(mapping: [".*": mockResponse])
230230
}
231231

232-
init(mapping: [String: MockResponse?]?) {
233-
self.responseCallback = { url in
232+
convenience init(mapping: [String: MockResponse?]?) {
233+
let responseCallback: (URL) -> MockResponse? = { url in
234234
MockNetworkSession.response(for: url.absoluteString, inMapping: mapping)
235235
}
236+
self.init(responseCallback: responseCallback)
236237
}
237238

238239
init(responseCallback: ((URL) -> MockResponse?)?) {

0 commit comments

Comments
 (0)