Skip to content

Commit 063c16e

Browse files
Add support for dismissal-date to live activity notification (#167)
1 parent 98acef3 commit 063c16e

File tree

5 files changed

+175
-11
lines changed

5 files changed

+175
-11
lines changed

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ try await client.sendAlertNotification(
9797
)
9898
```
9999

100-
## Sending Live Activity Update
100+
## Sending Live Activity Update / End
101101
It requires sending `ContentState` matching with the live activity configuration to successfully update activity state. `ContentState` needs to conform to `Encodable`
102102

103103
```swift
@@ -115,6 +115,21 @@ It requires sending `ContentState` matching with the live activity configuration
115115
)
116116
```
117117

118+
```swift
119+
try await client.sendLiveActivityNotification(
120+
.init(
121+
expiration: .immediately,
122+
priority: .immediately,
123+
appID: "com.app.bundle",
124+
contentState: ContentState,
125+
event: .end,
126+
timestamp: Int(Date().timeIntervalSince1970),
127+
dismissalDate: .dismissImmediately // Optional to alter default behaviour
128+
),
129+
activityPushToken: activityPushToken,
130+
deadline: .distantFuture
131+
)
132+
```
118133
## Authentication
119134
`APNSwift` provides two authentication methods. `jwt`, and `TLS`.
120135

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2022 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import struct Foundation.Date
16+
17+
public struct APNSLiveActivityDismissalDate: Hashable {
18+
/// The date at which the live activity will be dismissed
19+
/// This value is a UNIX epoch expressed in seconds (UTC)
20+
@usableFromInline
21+
let dismissal: Int?
22+
23+
/// Omits sending an dismissal date for APNs. APNs will default to a default value for dismissal time.
24+
public static let none = Self(dismissal: nil)
25+
26+
/// Have live activity dismiss immediately when end received
27+
public static let immediately = Self(dismissal: 0)
28+
29+
/// Specify dismissal as a unix time stamp, if in past will dismiss
30+
/// immedidately.
31+
public static func timeIntervalSince1970InSeconds(_ timeInterval: Int) -> Self {
32+
Self(dismissal: timeInterval)
33+
}
34+
35+
/// Specify dismissal as a date, if in past will dismiss
36+
/// immedidately.
37+
public static func date(_ date: Date) -> Self {
38+
Self(dismissal: Int(date.timeIntervalSince1970))
39+
}
40+
}

Sources/APNSwift/LiveActivity/APNSLiveActivityNotification.swift

+24-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import struct Foundation.UUID
1616

17-
/// An alert notification.
17+
/// A live activity notification.
1818
///
1919
/// It is **important** that you do not encode anything with the key `aps`.
2020
public struct APNSLiveActivityNotification<ContentState: Encodable>: Encodable {
@@ -58,6 +58,15 @@ public struct APNSLiveActivityNotification<ContentState: Encodable>: Encodable {
5858
}
5959
}
6060

61+
public var dismissalDate: Int? {
62+
get {
63+
return self.aps.dismissalDate
64+
}
65+
set {
66+
self.aps.dismissalDate = newValue
67+
}
68+
}
69+
6170
/// A canonical UUID that identifies the notification. If there is an error sending the notification,
6271
/// APNs uses this value to identify the notification to your server. The canonical form is 32 lowercase hexadecimal digits,
6372
/// displayed in five groups separated by hyphens in the form 8-4-4-4-12. An example UUID is as follows:
@@ -90,15 +99,18 @@ public struct APNSLiveActivityNotification<ContentState: Encodable>: Encodable {
9099
/// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.push-type.liveactivity`.
91100
/// - apnsID: A canonical UUID that identifies the notification.
92101
/// - contentState: Updated content-state of live activity
93-
/// - timestamp: Timestamp when sending notification
94102
/// - event: event type e.g. update
103+
/// - timestamp: Timestamp when sending notification
104+
/// - dismissalDate: Timestamp when to dismiss live notification when sent with `end`, if in the past
105+
/// dismiss immediately
95106
public init(
96107
expiration: APNSNotificationExpiration,
97108
priority: APNSPriority,
98109
appID: String,
99110
contentState: ContentState,
100111
event: APNSLiveActivityNotificationEvent,
101112
timestamp: Int,
113+
dismissalDate: APNSLiveActivityDismissalDate = .none,
102114
apnsID: UUID? = nil
103115
) {
104116
self.init(
@@ -107,11 +119,12 @@ public struct APNSLiveActivityNotification<ContentState: Encodable>: Encodable {
107119
topic: appID + ".push-type.liveactivity",
108120
contentState: contentState,
109121
event: event,
110-
timestamp: timestamp
122+
timestamp: timestamp,
123+
dismissalDate: dismissalDate
111124
)
112125
}
113126

114-
127+
115128
/// Initializes a new ``APNSLiveActivityNotification``.
116129
///
117130
/// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs.
@@ -123,21 +136,25 @@ public struct APNSLiveActivityNotification<ContentState: Encodable>: Encodable {
123136
/// - topic: The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.push-type.liveactivity`.
124137
/// - apnsID: A canonical UUID that identifies the notification.
125138
/// - contentState: Updated content-state of live activity
126-
/// - timestamp: Timestamp when sending notification
127139
/// - event: event type e.g. update
140+
/// - timestamp: Timestamp when sending notification
141+
/// - dismissalDate: Timestamp when to dismiss live notification when sent with `end`, if in the past
142+
/// dismiss immediately
128143
public init(
129144
expiration: APNSNotificationExpiration,
130145
priority: APNSPriority,
131146
topic: String,
132147
apnsID: UUID? = nil,
133148
contentState: ContentState,
134149
event: APNSLiveActivityNotificationEvent,
135-
timestamp: Int
150+
timestamp: Int,
151+
dismissalDate: APNSLiveActivityDismissalDate = .none
136152
) {
137153
self.aps = APNSLiveActivityNotificationAPSStorage(
138154
timestamp: timestamp,
139155
event: event.rawValue,
140-
contentState: contentState
156+
contentState: contentState,
157+
dismissalDate: dismissalDate.dismissal
141158
)
142159
self.apnsID = apnsID
143160
self.expiration = expiration

Sources/APNSwift/LiveActivity/APNSLiveActivityNotificationAPSStorage.swift

+6-3
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,23 @@ struct APNSLiveActivityNotificationAPSStorage<ContentState: Encodable>: Encodabl
1717
case timestamp = "timestamp"
1818
case event = "event"
1919
case contentState = "content-state"
20-
20+
case dismissalDate = "dismissal-date"
2121
}
2222

2323
var timestamp: Int
2424
var event: String
2525
var contentState: ContentState
26-
26+
var dismissalDate: Int?
27+
2728
init(
2829
timestamp: Int,
2930
event: String,
30-
contentState: ContentState
31+
contentState: ContentState,
32+
dismissalDate: Int?
3133
) {
3234
self.timestamp = timestamp
3335
self.event = event
3436
self.contentState = contentState
37+
self.dismissalDate = dismissalDate
3538
}
3639
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2022 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import APNSwift
16+
import XCTest
17+
18+
final class APNSLiveActivityNotificationTests: XCTestCase {
19+
20+
struct State: Encodable, Hashable {
21+
let string: String = "Test"
22+
let number: Int = 123
23+
}
24+
25+
func testEncodeUpdate() throws {
26+
let notification = APNSLiveActivityNotification(
27+
expiration: .immediately,
28+
priority: .immediately,
29+
appID: "test.app.id",
30+
contentState: State(),
31+
event: .update,
32+
timestamp: 1672680658)
33+
34+
let encoder = JSONEncoder()
35+
let data = try encoder.encode(notification)
36+
37+
let expectedJSONString = """
38+
{"aps":{"event":"update","content-state":{"string":"Test","number":123},"timestamp":1672680658}}
39+
"""
40+
41+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
42+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
43+
XCTAssertEqual(jsonObject1, jsonObject2)
44+
}
45+
46+
func testEncodeEndNoDismiss() throws {
47+
let notification = APNSLiveActivityNotification(
48+
expiration: .immediately,
49+
priority: .immediately,
50+
appID: "test.app.id",
51+
contentState: State(),
52+
event: .end,
53+
timestamp: 1672680658)
54+
55+
let encoder = JSONEncoder()
56+
let data = try encoder.encode(notification)
57+
58+
let expectedJSONString = """
59+
{"aps":{"event":"end","content-state":{"string":"Test","number":123},"timestamp":1672680658}}
60+
"""
61+
62+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
63+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
64+
XCTAssertEqual(jsonObject1, jsonObject2)
65+
}
66+
67+
func testEncodeEndDismiss() throws {
68+
let notification = APNSLiveActivityNotification(
69+
expiration: .immediately,
70+
priority: .immediately,
71+
appID: "test.app.id",
72+
contentState: State(),
73+
event: .end,
74+
timestamp: 1672680658,
75+
dismissalDate: .timeIntervalSince1970InSeconds(1672680800))
76+
77+
let encoder = JSONEncoder()
78+
let data = try encoder.encode(notification)
79+
80+
let expectedJSONString = """
81+
{"aps":{"event":"end","content-state":{"string":"Test","number":123},"timestamp":1672680658,
82+
"dismissal-date":1672680800}}
83+
"""
84+
85+
let jsonObject1 = try JSONSerialization.jsonObject(with: data) as! NSDictionary
86+
let jsonObject2 = try JSONSerialization.jsonObject(with: expectedJSONString.data(using: .utf8)!) as! NSDictionary
87+
XCTAssertEqual(jsonObject1, jsonObject2)
88+
}
89+
}

0 commit comments

Comments
 (0)