diff --git a/Airship.podspec b/Airship.podspec index e7d549de2..09343f60c 100644 --- a/Airship.podspec +++ b/Airship.podspec @@ -1,4 +1,4 @@ -AIRSHIP_VERSION="18.2.0" +AIRSHIP_VERSION="18.2.1" Pod::Spec.new do |s| s.version = AIRSHIP_VERSION diff --git a/Airship/AirshipAutomation/Source/InAppMessage/InAppMessage.swift b/Airship/AirshipAutomation/Source/InAppMessage/InAppMessage.swift index 486072cd1..c0e907ce5 100644 --- a/Airship/AirshipAutomation/Source/InAppMessage/InAppMessage.swift +++ b/Airship/AirshipAutomation/Source/InAppMessage/InAppMessage.swift @@ -113,7 +113,7 @@ public struct InAppMessage: Codable, Equatable, Sendable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let name = try container.decode(String.self, forKey: .name) + let name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" let source = try container.decodeIfPresent(InAppMessageSource.self, forKey: .source) let extras = try container.decodeIfPresent(AirshipJSON.self, forKey: .extras) let actions = try container.decodeIfPresent(AirshipJSON.self, forKey: .actions) diff --git a/Airship/AirshipAutomation/Source/RemoteData/AutomationRemoteDataAccess.swift b/Airship/AirshipAutomation/Source/RemoteData/AutomationRemoteDataAccess.swift index e9c61f356..0b9ec27f6 100644 --- a/Airship/AirshipAutomation/Source/RemoteData/AutomationRemoteDataAccess.swift +++ b/Airship/AirshipAutomation/Source/RemoteData/AutomationRemoteDataAccess.swift @@ -188,6 +188,29 @@ struct InAppRemoteData: Sendable { case schedules = "in_app_messages" case constraints = "frequency_constraints" } + + init(schedules: [AutomationSchedule], constraints: [FrequencyConstraint]? ) { + self.schedules = schedules + self.constraints = constraints + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let decodedSchedules = try container + .decode([ScheduleOrError].self, forKey: .schedules) + .compactMap { parsed in + switch(parsed) { + case .succeed(let result): return result + case .failed(let error): + AirshipLogger.warn("Failed to parse schedule \(error)") + return nil + } + } + + self.schedules = decodedSchedules + self.constraints = try container.decodeIfPresent([FrequencyConstraint].self, forKey: .constraints) + } } struct Payload { @@ -256,3 +279,18 @@ struct InAppRemoteData: Sendable { return InAppRemoteData(payloads: parsed) } } + +fileprivate enum ScheduleOrError: Decodable { + + case succeed(AutomationSchedule) + case failed(Error) + + init(from decoder: any Decoder) throws { + do { + let schedule = try AutomationSchedule(from: decoder) + self = .succeed(schedule) + } catch { + self = .failed(error) + } + } +} diff --git a/Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageTest.swift b/Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageTest.swift index f15a80732..4d038d8c5 100644 --- a/Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageTest.swift +++ b/Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageTest.swift @@ -442,6 +442,28 @@ final class InAppMessageTest: XCTestCase { try verify(json: json, expected: expected) } + + func testNamePropertyDefaultsToEmptyString() throws { + let json = """ + { + "source": "app-defined", + "display" : { + "cool": "story" + }, + "display_type" : "custom", + } + """ + + let expected = InAppMessage( + name: "", + displayContent: .custom( + AirshipJSON.object(["cool": .string("story")]) + ), + source: .appDefined + ) + + try verify(json: json, expected: expected) + } func verify(json: String, expected: InAppMessage) throws { let decoder = JSONDecoder() diff --git a/Airship/AirshipAutomation/Tests/RemoteData/AutomationRemoteDataAccessTest.swift b/Airship/AirshipAutomation/Tests/RemoteData/AutomationRemoteDataAccessTest.swift index 10976da26..84bd5c716 100644 --- a/Airship/AirshipAutomation/Tests/RemoteData/AutomationRemoteDataAccessTest.swift +++ b/Airship/AirshipAutomation/Tests/RemoteData/AutomationRemoteDataAccessTest.swift @@ -222,6 +222,73 @@ final class AutomationRemoteDataAccessTest: XCTestCase { XCTAssertEqual(self.remoteData.notifiedOutdatedInfos, [info]) } + func testRemoteDataInfoIgnoresInvalidSchedules() throws { + let validSchedule = """ + { + "id": "test_schedule", + "triggers": [ + { + "type": "custom_event_count", + "goal": 1, + "id": "json-id" + } + ], + "group": "test_group", + "priority": 2, + "limit": 5, + "start": "2023-12-20T00:00:00Z", + "end": "2023-12-21T00:00:00Z", + "audience": {}, + "delay": {}, + "interval": 3600, + "type": "actions", + "actions": { + "foo": "bar", + }, + "bypass_holdout_groups": true, + "edit_grace_period": 7, + "metadata": {}, + "frequency_constraint_ids": ["constraint1", "constraint2"], + "message_type": "test_type", + "last_updated": "2023-12-20T12:30:00Z", + "created": "2023-12-20T12:00:00Z" + } + """ + let invalidSchedule = """ + { + "priority": 2, + "limit": 5, + "start": "2023-12-20T00:00:00Z", + "end": "2023-12-21T00:00:00Z", + "audience": {}, + "delay": {}, + "interval": 3600, + "type": "actions", + "actions": { + "foo": "bar", + }, + "bypass_holdout_groups": true, + "edit_grace_period": 7, + "metadata": {}, + "frequency_constraint_ids": ["constraint1", "constraint2"], + "message_type": "test_type", + "last_updated": "2023-12-20T12:30:00Z", + "created": "2023-12-20T12:00:00Z" + } + """ + + let dataJson = try AirshipJSON.from(json: "{\"in_app_messages\": [\(validSchedule), \(invalidSchedule)]}") + let payload = RemoteDataPayload( + type: "schedule_test", + timestamp: Date(), + data: dataJson, + remoteDataInfo: nil) + + let decoded: InAppRemoteData.Data = try payload.data.decode() + XCTAssertEqual(1, decoded.schedules.count) + XCTAssertEqual("test_schedule", decoded.schedules.first?.identifier) + } + private func makeSchedule(remoteDataInfo: RemoteDataInfo?) -> AutomationSchedule { return AutomationSchedule( identifier: UUID().uuidString, diff --git a/Airship/AirshipConfig.xcconfig b/Airship/AirshipConfig.xcconfig index 188429853..b59e939bf 100644 --- a/Airship/AirshipConfig.xcconfig +++ b/Airship/AirshipConfig.xcconfig @@ -1,6 +1,6 @@ //* Copyright Airship and Contributors */ -CURRENT_PROJECT_VERSION = 18.2.0 +CURRENT_PROJECT_VERSION = 18.2.1 // Uncomment to include the preview build warning // OTHER_CFLAGS = $(inherited) -DUA_PREVIEW=1 diff --git a/Airship/AirshipCore/Source/AirshipVersion.swift b/Airship/AirshipCore/Source/AirshipVersion.swift index a34f971bd..4abff403a 100644 --- a/Airship/AirshipCore/Source/AirshipVersion.swift +++ b/Airship/AirshipCore/Source/AirshipVersion.swift @@ -3,7 +3,7 @@ import Foundation public struct AirshipVersion { - public static let version = "18.2.0" + public static let version = "18.2.1" public static func get() -> String { return version } diff --git a/AirshipContentExtension.podspec b/AirshipContentExtension.podspec index 6e1b42bca..76622d737 100644 --- a/AirshipContentExtension.podspec +++ b/AirshipContentExtension.podspec @@ -1,4 +1,4 @@ -AIRSHIP_VERSION="18.2.0" +AIRSHIP_VERSION="18.2.1" Pod::Spec.new do |s| s.version = AIRSHIP_VERSION diff --git a/AirshipDebug.podspec b/AirshipDebug.podspec index 1388e066b..0f57fb062 100644 --- a/AirshipDebug.podspec +++ b/AirshipDebug.podspec @@ -1,4 +1,4 @@ -AIRSHIP_VERSION="18.2.0" +AIRSHIP_VERSION="18.2.1" Pod::Spec.new do |s| s.version = AIRSHIP_VERSION diff --git a/AirshipServiceExtension.podspec b/AirshipServiceExtension.podspec index 4244a15fe..4d2bd5486 100644 --- a/AirshipServiceExtension.podspec +++ b/AirshipServiceExtension.podspec @@ -1,4 +1,4 @@ -AIRSHIP_VERSION="18.2.0" +AIRSHIP_VERSION="18.2.1" Pod::Spec.new do |s| s.version = AIRSHIP_VERSION diff --git a/CHANGELOG.md b/CHANGELOG.md index 196c2394a..9c95664f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ [Migration Guides](https://github.com/urbanairship/ios-library/tree/main/Documentation/Migration) +## Version 18.2.1, May 14, 2024 +Patch release that makes IAA name property is optional and defaults to an empty string. + +### Changes +- Fixed InAppMessage parsing to handle the optional name property +- Ignore invalid schedules on parsing + ## Version 18.2.0, May 7, 2024 Minor release with updates for in-app message customization, video playback improvements in scenes, web view inspection configuration and several bug fixes. Apps that require obj-c support or are migrating from an older version of the SDK to 18.x should update to this version.