-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathDeepLinkManager.swift
More file actions
130 lines (112 loc) · 6.31 KB
/
DeepLinkManager.swift
File metadata and controls
130 lines (112 loc) · 6.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//
// Copyright © 2018 Iterable. All rights reserved.
//
import Foundation
class DeepLinkManager: NSObject {
init(redirectNetworkSessionProvider: RedirectNetworkSessionProvider) {
self.redirectNetworkSessionProvider = redirectNetworkSessionProvider
}
/// Handles a Universal Link
/// For Iterable links, it will track the click and retrieve the original URL,
/// pass it to `IterableURLDelegate` for handling
/// If it's not an Iterable link, it just passes the same URL to `IterableURLDelegate`
func handleUniversalLink(_ url: URL,
urlDelegate: IterableURLDelegate?,
urlOpener: UrlOpenerProtocol,
allowedProtocols: [String] = []) -> (Bool, Pending<IterableAttributionInfo?, Error>) {
if isIterableDeepLink(url.absoluteString) {
let pending = resolve(appLinkURL: url).map { (resolvedUrl, attributionInfo) -> IterableAttributionInfo? in
var resolvedUrlString: String
if let resolvedUrl = resolvedUrl {
resolvedUrlString = resolvedUrl.absoluteString
} else {
resolvedUrlString = url.absoluteString
}
if let action = IterableAction.actionOpenUrl(fromUrlString: resolvedUrlString) {
let context = IterableActionContext(action: action, source: .universalLink)
ActionRunner.execute(action: action,
context: context,
urlHandler: IterableUtil.urlHandler(fromUrlDelegate: urlDelegate,
inContext: context),
urlOpener: urlOpener,
allowedProtocols: allowedProtocols)
}
return attributionInfo
}
// Always return true for deep link
return (true, pending)
} else {
var result: Bool = false
if let action = IterableAction.actionOpenUrl(fromUrlString: url.absoluteString) {
let context = IterableActionContext(action: action, source: .universalLink)
result = ActionRunner.execute(action: action,
context: context,
urlHandler: IterableUtil.urlHandler(fromUrlDelegate: urlDelegate,
inContext: context),
urlOpener: urlOpener,
allowedProtocols: allowedProtocols)
}
return (result, Fulfill<IterableAttributionInfo?, Error>(value: nil))
}
}
/// And we will resolve with redirected URL from our server and we will also try to get attribution info.
/// Otherwise, we will just resolve with the original URL.
private func resolve(appLinkURL: URL) -> Pending<(URL?, IterableAttributionInfo?), Error> {
let fulfill = Fulfill<(URL?, IterableAttributionInfo?), Error>()
deepLinkCampaignId = nil
deepLinkTemplateId = nil
deepLinkMessageId = nil
if isIterableDeepLink(appLinkURL.absoluteString) {
redirectUrlSession.makeDataRequest(with: appLinkURL) { [unowned self] _, _, error in
// Check if we successfully captured redirect data FIRST
// The delegate callback happens before the error, so deepLinkLocation
// may be set even if we get NSURLErrorTimedOut (-1001) or
// NSURLErrorCancelled (-999) from cancelling the redirect
if self.deepLinkLocation != nil {
// We successfully intercepted the redirect
if let deepLinkCampaignId = self.deepLinkCampaignId,
let deepLinkTemplateId = self.deepLinkTemplateId,
let deepLinkMessageId = self.deepLinkMessageId {
fulfill.resolve(with: (self.deepLinkLocation, IterableAttributionInfo(campaignId: deepLinkCampaignId, templateId: deepLinkTemplateId, messageId: deepLinkMessageId)))
} else {
// We have location but missing attribution cookies
// This is still a success case - user can navigate to the deep link
fulfill.resolve(with: (self.deepLinkLocation, nil))
}
} else if let error = error {
// Only treat as error if we didn't capture the redirect location
ITBError("error: \(error.localizedDescription)")
fulfill.resolve(with: (nil, nil))
} else {
// No redirect, no error - shouldn't happen but handle gracefully
fulfill.resolve(with: (nil, nil))
}
}
} else {
fulfill.resolve(with: (appLinkURL, nil))
}
return fulfill
}
private func isIterableDeepLink(_ urlString: String) -> Bool {
guard let regex = try? NSRegularExpression(pattern: Const.deepLinkRegex, options: []) else {
return false
}
return regex.firstMatch(in: urlString, options: [], range: NSMakeRange(0, urlString.count)) != nil
}
private lazy var redirectUrlSession: NetworkSessionProtocol = {
redirectNetworkSessionProvider.createRedirectNetworkSession(delegate: self)
}()
private var redirectNetworkSessionProvider: RedirectNetworkSessionProvider
private var deepLinkLocation: URL?
private var deepLinkCampaignId: NSNumber?
private var deepLinkTemplateId: NSNumber?
private var deepLinkMessageId: String?
}
extension DeepLinkManager: RedirectNetworkSessionDelegate {
func onRedirect(deepLinkLocation: URL?, campaignId: NSNumber?, templateId: NSNumber?, messageId: String?) {
self.deepLinkLocation = deepLinkLocation
self.deepLinkCampaignId = campaignId
self.deepLinkTemplateId = templateId
self.deepLinkMessageId = messageId
}
}