Skip to content

Commit 6ff8247

Browse files
authored
SDK-26 Add placement id list as a parameter for syncMessages (#985)
1 parent 8ddb9b9 commit 6ff8247

File tree

10 files changed

+149
-17
lines changed

10 files changed

+149
-17
lines changed

swift-sdk/Core/Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ enum JsonKey {
316316
enum Embedded {
317317
static let packageName = "packageName"
318318
static let sdkVersion = "SDKVersion"
319+
static let placementIds = "placementIds"
319320
}
320321

321322
enum Header {

swift-sdk/Core/Protocols/IterableEmbeddedManagerProtocol.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ public protocol IterableEmbeddedManagerProtocol {
1010
func addUpdateListener(_ listener: IterableEmbeddedUpdateDelegate)
1111
func removeUpdateListener(_ listener: IterableEmbeddedUpdateDelegate)
1212

13-
func syncMessages(completion: @escaping () -> Void)
13+
func syncMessages(placementIds: [Int]?, completion: @escaping () -> Void)
1414
func handleEmbeddedClick(message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String)
1515
func reset()
1616
}
17+
18+
public extension IterableEmbeddedManagerProtocol {
19+
func syncMessages(completion: @escaping () -> Void) {
20+
syncMessages(placementIds: nil, completion: completion)
21+
}
22+
}

swift-sdk/Internal/EmptyEmbeddedManager.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class EmptyEmbeddedManager: IterableInternalEmbeddedManagerProtocol {
2323

2424
func syncMessages(completion: @escaping () -> Void) {
2525

26+
}
27+
28+
func syncMessages(placementIds: [Int]?, completion: @escaping () -> Void) {
29+
2630
}
2731

2832
public func handleEmbeddedClick(message: IterableEmbeddedMessage, buttonIdentifier: String?, clickedUrl: String) {

swift-sdk/Internal/IterableEmbeddedManager.swift

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ class IterableEmbeddedManager: NSObject, IterableInternalEmbeddedManagerProtocol
161161
syncMessages { }
162162
}
163163

164-
private func retrieveEmbeddedMessages(completion: @escaping () -> Void) {
165-
apiClient.getEmbeddedMessages()
164+
private func retrieveEmbeddedMessages(placementIds: [Int]?, completion: @escaping () -> Void) {
165+
apiClient.getEmbeddedMessages(placementIds: placementIds)
166166
.onCompletion(
167167
receiveValue: { embeddedMessagesPayload in
168168
let placements = embeddedMessagesPayload.placements
@@ -172,7 +172,18 @@ class IterableEmbeddedManager: NSObject, IterableInternalEmbeddedManagerProtocol
172172
fetchedMessagesDict[placement.placementId!] = placement.embeddedMessages
173173
}
174174

175-
let processor = EmbeddedMessagingProcessor(currentMessages: self.messages,
175+
let currentMessagesSnapshot: [Int: [IterableEmbeddedMessage]] = self.messageProcessingQueue.sync {
176+
self.messages
177+
}
178+
179+
if let placementIds, !placementIds.isEmpty {
180+
let requestedPlacementIds = Set(placementIds)
181+
for (placementId, currentMessages) in currentMessagesSnapshot where !requestedPlacementIds.contains(placementId) {
182+
fetchedMessagesDict[placementId] = currentMessages
183+
}
184+
}
185+
186+
let processor = EmbeddedMessagingProcessor(currentMessages: currentMessagesSnapshot,
176187
fetchedMessages: fetchedMessagesDict)
177188

178189
self.setMessages(processor)
@@ -260,8 +271,12 @@ class IterableEmbeddedManager: NSObject, IterableInternalEmbeddedManagerProtocol
260271

261272
extension IterableEmbeddedManager: EmbeddedNotifiable {
262273
public func syncMessages(completion: @escaping () -> Void) {
263-
if (enableEmbeddedMessaging) {
264-
retrieveEmbeddedMessages(completion: completion)
274+
syncMessages(placementIds: nil, completion: completion)
275+
}
276+
277+
public func syncMessages(placementIds: [Int]?, completion: @escaping () -> Void) {
278+
if enableEmbeddedMessaging {
279+
retrieveEmbeddedMessages(placementIds: placementIds, completion: completion)
265280
}
266281
}
267282
}

swift-sdk/Internal/api-client/ApiClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ extension ApiClient: ApiClientProtocol {
300300

301301
// MARK: - Embedded Messaging
302302

303-
func getEmbeddedMessages() -> Pending<PlacementsPayload, SendRequestError> {
304-
let result = createRequestCreator().flatMap { $0.createGetEmbeddedMessagesRequest() }
303+
func getEmbeddedMessages(placementIds: [Int]?) -> Pending<PlacementsPayload, SendRequestError> {
304+
let result = createRequestCreator().flatMap { $0.createGetEmbeddedMessagesRequest(placementIds: placementIds) }
305305
return send(iterableRequestResult: result)
306306
}
307307

swift-sdk/Internal/api-client/ApiClientProtocol.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protocol ApiClientProtocol: AnyObject {
6060

6161
func trackConsent(consentTimestamp: Int64, email: String?, userId: String?, isUserKnown: Bool) -> Pending<SendRequestValue, SendRequestError>
6262

63-
func getEmbeddedMessages() -> Pending<PlacementsPayload, SendRequestError>
63+
func getEmbeddedMessages(placementIds: [Int]?) -> Pending<PlacementsPayload, SendRequestError>
6464

6565
@discardableResult func track(embeddedMessageReceived message: IterableEmbeddedMessage) -> Pending<SendRequestValue, SendRequestError>
6666

@@ -72,3 +72,9 @@ protocol ApiClientProtocol: AnyObject {
7272

7373
@discardableResult func track(embeddedSession: IterableEmbeddedSession) -> Pending<SendRequestValue, SendRequestError>
7474
}
75+
76+
extension ApiClientProtocol {
77+
func getEmbeddedMessages() -> Pending<PlacementsPayload, SendRequestError> {
78+
getEmbeddedMessages(placementIds: nil)
79+
}
80+
}

swift-sdk/Internal/api-client/Request/RequestCreator.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ struct RequestCreator {
500500

501501
// MARK: - Embedded Messaging Request Calls
502502

503-
func createGetEmbeddedMessagesRequest() -> Result<IterableRequest, IterableError> {
503+
func createGetEmbeddedMessagesRequest(placementIds: [Int]?) -> Result<IterableRequest, IterableError> {
504504
if case .none = auth.emailOrUserId {
505505
ITBError(Self.authMissingMessage)
506506
return .failure(IterableError.general(description: Self.authMissingMessage))
@@ -513,10 +513,28 @@ struct RequestCreator {
513513
if let packageName = Bundle.main.appPackageName {
514514
args[JsonKey.Embedded.packageName] = packageName
515515
}
516-
517516
setCurrentUser(inDict: &args)
518-
519-
return .success(.get(createGetRequest(forPath: Const.Path.getEmbeddedMessages, withArgs: args as! [String: String])))
517+
518+
let placementIdQueryItems: [URLQueryItem]
519+
if let placementIds, !placementIds.isEmpty {
520+
placementIdQueryItems = placementIds.map { URLQueryItem(name: JsonKey.Embedded.placementIds, value: String($0)) }
521+
} else {
522+
placementIdQueryItems = []
523+
}
524+
525+
let argsQueryItems: [URLQueryItem] = args.compactMap { key, value in
526+
guard let key = key as? String, let value = value as? String else { return nil }
527+
if key == JsonKey.Embedded.placementIds { return nil }
528+
return URLQueryItem(name: key, value: value)
529+
}
530+
531+
var components = URLComponents()
532+
components.path = Const.Path.getEmbeddedMessages
533+
components.queryItems = argsQueryItems + placementIdQueryItems
534+
let query = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
535+
let pathWithQuery = components.path + (query.map { "?\($0)" } ?? "")
536+
537+
return .success(.get(GetRequest(path: pathWithQuery, args: nil)))
520538
}
521539

522540
func createEmbeddedMessageReceivedRequest(_ message: IterableEmbeddedMessage) -> Result<IterableRequest, IterableError> {

tests/unit-tests/BlankApiClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class BlankApiClient: ApiClientProtocol {
114114
Pending()
115115
}
116116

117-
func getEmbeddedMessages() -> Pending<PlacementsPayload, SendRequestError> {
117+
func getEmbeddedMessages(placementIds: [Int]?) -> Pending<PlacementsPayload, SendRequestError> {
118118
Pending()
119119
}
120120

tests/unit-tests/EmbeddedManagerTests.swift

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,33 @@ final class EmbeddedManagerTests: XCTestCase {
109109

110110
wait(for: [syncMessagesExpectation, delegateExpectation, syncSuccessExpectation], timeout: 2)
111111
}
112+
113+
func testSyncMessagesWithPlacementIdsDoesNotClearOtherPlacements() {
114+
let mockApiClient = MockApiClient()
115+
mockApiClient.populateMessages([
116+
1: [IterableEmbeddedMessage(messageId: "1a", placementId: 1)],
117+
2: [IterableEmbeddedMessage(messageId: "2a", placementId: 2)],
118+
])
119+
120+
let manager = IterableEmbeddedManager(apiClient: mockApiClient,
121+
urlDelegate: nil,
122+
customActionDelegate: nil,
123+
urlOpener: MockUrlOpener(),
124+
allowedProtocols: [],
125+
enableEmbeddedMessaging: true)
126+
127+
manager.syncMessages { }
128+
XCTAssertEqual(manager.getMessages(for: 2).map { $0.metadata.messageId }, ["2a"])
129+
130+
// Update only placement 1 on the "server", then request only that placement.
131+
mockApiClient.populateMessages([
132+
1: [IterableEmbeddedMessage(messageId: "1b", placementId: 1)],
133+
])
134+
manager.syncMessages(placementIds: [1]) { }
135+
136+
XCTAssertEqual(manager.getMessages(for: 1).map { $0.metadata.messageId }, ["1b"])
137+
XCTAssertEqual(manager.getMessages(for: 2).map { $0.metadata.messageId }, ["2a"])
138+
}
112139

113140
func testManagerReset() {
114141
let syncMessagesExpectation = expectation(description: "syncMessages should complete")
@@ -392,16 +419,19 @@ final class EmbeddedManagerTests: XCTestCase {
392419
invalidApiKey = true
393420
}
394421

395-
override func getEmbeddedMessages() -> IterableSDK.Pending<IterableSDK.PlacementsPayload, IterableSDK.SendRequestError> {
422+
override func getEmbeddedMessages(placementIds: [Int]?) -> IterableSDK.Pending<IterableSDK.PlacementsPayload, IterableSDK.SendRequestError> {
396423
if invalidApiKey {
397424
return FailPending(error: IterableSDK.SendRequestError(reason: "Invalid API Key"))
398425
}
399426

400427
if newMessages {
401428
var placements: [Placement] = []
429+
let requested = Set(placementIds ?? [])
402430
for (placementId, messages) in mockMessages {
403-
let placement = Placement(placementId: placementId, embeddedMessages: messages)
404-
placements.append(placement)
431+
if placementIds == nil || requested.contains(placementId) {
432+
let placement = Placement(placementId: placementId, embeddedMessages: messages)
433+
placements.append(placement)
434+
}
405435
}
406436

407437
let payload = PlacementsPayload(placements: placements)

tests/unit-tests/RequestCreatorTests.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,58 @@ class RequestCreatorTests: XCTestCase {
177177
XCTAssertEqual(args[JsonKey.InApp.count], inAppMessageRequestCount.stringValue)
178178
XCTAssertEqual(args[JsonKey.systemVersion], UIDevice.current.systemVersion)
179179
}
180+
181+
func testGetEmbeddedMessagesRequestFailure() {
182+
let auth = Auth(userId: nil, email: nil, authToken: nil, userIdUnknownUser: nil)
183+
let requestCreator = RequestCreator(auth: auth, deviceMetadata: deviceMetadata)
184+
185+
let failingRequest = requestCreator.createGetEmbeddedMessagesRequest(placementIds: nil)
186+
187+
if let _ = try? failingRequest.get() {
188+
XCTFail("request succeeded despite userId and email being nil")
189+
}
190+
}
191+
192+
func testGetEmbeddedMessagesRequest() {
193+
let request = createRequestCreator().createGetEmbeddedMessagesRequest(placementIds: nil)
194+
let urlRequest = convertToUrlRequest(request)
195+
196+
TestUtils.validateHeader(urlRequest, apiKey)
197+
TestUtils.validate(request: urlRequest, requestType: .get, apiEndPoint: Endpoint.api, path: Const.Path.getEmbeddedMessages)
198+
199+
guard let url = urlRequest.url, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
200+
XCTFail("could not create URLComponents from request url")
201+
return
202+
}
203+
204+
let queryItems = urlComponents.queryItems ?? []
205+
XCTAssertEqual(queryItems.first(where: { $0.name == JsonKey.email })?.value, auth.email)
206+
XCTAssertEqual(queryItems.first(where: { $0.name == JsonKey.Embedded.packageName })?.value, Bundle.main.appPackageName)
207+
XCTAssertEqual(queryItems.first(where: { $0.name == JsonKey.systemVersion })?.value, UIDevice.current.systemVersion)
208+
XCTAssertTrue(queryItems.filter { $0.name == JsonKey.Embedded.placementIds }.isEmpty)
209+
}
210+
211+
func testGetEmbeddedMessagesRequestWithPlacementIds() {
212+
let request = createRequestCreator().createGetEmbeddedMessagesRequest(placementIds: [1, 2, 3])
213+
let urlRequest = convertToUrlRequest(request)
214+
215+
TestUtils.validateHeader(urlRequest, apiKey)
216+
TestUtils.validate(request: urlRequest, requestType: .get, apiEndPoint: Endpoint.api, path: Const.Path.getEmbeddedMessages)
217+
218+
guard let url = urlRequest.url, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
219+
XCTFail("could not create URLComponents from request url")
220+
return
221+
}
222+
223+
let queryItems = urlComponents.queryItems ?? []
224+
XCTAssertEqual(queryItems.first(where: { $0.name == JsonKey.email })?.value, auth.email)
225+
226+
let placementIds = queryItems
227+
.filter { $0.name == JsonKey.Embedded.placementIds }
228+
.compactMap(\.value)
229+
230+
XCTAssertEqual(placementIds, ["1", "2", "3"])
231+
}
180232

181233
func testTrackEventRequest() {
182234
let eventName = "dsfsdf"

0 commit comments

Comments
 (0)