Skip to content

Commit 3e10675

Browse files
authored
Merge pull request #687 from Iterable/evan/MOB-7252-sets-up-click-handling
[MOB-7252] sets up click handling for external/deep linking
2 parents 4b7c554 + 2126aa9 commit 3e10675

7 files changed

+140
-18
lines changed

swift-sdk/Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ enum MobileDeviceType: String, Codable {
374374
case push
375375
case universalLink
376376
case inApp
377+
case embedded
377378
}
378379

379380
// Lowest level that will be logged. By default the LogLevel is set to LogLevel.info.

swift-sdk/Internal/DependencyContainerProtocol.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ extension DependencyContainerProtocol {
5858

5959
func createEmbeddedManager(config: IterableConfig,
6060
apiClient: ApiClientProtocol) -> IterableEmbeddedManagerProtocol {
61-
IterableEmbeddedManager(apiClient: apiClient)
61+
IterableEmbeddedManager(apiClient: apiClient,
62+
urlDelegate: config.urlDelegate,
63+
urlOpener: urlOpener,
64+
allowedProtocols: config.allowedProtocols)
6265
}
6366

6467
func createRequestHandler(apiKey: String,

swift-sdk/Internal/EmptyEmbeddedManager.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ class EmptyEmbeddedManager: IterableEmbeddedManagerProtocol {
2626

2727
}
2828

29+
public func handleEmbeddedClick(message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String) {
30+
31+
}
32+
33+
func reset() {
34+
35+
}
36+
2937
func track(click message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String) {
3038

3139
}

swift-sdk/Internal/InternalIterableAPI.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,7 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
548548
authManager.logoutUser()
549549

550550
_ = inAppManager.reset()
551+
_ = embeddedManager.reset()
551552

552553
try? requestHandler.handleLogout()
553554
}

swift-sdk/Internal/IterableEmbeddedManager.swift

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ import Foundation
66
import UIKit
77

88
class IterableEmbeddedManager: NSObject, IterableEmbeddedManagerProtocol {
9-
init(apiClient: ApiClientProtocol) {
10-
ITBInfo()
9+
init(apiClient: ApiClientProtocol,
10+
urlDelegate: IterableURLDelegate?,
11+
urlOpener: UrlOpenerProtocol,
12+
allowedProtocols: [String]) {
13+
ITBInfo()
1114

1215
self.apiClient = apiClient
16+
self.urlDelegate = urlDelegate
17+
self.urlOpener = urlOpener
18+
self.allowedProtocols = allowedProtocols
19+
1320
super.init()
1421
addForegroundObservers()
1522
}
@@ -43,8 +50,53 @@ class IterableEmbeddedManager: NSObject, IterableEmbeddedManagerProtocol {
4350
public func syncMessages(completion: @escaping () -> Void) {
4451
retrieveEmbeddedMessages(completion: completion)
4552
}
53+
54+
public func handleEmbeddedClick(message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String) {
55+
56+
if let url = URL(string: clickedUrl) {
57+
handleUrl(url: url.absoluteString)
58+
} else {
59+
print("Invalid URL: \(clickedUrl)")
60+
}
61+
}
62+
63+
public func reset() {
64+
let processor = EmbeddedMessagingProcessor(currentMessages: self.messages, fetchedMessages: [])
65+
self.setMessages(processor)
66+
self.notifyUpdateDelegates(processor)
67+
}
68+
69+
private func createAction(fromUrlOrAction url: String) -> IterableAction? {
70+
if let parsedUrl = URL(string: url), let _ = parsedUrl.scheme {
71+
return IterableAction.actionOpenUrl(fromUrlString: url)
72+
} else {
73+
return IterableAction.action(fromDictionary: ["type": url])
74+
}
75+
}
76+
77+
private func handleUrl(url: String) {
78+
guard let action = createAction(fromUrlOrAction: url) else {
79+
ITBError("Could not create action from: \(url)")
80+
return
81+
}
82+
83+
let context = IterableActionContext(action: action, source: .embedded)
84+
DispatchQueue.main.async { [weak self] in
85+
ActionRunner.execute(action: action,
86+
context: context,
87+
urlHandler: IterableUtil.urlHandler(fromUrlDelegate: self?.urlDelegate, inContext: context),
88+
urlOpener: self?.urlOpener,
89+
allowedProtocols: self?.allowedProtocols ?? [])
90+
}
91+
}
4692

4793
// MARK: - PRIVATE/INTERNAL
94+
private var apiClient: ApiClientProtocol
95+
private let urlDelegate: IterableURLDelegate?
96+
private let urlOpener: UrlOpenerProtocol
97+
private let allowedProtocols: [String]
98+
private var messages: [IterableEmbeddedMessage] = []
99+
private var listeners: NSHashTable<IterableEmbeddedUpdateDelegate> = NSHashTable(options: [.weakMemory])
48100

49101
private func addForegroundObservers() {
50102
NotificationCenter.default.addObserver(self,
@@ -124,9 +176,4 @@ class IterableEmbeddedManager: NSObject, IterableEmbeddedManagerProtocol {
124176
listener.onEmbeddedMessagingDisabled()
125177
}
126178
}
127-
private var apiClient: ApiClientProtocol
128-
129-
private var messages: [IterableEmbeddedMessage] = []
130-
131-
private var listeners: NSHashTable<IterableEmbeddedUpdateDelegate> = NSHashTable(options: [.weakMemory])
132179
}

swift-sdk/IterableEmbeddedManagerProtocol.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ import Foundation
1212
func removeUpdateListener(_ listener: IterableEmbeddedUpdateDelegate)
1313

1414
func syncMessages(completion: @escaping () -> Void)
15+
func handleEmbeddedClick(message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String)
16+
func reset()
1517
}

tests/unit-tests/EmbeddedManagerTests.swift

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ final class EmbeddedManagerTests: XCTestCase {
1212

1313
let mockApiClient = MockApiClient()
1414

15-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
15+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
16+
urlDelegate: nil,
17+
urlOpener: MockUrlOpener(),
18+
allowedProtocols: [])
1619

1720
let view1 = ViewWithUpdateDelegate(
1821
onMessagesUpdatedCallback: {
@@ -32,7 +35,10 @@ final class EmbeddedManagerTests: XCTestCase {
3235
// getMessages
3336
func testGetMessagesWhenEmpty() {
3437
let mockApiClient = MockApiClient()
35-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
38+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
39+
urlDelegate: nil,
40+
urlOpener: MockUrlOpener(),
41+
allowedProtocols: [])
3642
XCTAssertEqual(manager.getMessages().count, 0)
3743
}
3844
func testGetMessagesForPlacement() {
@@ -43,9 +49,12 @@ final class EmbeddedManagerTests: XCTestCase {
4349
IterableEmbeddedMessage(messageId: "3", placementId: 2),
4450
IterableEmbeddedMessage(messageId: "4", placementId: 3),
4551
])
46-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
52+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
53+
urlDelegate: nil,
54+
urlOpener: MockUrlOpener(),
55+
allowedProtocols: [])
4756

48-
manager.syncMessages {}
57+
manager.syncMessages { }
4958

5059
let messagesForPlacement2 = manager.getMessages(for: 2)
5160

@@ -67,7 +76,10 @@ final class EmbeddedManagerTests: XCTestCase {
6776
IterableEmbeddedMessage(messageId: "2", placementId: 1),
6877
])
6978

70-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
79+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
80+
urlDelegate: nil,
81+
urlOpener: MockUrlOpener(),
82+
allowedProtocols: [])
7183

7284
let view = ViewWithUpdateDelegate(
7385
onMessagesUpdatedCallback: {
@@ -84,12 +96,42 @@ final class EmbeddedManagerTests: XCTestCase {
8496

8597
wait(for: [syncMessagesExpectation, delegateExpectation], timeout: 2)
8698
}
99+
100+
func testManagerReset() {
101+
let syncMessagesExpectation = expectation(description: "syncMessages should complete")
102+
103+
let mockApiClient = MockApiClient()
104+
105+
mockApiClient.populateMessages([
106+
IterableEmbeddedMessage(messageId: "1", placementId: 1),
107+
IterableEmbeddedMessage(messageId: "2", placementId: 1),
108+
])
109+
110+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
111+
urlDelegate: nil,
112+
urlOpener: MockUrlOpener(),
113+
allowedProtocols: [])
114+
115+
manager.syncMessages {
116+
syncMessagesExpectation.fulfill()
117+
}
118+
119+
wait(for: [syncMessagesExpectation], timeout: 2)
120+
121+
manager.reset()
122+
123+
XCTAssertEqual(manager.getMessages().count, 0)
124+
}
125+
87126
func testSyncMessagesFailedDueToInvalidAPIKey() {
88127
let condition = expectation(description: "syncMessages should notify of disabled messaging due to invalid API Key")
89128

90129
let mockApiClient = MockApiClient()
91130
mockApiClient.setInvalidAPIKey()
92-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
131+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
132+
urlDelegate: nil,
133+
urlOpener: MockUrlOpener(),
134+
allowedProtocols: [])
93135

94136
let view = ViewWithUpdateDelegate(
95137
onMessagesUpdatedCallback: nil,
@@ -108,7 +150,10 @@ final class EmbeddedManagerTests: XCTestCase {
108150
// notify multiple delegates
109151
func testManagerNotifiesMultipleDelegates() {
110152
let mockApiClient = MockApiClient()
111-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
153+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
154+
urlDelegate: nil,
155+
urlOpener: MockUrlOpener(),
156+
allowedProtocols: [])
112157

113158
var delegate1Called = false
114159
var delegate2Called = false
@@ -137,7 +182,10 @@ final class EmbeddedManagerTests: XCTestCase {
137182
// add and remove listeners
138183
func testManagerCorrectlyAddsAndRemovesListeners() {
139184
let mockApiClient = MockApiClient()
140-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
185+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
186+
urlDelegate: nil,
187+
urlOpener: MockUrlOpener(),
188+
allowedProtocols: [])
141189

142190
var delegateCalled = false
143191

@@ -169,7 +217,11 @@ final class EmbeddedManagerTests: XCTestCase {
169217
// init/deinit
170218
func testManagerInitializationAndDeinitialization() {
171219
let deinitExpectation = expectation(description: "Manager should deinitialize")
172-
var manager: IterableEmbeddedManager? = IterableEmbeddedManager(apiClient: MockApiClient())
220+
let mockApiClient = MockApiClient()
221+
var manager: IterableEmbeddedManager? = IterableEmbeddedManager(apiClient: mockApiClient,
222+
urlDelegate: nil,
223+
urlOpener: MockUrlOpener(),
224+
allowedProtocols: [])
173225
manager?.onDeinit = {
174226
deinitExpectation.fulfill()
175227
}
@@ -184,7 +236,10 @@ final class EmbeddedManagerTests: XCTestCase {
184236
let expectation = XCTestExpectation(description: "onMessagesUpdated called")
185237

186238
let mockApiClient = MockApiClient()
187-
let manager = IterableEmbeddedManager(apiClient: mockApiClient)
239+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
240+
urlDelegate: nil,
241+
urlOpener: MockUrlOpener(),
242+
allowedProtocols: [])
188243

189244
let mockDelegate = ViewWithUpdateDelegate(
190245
onMessagesUpdatedCallback: {
@@ -229,6 +284,11 @@ final class EmbeddedManagerTests: XCTestCase {
229284
}
230285
}
231286

287+
private class MockUrlOpener: NSObject, UrlOpenerProtocol {
288+
func open(url: URL) {
289+
}
290+
}
291+
232292
private class MockApiClient: BlankApiClient {
233293
private var newMessages = false
234294
private var invalidApiKey = false

0 commit comments

Comments
 (0)