diff --git a/Sources/APNSCore/Alert/APNSAlertNotification.swift b/Sources/APNSCore/Alert/APNSAlertNotification.swift index e302f437..9beafdaa 100644 --- a/Sources/APNSCore/Alert/APNSAlertNotification.swift +++ b/Sources/APNSCore/Alert/APNSAlertNotification.swift @@ -130,6 +130,18 @@ public struct APNSAlertNotification: APNSMessage, } } + /// The content available flag, when set to `1`, the notification will wake up your app in the background and trigger `didReceiveRemoteNotification`. + /// + /// The highest score gets featured in the notification summary + public var contentAvailable: Int? { + get { + self.aps.contentAvailable + } + set { + self.aps.contentAvailable = newValue + } + } + /// A canonical UUID that identifies the notification. If there is an error sending the notification, /// APNs uses this value to identify the notification to your server. The canonical form is 32 lowercase hexadecimal digits, /// displayed in five groups separated by hyphens in the form 8-4-4-4-12. An example UUID is as follows: @@ -176,6 +188,7 @@ public struct APNSAlertNotification: APNSMessage, /// - targetContentID: The identifier of the window brought forward. /// - interruptionLevel: A string that indicates the importance and delivery timing of a notification. /// - relevanceScore: The relevance score, a number between `0` and `1`, that the system uses to sort the notifications from your app. + /// - contentAvailable: The content available flag, when set to `1`, the notification will wake up your app in the background and trigger `didReceiveRemoteNotification`. /// - apnsID: A canonical UUID that identifies the notification. public init( alert: APNSAlertNotificationContent, @@ -191,6 +204,7 @@ public struct APNSAlertNotification: APNSMessage, targetContentID: String? = nil, interruptionLevel: APNSAlertNotificationInterruptionLevel? = nil, relevanceScore: Double? = nil, + contentAvailable: Int? = nil, apnsID: UUID? = nil ) { self.aps = APNSAlertNotificationAPSStorage( @@ -202,7 +216,8 @@ public struct APNSAlertNotification: APNSMessage, mutableContent: mutableContent, targetContentID: targetContentID, interruptionLevel: interruptionLevel, - relevanceScore: relevanceScore + relevanceScore: relevanceScore, + contentAvailable: contentAvailable ) self.apnsID = apnsID self.expiration = expiration @@ -239,6 +254,7 @@ extension APNSAlertNotification where Payload == EmptyPayload { /// - targetContentID: The identifier of the window brought forward. /// - interruptionLevel: A string that indicates the importance and delivery timing of a notification. /// - relevanceScore: The relevance score, a number between `0` and `1`, that the system uses to sort the notifications from your app. + /// - contentAvailable: The content available flag, when set to `1`, the notification will wake up your app in the background and trigger `didReceiveRemoteNotification`. /// - apnsID: A canonical UUID that identifies the notification. public init( alert: APNSAlertNotificationContent, @@ -253,6 +269,7 @@ extension APNSAlertNotification where Payload == EmptyPayload { targetContentID: String? = nil, interruptionLevel: APNSAlertNotificationInterruptionLevel? = nil, relevanceScore: Double? = nil, + contentAvailable: Int? = nil, apnsID: UUID? = nil ) { self.aps = APNSAlertNotificationAPSStorage( @@ -264,7 +281,8 @@ extension APNSAlertNotification where Payload == EmptyPayload { mutableContent: mutableContent, targetContentID: targetContentID, interruptionLevel: interruptionLevel, - relevanceScore: relevanceScore + relevanceScore: relevanceScore, + contentAvailable: contentAvailable ) self.apnsID = apnsID self.expiration = expiration diff --git a/Sources/APNSCore/Alert/APNSAlertNotificationAPSStorage.swift b/Sources/APNSCore/Alert/APNSAlertNotificationAPSStorage.swift index ad84ff90..f43edbe0 100644 --- a/Sources/APNSCore/Alert/APNSAlertNotificationAPSStorage.swift +++ b/Sources/APNSCore/Alert/APNSAlertNotificationAPSStorage.swift @@ -23,6 +23,7 @@ struct APNSAlertNotificationAPSStorage: Encodable, Sendable { case targetContentID = "target-content-id" case interruptionLevel = "interruption-level" case relevanceScore = "relevance-score" + case contentAvailable = "content-available" } var alert: APNSAlertNotificationContent @@ -49,6 +50,8 @@ struct APNSAlertNotificationAPSStorage: Encodable, Sendable { } } + var contentAvailable: Int? + init( alert: APNSAlertNotificationContent, badge: Int? = nil, @@ -58,7 +61,8 @@ struct APNSAlertNotificationAPSStorage: Encodable, Sendable { mutableContent: Double? = nil, targetContentID: String? = nil, interruptionLevel: APNSAlertNotificationInterruptionLevel? = nil, - relevanceScore: Double? = nil + relevanceScore: Double? = nil, + contentAvailable: Int? = nil ) { if let relevanceScore = relevanceScore { precondition(relevanceScore >= 0 && relevanceScore <= 1, "The relevance score can only be between 0 and 1") @@ -72,5 +76,6 @@ struct APNSAlertNotificationAPSStorage: Encodable, Sendable { self.targetContentID = targetContentID self.interruptionLevel = interruptionLevel self.relevanceScore = relevanceScore + self.contentAvailable = contentAvailable } } diff --git a/Tests/APNSTests/Alert/APNSAlertNotificationTests.swift b/Tests/APNSTests/Alert/APNSAlertNotificationTests.swift index 8d345b17..f3ad5365 100644 --- a/Tests/APNSTests/Alert/APNSAlertNotificationTests.swift +++ b/Tests/APNSTests/Alert/APNSAlertNotificationTests.swift @@ -86,13 +86,14 @@ final class APNSAlertNotificationTests: XCTestCase { targetContentID: "targetContentID", interruptionLevel: .critical, relevanceScore: 1, + contentAvailable: 1, apnsID: .init() ) let encoder = JSONEncoder() let data = try encoder.encode(notification) let expectedJSONString = """ - {\"payload\":\"payload\",\"aps\":{\"category\":\"category\",\"relevance-score\":1,\"badge\":1,\"target-content-id\":\"targetContentID\",\"sound\":\"default\",\"interruption-level\":\"critical\",\"alert\":{\"body\":\"body\",\"subtitle-loc-key\":\"subtitle-key\",\"title\":\"title\",\"launch-image\":\"launchimage\",\"subtitle-loc-args\":[\"arg1\"]},\"thread-id\":\"threadID\",\"mutable-content\":1}} + {\"payload\":\"payload\",\"aps\":{\"category\":\"category\",\"relevance-score\":1,\"content-available\":1,\"badge\":1,\"target-content-id\":\"targetContentID\",\"sound\":\"default\",\"interruption-level\":\"critical\",\"alert\":{\"body\":\"body\",\"subtitle-loc-key\":\"subtitle-key\",\"title\":\"title\",\"launch-image\":\"launchimage\",\"subtitle-loc-args\":[\"arg1\"]},\"thread-id\":\"threadID\",\"mutable-content\":1}} """ let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary @@ -125,13 +126,14 @@ final class APNSAlertNotificationTests: XCTestCase { targetContentID: "targetContentID", interruptionLevel: .critical, relevanceScore: 1, + contentAvailable: 1, apnsID: .init() ) let encoder = JSONEncoder() let data = try encoder.encode(notification) let expectedJSONString = """ - {\"payload\":\"payload\",\"aps\":{\"category\":\"category\",\"relevance-score\":1,\"badge\":1,\"target-content-id\":\"targetContentID\",\"sound\":{\"name\":\"file\",\"volume\":1,\"critical\":1},\"interruption-level\":\"critical\",\"alert\":{\"body\":\"body\",\"subtitle-loc-key\":\"subtitle-key\",\"title\":\"title\",\"launch-image\":\"launchimage\",\"subtitle-loc-args\":[\"arg1\"]},\"thread-id\":\"threadID\",\"mutable-content\":1}} + {\"payload\":\"payload\",\"aps\":{\"category\":\"category\",\"relevance-score\":1,\"content-available\":1,\"badge\":1,\"target-content-id\":\"targetContentID\",\"sound\":{\"name\":\"file\",\"volume\":1,\"critical\":1},\"interruption-level\":\"critical\",\"alert\":{\"body\":\"body\",\"subtitle-loc-key\":\"subtitle-key\",\"title\":\"title\",\"launch-image\":\"launchimage\",\"subtitle-loc-args\":[\"arg1\"]},\"thread-id\":\"threadID\",\"mutable-content\":1}} """ let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary