Skip to content

Commit 0418312

Browse files
authored
Add semantic safe API for VoIP notifications (#148)
# Motivation We want to provide new APIs that are semantically safe when sending VoIP notifications to APNs. # Modification The PR adds new types for the VoIP notification and a convenience method to send it notifications with the APNSClient. # Result We can now send VoIP notifications.
1 parent 0a00eda commit 0418312

File tree

4 files changed

+216
-0
lines changed

4 files changed

+216
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 NIOCore
16+
import Logging
17+
18+
extension APNSClient {
19+
/// Sends a VoIP notification to APNs.
20+
///
21+
/// - Parameters:
22+
/// - notification: The notification to send.
23+
///
24+
/// - deviceToken: The hexadecimal bytes that identify the user’s device. Your app receives the bytes for this device token
25+
/// when registering for remote notifications.
26+
///
27+
/// - deadline: Point in time by which sending the notification to APNs must complete.
28+
///
29+
/// - logger: The logger to use for sending this notification.
30+
@discardableResult
31+
@inlinable
32+
public func sendVoIPNotification<Payload: Encodable>(
33+
_ notification: APNSVoIPNotification<Payload>,
34+
deviceToken: String,
35+
deadline: NIODeadline,
36+
logger: Logger = _noOpLogger
37+
) async throws -> APNSResponse {
38+
try await self.send(
39+
payload: notification.payload,
40+
deviceToken: deviceToken,
41+
pushType: .voip,
42+
expiration: notification.expiration,
43+
priority: notification.priority,
44+
topic: notification.topic,
45+
deadline: deadline,
46+
logger: logger
47+
)
48+
}
49+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.UUID
16+
17+
/// A voice-over-IP notification.
18+
public struct APNSVoIPNotification<Payload: Encodable> {
19+
/// A canonical UUID that identifies the notification. If there is an error sending the notification,
20+
/// APNs uses this value to identify the notification to your server. The canonical form is 32 lowercase hexadecimal digits,
21+
/// displayed in five groups separated by hyphens in the form 8-4-4-4-12. An example UUID is as follows:
22+
/// `123e4567-e89b-12d3-a456-42665544000`.
23+
///
24+
/// If you omit this, a new UUID is created by APNs and returned in the response.
25+
public var apnsID: UUID?
26+
27+
/// The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.voip`.
28+
public var topic: String
29+
30+
/// The date when the notification is no longer valid and can be discarded. If this value is not `none`,
31+
/// APNs stores the notification and tries to deliver it at least once,
32+
/// repeating the attempt as needed if it is unable to deliver the notification the first time.
33+
/// If the value is `immediately`, APNs treats the notification as if it expires immediately
34+
/// and does not store the notification or attempt to redeliver it.
35+
public var expiration: APNSNotificationExpiration
36+
37+
/// The priority of the notification.
38+
public var priority: APNSPriority
39+
40+
/// Your custom payload.
41+
public var payload: Payload
42+
43+
/// Initializes a new ``APNSVoIPNotification``.
44+
///
45+
/// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs.
46+
/// It is **important** that you do not encode anything with the key `aps`
47+
///
48+
/// - Parameters:
49+
/// - expiration: The date when the notification is no longer valid and can be discarded. Defaults to `.immediately`
50+
/// - priority: The priority of the notification.
51+
/// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.voip`.
52+
/// - payload: Your custom payload.
53+
/// - apnsID: A canonical UUID that identifies the notification.
54+
@inlinable
55+
public init(
56+
expiration: APNSNotificationExpiration = .immediately,
57+
priority: APNSPriority,
58+
appID: String,
59+
payload: Payload,
60+
apnsID: UUID? = nil
61+
) {
62+
self.init(
63+
expiration: expiration,
64+
priority: priority,
65+
topic: appID + ".voip",
66+
payload: payload,
67+
apnsID: apnsID
68+
)
69+
}
70+
71+
/// Initializes a new ``APNSVoIPNotification``.
72+
///
73+
/// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs.
74+
/// It is **important** that you do not encode anything with the key `aps`
75+
///
76+
/// - Parameters:
77+
/// - expiration: The date when the notification is no longer valid and can be discarded. Defaults to `.immediately`
78+
/// - priority: The priority of the notification.
79+
/// - topic: The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.voip`.
80+
/// - payload: Your custom payload.
81+
/// - apnsID: A canonical UUID that identifies the notification.
82+
@inlinable
83+
public init(
84+
expiration: APNSNotificationExpiration = .immediately,
85+
priority: APNSPriority,
86+
topic: String,
87+
payload: Payload,
88+
apnsID: UUID? = nil
89+
) {
90+
self.expiration = expiration
91+
self.priority = priority
92+
self.topic = topic
93+
self.payload = payload
94+
self.apnsID = apnsID
95+
}
96+
}
97+

Sources/APNSwiftExample/Program.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ struct Main {
6666
try await Self.sendThreadedAlert(with: client)
6767
try await Self.sendCustomCategoryAlert(with: client)
6868
try await Self.sendMutableContentAlert(with: client)
69+
try await Self.sendBackground(with: client)
70+
try await Self.sendVoIP(with: client)
6971
} catch {
7072
self.logger.error("Failed sending push", metadata: ["error": "\(error)"])
7173
}
@@ -179,3 +181,40 @@ extension Main {
179181
)
180182
}
181183
}
184+
185+
// MARK: Background
186+
187+
@available(macOS 11.0, *)
188+
extension Main {
189+
static func sendBackground(with client: APNSClient<JSONDecoder, JSONEncoder>) async throws {
190+
try await client.sendBackgroundNotification(
191+
.init(
192+
expiration: .immediately,
193+
topic: self.appBundleID,
194+
payload: Payload()
195+
),
196+
deviceToken: self.deviceToken,
197+
deadline: .distantFuture,
198+
logger: self.logger
199+
)
200+
}
201+
}
202+
203+
// MARK: VoIP
204+
205+
@available(macOS 11.0, *)
206+
extension Main {
207+
static func sendVoIP(with client: APNSClient<JSONDecoder, JSONEncoder>) async throws {
208+
try await client.sendVoIPNotification(
209+
.init(
210+
expiration: .immediately,
211+
priority: .immediately,
212+
appID: self.appBundleID,
213+
payload: Payload()
214+
),
215+
deviceToken: self.pushKitDeviceToken,
216+
deadline: .distantFuture,
217+
logger: self.logger
218+
)
219+
}
220+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 APNSVoIPNotificationTests: XCTestCase {
19+
func testAppID() {
20+
struct Payload: Encodable {
21+
let foo = "bar"
22+
}
23+
let voipNotification = APNSVoIPNotification(
24+
priority: .immediately,
25+
appID: "com.example.app",
26+
payload: Payload()
27+
)
28+
29+
XCTAssertEqual(voipNotification.topic, "com.example.app.voip")
30+
}
31+
}

0 commit comments

Comments
 (0)