Skip to content

Commit fe9a00c

Browse files
Push to Talk (PTT) Support (#181)
add Push to Talk (PTT) Support Co-authored-by: Kyle Browning <[email protected]>
1 parent 4aee81d commit fe9a00c

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

Sources/APNSCore/APNSPushType.swift

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public struct APNSPushType: Hashable, Sendable, CustomStringConvertible {
2424
case fileprovider
2525
case mdm
2626
case liveactivity
27+
case pushtotalk
2728
}
2829

2930
public var description: String {
@@ -99,4 +100,12 @@ public struct APNSPushType: Hashable, Sendable, CustomStringConvertible {
99100
/// Use the live activity push type to update your live activity.
100101
///
101102
public static let liveactivity = Self(configuration: .liveactivity)
103+
104+
/// Use the pushtotalk push type for notifications that provide information about an incoming Push to Talk (Ptt).
105+
///
106+
/// Push to Talk services aren’t available to compatible iPad and iPhone apps running in visionOS.
107+
///
108+
/// - Important: If you set this push type, the topic must use your app’s bundle ID with `.voip-ptt` appended to the end.
109+
///
110+
public static let pushtotalk = Self(configuration: .pushtotalk)
102111
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
16+
extension APNSClientProtocol {
17+
/// Sends a Push To Talk (PTT) notification to APNs.
18+
///
19+
/// - Parameters:
20+
/// - notification: The notification to send.
21+
///
22+
/// - deviceToken: The hexadecimal bytes that identify the user’s device. Your app receives the bytes for this device token
23+
/// when registering for remote notifications.
24+
///
25+
///
26+
/// - logger: The logger to use for sending this notification.
27+
@discardableResult
28+
@inlinable
29+
public func sendPushToTalkNotification<Payload: Encodable & Sendable>(
30+
_ notification: APNSPushToTalkNotification<Payload>,
31+
deviceToken: String
32+
) async throws -> APNSResponse {
33+
let request = APNSRequest(
34+
message: notification,
35+
deviceToken: deviceToken,
36+
pushType: .pushtotalk,
37+
expiration: notification.expiration,
38+
priority: notification.priority,
39+
apnsID: notification.apnsID,
40+
topic: notification.topic,
41+
collapseID: nil
42+
)
43+
return try await send(request)
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 Push to Talk (PTT) notification.
18+
public struct APNSPushToTalkNotification<Payload: Encodable & Sendable>: APNSMessage {
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-ptt`.
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 ``APNSPushToTalkNotification``.
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. Defaults to `.immediately`
51+
/// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.voip-ptt`.
52+
/// - payload: Payload contain speaker data like name of active speaker or indication the session has ended.
53+
/// - apnsID: A canonical UUID that identifies the notification.
54+
@inlinable
55+
public init(
56+
expiration: APNSNotificationExpiration = .immediately,
57+
priority: APNSPriority = .immediately,
58+
appID: String,
59+
payload: Payload,
60+
apnsID: UUID? = nil
61+
) {
62+
self.init(
63+
expiration: expiration,
64+
priority: priority,
65+
topic: appID + ".voip-ptt",
66+
payload: payload,
67+
apnsID: apnsID
68+
)
69+
}
70+
71+
/// Initializes a new ``APNSPushToTalkNotification``.
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. Defaults to `.immediately`
79+
/// - topic: The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.voip-ptt`.
80+
/// - payload: Payload contain speaker data like name of active speaker or indication the session has ended.
81+
/// - apnsID: A canonical UUID that identifies the notification.
82+
@inlinable
83+
public init(
84+
expiration: APNSNotificationExpiration = .immediately,
85+
priority: APNSPriority = .immediately,
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+
98+
extension APNSPushToTalkNotification where Payload == EmptyPayload {
99+
/// Initializes a new ``APNSPushToTalkNotification`` with an EmptyPayload.
100+
///
101+
/// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs.
102+
/// It is **important** that you do not encode anything with the key `aps`
103+
///
104+
/// - Parameters:
105+
/// - expiration: The date when the notification is no longer valid and can be discarded. Defaults to `.immediately`
106+
/// - priority: The priority of the notification. Defaults to `.immediately`
107+
/// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.voip-ptt`.
108+
/// - payload: Empty Payload.
109+
/// - apnsID: A canonical UUID that identifies the notification.
110+
public init(
111+
expiration: APNSNotificationExpiration = .immediately,
112+
priority: APNSPriority = .immediately,
113+
appID: String,
114+
apnsID: UUID? = nil
115+
) {
116+
self.init(
117+
expiration: expiration,
118+
priority: priority,
119+
topic: appID + ".voip-ptt",
120+
payload: EmptyPayload(),
121+
apnsID: apnsID
122+
)
123+
}
124+
}

Sources/APNSExample/Program.swift

+20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct Main {
2222
/// To use this example app please provide proper values for variable below.
2323
static let deviceToken = ""
2424
static let pushKitDeviceToken = ""
25+
static let ephemeralPushToken = "" // PTT
2526
static let fileProviderDeviceToken = ""
2627
static let appBundleID = ""
2728
static let privateKey = """
@@ -52,6 +53,7 @@ struct Main {
5253
try await Self.sendBackground(with: client)
5354
try await Self.sendVoIP(with: client)
5455
try await Self.sendFileProvider(with: client)
56+
try await Self.sendPushToTalk(with: client)
5557
}
5658
}
5759

@@ -201,3 +203,21 @@ extension Main {
201203
)
202204
}
203205
}
206+
207+
208+
// MARK: Push to Talk (PTT)
209+
210+
@available(macOS 11.0, *)
211+
extension Main {
212+
static func sendPushToTalk(with client: some APNSClientProtocol) async throws {
213+
try await client.sendPushToTalkNotification(
214+
.init(
215+
expiration: .immediately,
216+
priority: .immediately,
217+
appID: self.appBundleID,
218+
payload: EmptyPayload()
219+
),
220+
deviceToken: self.ephemeralPushToken
221+
)
222+
}
223+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 APNSCore
16+
import XCTest
17+
18+
final class APNSPushToTalkNotificationTests: XCTestCase {
19+
func testAppID() {
20+
struct Payload: Encodable {
21+
let foo = "bar"
22+
}
23+
let voipNotification = APNSPushToTalkNotification(
24+
appID: "com.example.app",
25+
payload: Payload()
26+
)
27+
28+
XCTAssertEqual(voipNotification.topic, "com.example.app.voip-ptt")
29+
}
30+
}

0 commit comments

Comments
 (0)