Skip to content

Commit 16b8864

Browse files
authored
Merge pull request #880 from Iterable/feature/MOB-9233-in-app-json-only
MOB-9233 Support for In App JSON Only Messages
2 parents fe619f3 + 0e0f8a8 commit 16b8864

10 files changed

+741
-38
lines changed

.editorconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
tab_width = 4

swift-sdk/Core/Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ enum JsonKey {
253253
static let packageName = "packageName"
254254
static let sdkVersion = "SDKVersion"
255255
static let content = "content"
256+
static let jsonOnly = "jsonOnly"
256257
}
257258

258259
enum Payload {

swift-sdk/Core/Models/IterableInAppMessage.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ import Foundation
5151

5252
/// the urgency level of this message (nil will be treated as `unassigned` when displaying this message)
5353
public var priorityLevel: Double
54+
55+
/// Whether this message is a JSON-only message
56+
public let jsonOnly: Bool
5457

5558
// MARK: - Private/Internal
5659

@@ -64,7 +67,8 @@ import Foundation
6467
inboxMetadata: IterableInboxMetadata? = nil,
6568
customPayload: [AnyHashable: Any]? = nil,
6669
read: Bool = false,
67-
priorityLevel: Double = Const.PriorityLevel.unassigned) {
70+
priorityLevel: Double = Const.PriorityLevel.unassigned,
71+
jsonOnly: Bool = false) {
6872
self.messageId = messageId
6973
self.campaignId = campaignId
7074
self.trigger = trigger
@@ -76,5 +80,6 @@ import Foundation
7680
self.customPayload = customPayload
7781
self.read = read
7882
self.priorityLevel = priorityLevel
83+
self.jsonOnly = jsonOnly
7984
}
8085
}

swift-sdk/Internal/in-app/InAppManager+Functions.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Foundation
66

77
enum MessagesProcessorResult {
88
case show(message: IterableInAppMessage, messagesMap: OrderedDictionary<String, IterableInAppMessage>)
9-
case noShow(messagesMap: OrderedDictionary<String, IterableInAppMessage>)
9+
case noShow(message: IterableInAppMessage?, messagesMap: OrderedDictionary<String, IterableInAppMessage>)
1010
}
1111

1212
struct MessagesProcessor {
@@ -30,14 +30,18 @@ struct MessagesProcessor {
3030
case let .skip(message):
3131
updateMessage(message, didProcessTrigger: true)
3232
return processMessages()
33+
case let .skipAndConsume(message):
34+
updateMessage(message, didProcessTrigger: true, consumed: true)
35+
return .noShow(message: message, messagesMap: messagesMap)
3336
case .none, .wait:
34-
return .noShow(messagesMap: messagesMap)
37+
return .noShow(message: nil, messagesMap: messagesMap)
3538
}
3639
}
3740

3841
private enum ProcessNextMessageResult {
3942
case show(IterableInAppMessage)
4043
case skip(IterableInAppMessage)
44+
case skipAndConsume(IterableInAppMessage)
4145
case none
4246
case wait
4347
}
@@ -59,7 +63,11 @@ struct MessagesProcessor {
5963

6064
ITBDebug("isOkToShowNow")
6165

62-
if inAppDelegate.onNew(message: message) == .show {
66+
let returnValue = inAppDelegate.onNew(message: message)
67+
if message.isJsonOnly {
68+
return .skipAndConsume(message)
69+
}
70+
if returnValue == .show {
6371
ITBDebug("delegate returned show")
6472
return .show(message)
6573
} else {

swift-sdk/Internal/in-app/InAppManager.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ class InAppManager: NSObject, IterableInternalInAppManagerProtocol {
286286

287287
private func getMessagesMap(fromMessagesProcessorResult messagesProcessorResult: MessagesProcessorResult) -> OrderedDictionary<String, IterableInAppMessage> {
288288
switch messagesProcessorResult {
289-
case let .noShow(messagesMap: messagesMap):
289+
case let .noShow(message: _, messagesMap: messagesMap):
290290
return messagesMap
291291
case .show(message: _, messagesMap: let messagesMap):
292292
return messagesMap
@@ -308,6 +308,13 @@ class InAppManager: NSObject, IterableInternalInAppManagerProtocol {
308308
let messagesProcessorResult = processor.processMessages()
309309
self.messagesMap = getMessagesMap(fromMessagesProcessorResult: messagesProcessorResult)
310310

311+
if case let .noShow(message, _) = messagesProcessorResult,
312+
let message = message, message.isJsonOnly {
313+
requestHandler?.inAppConsume(message.messageId,
314+
onSuccess: nil,
315+
onFailure: nil)
316+
}
317+
311318
showMessage(fromMessagesProcessorResult: messagesProcessorResult)
312319
}
313320

@@ -316,6 +323,10 @@ class InAppManager: NSObject, IterableInternalInAppManagerProtocol {
316323
callback: ITBURLCallback? = nil) {
317324
ITBInfo()
318325

326+
guard !message.isJsonOnly else {
327+
return
328+
}
329+
319330
guard Thread.isMainThread else {
320331
ITBError("This must be called from the main thread")
321332
return

swift-sdk/Internal/in-app/InAppMessageParser.swift

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -81,41 +81,79 @@ struct InAppMessageParser {
8181
return .failure(.parseFailed(reason: "no messageId", messageId: nil))
8282
}
8383

84-
guard let contentDict = json[JsonKey.InApp.content] as? [AnyHashable: Any] else {
85-
return .failure(.parseFailed(reason: "no content in json payload", messageId: messageId))
86-
}
87-
88-
let content: IterableInAppContent
84+
// Check if the jsonOnly key is present and is set to 1 (true)
85+
let jsonOnly = (json[JsonKey.InApp.jsonOnly] as? Int ?? 0) == 1
86+
var customPayload = parseCustomPayload(fromPayload: json)
8987

90-
switch InAppContentParser.parse(contentDict: contentDict) {
91-
case let .success(parsedContent):
92-
content = parsedContent
93-
case let .failure(reason):
94-
return .failure(.parseFailed(reason: reason, messageId: messageId))
88+
if jsonOnly && customPayload == nil {
89+
customPayload = [:]
9590
}
9691

92+
// For non-JSON-only messages, we require content
93+
if !jsonOnly {
94+
guard let contentDict = json[JsonKey.InApp.content] as? [AnyHashable: Any] else {
95+
return .failure(.parseFailed(reason: "no content in json payload", messageId: messageId))
96+
}
97+
98+
let content: IterableInAppContent
99+
switch InAppContentParser.parse(contentDict: contentDict) {
100+
case let .success(parsedContent):
101+
content = parsedContent
102+
case let .failure(reason):
103+
return .failure(.parseFailed(reason: reason, messageId: messageId))
104+
}
105+
106+
return .success(createMessage(
107+
messageId: messageId,
108+
json: json,
109+
content: content,
110+
customPayload: customPayload,
111+
jsonOnly: jsonOnly
112+
))
113+
} else {
114+
// For JSON-only messages, use default HTML content
115+
let content = IterableHtmlInAppContent(edgeInsets: .zero, html: "")
116+
117+
return .success(createMessage(
118+
messageId: messageId,
119+
json: json,
120+
content: content,
121+
customPayload: customPayload,
122+
jsonOnly: jsonOnly
123+
))
124+
}
125+
}
126+
127+
private static func createMessage(
128+
messageId: String,
129+
json: [AnyHashable: Any],
130+
content: IterableInAppContent,
131+
customPayload: [AnyHashable: Any]?,
132+
jsonOnly: Bool
133+
) -> IterableInAppMessage {
97134
let campaignId = json[JsonKey.campaignId] as? NSNumber
98-
99-
let saveToInbox = json[JsonKey.saveToInbox] as? Bool ?? false
135+
let saveToInbox = (json[JsonKey.saveToInbox] as? Bool ?? false) && !jsonOnly // Force false for JSON-only
100136
let inboxMetadata = parseInboxMetadata(fromPayload: json)
101137
let trigger = parseTrigger(fromTriggerElement: json[JsonKey.InApp.trigger] as? [AnyHashable: Any])
102-
let customPayload = parseCustomPayload(fromPayload: json)
103138
let createdAt = parseTime(withKey: JsonKey.inboxCreatedAt, fromJson: json)
104139
let expiresAt = parseTime(withKey: JsonKey.inboxExpiresAt, fromJson: json)
105140
let read = json[JsonKey.read] as? Bool ?? false
106141
let priorityLevel = json[JsonKey.priorityLevel] as? Double ?? Const.PriorityLevel.unassigned
107142

108-
return .success(IterableInAppMessage(messageId: messageId,
109-
campaignId: campaignId,
110-
trigger: trigger,
111-
createdAt: createdAt,
112-
expiresAt: expiresAt,
113-
content: content,
114-
saveToInbox: saveToInbox,
115-
inboxMetadata: inboxMetadata,
116-
customPayload: customPayload,
117-
read: read,
118-
priorityLevel: priorityLevel))
143+
return IterableInAppMessage(
144+
messageId: messageId,
145+
campaignId: campaignId,
146+
trigger: trigger,
147+
createdAt: createdAt,
148+
expiresAt: expiresAt,
149+
content: content,
150+
saveToInbox: saveToInbox,
151+
inboxMetadata: inboxMetadata,
152+
customPayload: customPayload,
153+
read: read,
154+
priorityLevel: priorityLevel,
155+
jsonOnly: jsonOnly
156+
)
119157
}
120158

121159
private static func parseTime(withKey key: AnyHashable, fromJson json: [AnyHashable: Any]) -> Date? {

swift-sdk/Internal/in-app/InAppPersistence.swift

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ extension IterableInAppMessage: Codable {
235235
case trigger
236236
case content
237237
case priorityLevel
238+
case jsonOnly
238239
}
239240

240241
enum ContentCodingKeys: String, CodingKey {
@@ -252,20 +253,26 @@ extension IterableInAppMessage: Codable {
252253
return
253254
}
254255

256+
let jsonOnly = (try? container.decode(Int.self, forKey: .jsonOnly)) ?? 0
257+
let customPayloadData = try? container.decode(Data.self, forKey: .customPayload)
258+
var customPayload = IterableInAppMessage.deserializeCustomPayload(withData: customPayloadData)
259+
260+
if jsonOnly == 1 && customPayload == nil {
261+
customPayload = [:]
262+
}
263+
255264
let saveToInbox = (try? container.decode(Bool.self, forKey: .saveToInbox)) ?? false
256265
let inboxMetadata = (try? container.decode(IterableInboxMetadata.self, forKey: .inboxMetadata))
257266
let messageId = (try? container.decode(String.self, forKey: .messageId)) ?? ""
258267
let campaignId = (try? container.decode(Int.self, forKey: .campaignId)).map { NSNumber(value: $0) }
259268
let createdAt = (try? container.decode(Date.self, forKey: .createdAt))
260269
let expiresAt = (try? container.decode(Date.self, forKey: .expiresAt))
261-
let customPayloadData = try? container.decode(Data.self, forKey: .customPayload)
262-
let customPayload = IterableInAppMessage.deserializeCustomPayload(withData: customPayloadData)
263270
let didProcessTrigger = (try? container.decode(Bool.self, forKey: .didProcessTrigger)) ?? false
264271
let consumed = (try? container.decode(Bool.self, forKey: .consumed)) ?? false
265272
let read = (try? container.decode(Bool.self, forKey: .read)) ?? false
266273

267274
let trigger = (try? container.decode(IterableInAppTrigger.self, forKey: .trigger)) ?? .undefinedTrigger
268-
let content = IterableInAppMessage.decodeContent(from: container)
275+
let content = IterableInAppMessage.decodeContent(from: container, isJsonOnly: jsonOnly == 1)
269276
let priorityLevel = (try? container.decode(Double.self, forKey: .priorityLevel)) ?? Const.PriorityLevel.unassigned
270277

271278
self.init(messageId: messageId,
@@ -274,21 +281,29 @@ extension IterableInAppMessage: Codable {
274281
createdAt: createdAt,
275282
expiresAt: expiresAt,
276283
content: content,
277-
saveToInbox: saveToInbox,
284+
saveToInbox: saveToInbox && jsonOnly != 1,
278285
inboxMetadata: inboxMetadata,
279286
customPayload: customPayload,
280287
read: read,
281-
priorityLevel: priorityLevel)
288+
priorityLevel: priorityLevel,
289+
jsonOnly: jsonOnly == 1)
282290

283291
self.didProcessTrigger = didProcessTrigger
284292
self.consumed = consumed
285293
}
286294

295+
var isJsonOnly: Bool {
296+
return jsonOnly
297+
}
298+
287299
public func encode(to encoder: Encoder) {
288300
var container = encoder.container(keyedBy: CodingKeys.self)
289301

302+
// Encode jsonOnly first
303+
try? container.encode(isJsonOnly ? 1 : 0, forKey: .jsonOnly)
304+
290305
try? container.encode(trigger, forKey: .trigger)
291-
try? container.encode(saveToInbox, forKey: .saveToInbox)
306+
try? container.encode(saveToInbox && !isJsonOnly, forKey: .saveToInbox)
292307
try? container.encode(messageId, forKey: .messageId)
293308
try? container.encode(campaignId as? Int, forKey: .campaignId)
294309
try? container.encode(createdAt, forKey: .createdAt)
@@ -303,7 +318,10 @@ extension IterableInAppMessage: Codable {
303318
try? container.encode(inboxMetadata, forKey: .inboxMetadata)
304319
}
305320

306-
IterableInAppMessage.encode(content: content, inContainer: &container)
321+
// Only encode content if not JSON-only
322+
if !isJsonOnly {
323+
IterableInAppMessage.encode(content: content, inContainer: &container)
324+
}
307325
}
308326

309327
private static func createDefaultContent() -> IterableInAppContent {
@@ -328,7 +346,11 @@ extension IterableInAppMessage: Codable {
328346
return deserialized as? [AnyHashable: Any]
329347
}
330348

331-
private static func decodeContent(from container: KeyedDecodingContainer<IterableInAppMessage.CodingKeys>) -> IterableInAppContent {
349+
private static func decodeContent(from container: KeyedDecodingContainer<IterableInAppMessage.CodingKeys>, isJsonOnly: Bool) -> IterableInAppContent {
350+
if isJsonOnly {
351+
return createDefaultContent()
352+
}
353+
332354
guard let contentContainer = try? container.nestedContainer(keyedBy: ContentCodingKeys.self, forKey: .content) else {
333355
ITBError()
334356

@@ -357,6 +379,7 @@ extension IterableInAppMessage: Codable {
357379
}
358380
}
359381
}
382+
360383
}
361384

362385
protocol InAppPersistenceProtocol {

tests/endpoint-tests/scripts/run_test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ sed -e "s/\(apiKey = \).*$/\1\"$api_key\"/" \
2323
xcodebuild -project swift-sdk.xcodeproj \
2424
-scheme endpoint-tests \
2525
-sdk iphonesimulator \
26-
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
26+
-destination 'platform=iOS Simulator,OS=18.1,name=iPhone 16 Pro' \
2727
test | xcpretty

0 commit comments

Comments
 (0)