From 1af61a6d0b06c04ffe6965c0ca1e0ce3c281b983 Mon Sep 17 00:00:00 2001 From: mash'al AL-Ma'mari Date: Sun, 3 Sep 2023 01:14:14 +0400 Subject: [PATCH] add Push to Talk (PTT) Support --- Sources/APNSCore/APNSPushType.swift | 9 ++ .../PushToTalk/APNSClient+PushToTalk.swift | 45 +++++++ .../APNSPushToTalkNotification.swift | 124 ++++++++++++++++++ Sources/APNSExample/Program.swift | 20 +++ .../APNSPushToTalkNotificationTests.swift | 30 +++++ 5 files changed, 228 insertions(+) create mode 100644 Sources/APNSCore/PushToTalk/APNSClient+PushToTalk.swift create mode 100644 Sources/APNSCore/PushToTalk/APNSPushToTalkNotification.swift create mode 100644 Tests/APNSTests/PushToTalk/APNSPushToTalkNotificationTests.swift diff --git a/Sources/APNSCore/APNSPushType.swift b/Sources/APNSCore/APNSPushType.swift index 8850453d..e718a6cf 100644 --- a/Sources/APNSCore/APNSPushType.swift +++ b/Sources/APNSCore/APNSPushType.swift @@ -23,6 +23,7 @@ public struct APNSPushType: Hashable, Sendable { case fileprovider case mdm case liveactivity + case pushtotalk } /// The underlying raw value that is send to APNs. @@ -94,4 +95,12 @@ public struct APNSPushType: Hashable, Sendable { /// Use the live activity push type to update your live activity. /// public static let liveactivity = Self(configuration: .liveactivity) + + /// Use the pushtotalk push type for notifications that provide information about an incoming Push to Talk (Ptt). + /// + /// Push to Talk services aren’t available to compatible iPad and iPhone apps running in visionOS. + /// + /// - Important: If you set this push type, the topic must use your app’s bundle ID with `.voip-ptt` appended to the end. + /// + public static let pushtotalk = Self(configuration: .pushtotalk) } diff --git a/Sources/APNSCore/PushToTalk/APNSClient+PushToTalk.swift b/Sources/APNSCore/PushToTalk/APNSClient+PushToTalk.swift new file mode 100644 index 00000000..a225332e --- /dev/null +++ b/Sources/APNSCore/PushToTalk/APNSClient+PushToTalk.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the APNSwift open source project +// +// Copyright (c) 2022 the APNSwift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of APNSwift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + + +extension APNSClientProtocol { + /// Sends a Push To Talk (PTT) notification to APNs. + /// + /// - Parameters: + /// - notification: The notification to send. + /// + /// - deviceToken: The hexadecimal bytes that identify the user’s device. Your app receives the bytes for this device token + /// when registering for remote notifications. + /// + /// + /// - logger: The logger to use for sending this notification. + @discardableResult + @inlinable + public func sendPushToTalkNotification( + _ notification: APNSPushToTalkNotification, + deviceToken: String + ) async throws -> APNSResponse { + let request = APNSRequest( + message: notification, + deviceToken: deviceToken, + pushType: .pushtotalk, + expiration: notification.expiration, + priority: notification.priority, + apnsID: notification.apnsID, + topic: notification.topic, + collapseID: nil + ) + return try await send(request) + } +} diff --git a/Sources/APNSCore/PushToTalk/APNSPushToTalkNotification.swift b/Sources/APNSCore/PushToTalk/APNSPushToTalkNotification.swift new file mode 100644 index 00000000..dea79305 --- /dev/null +++ b/Sources/APNSCore/PushToTalk/APNSPushToTalkNotification.swift @@ -0,0 +1,124 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the APNSwift open source project +// +// Copyright (c) 2022 the APNSwift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of APNSwift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import struct Foundation.UUID + +/// A Push to Talk (PTT) notification. +public struct APNSPushToTalkNotification: APNSMessage { + /// 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: + /// `123e4567-e89b-12d3-a456-42665544000`. + /// + /// If you omit this, a new UUID is created by APNs and returned in the response. + public var apnsID: UUID? + + /// The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.voip-ptt`. + public var topic: String + + /// The date when the notification is no longer valid and can be discarded. If this value is not `none`, + /// APNs stores the notification and tries to deliver it at least once, + /// repeating the attempt as needed if it is unable to deliver the notification the first time. + /// If the value is `immediately`, APNs treats the notification as if it expires immediately + /// and does not store the notification or attempt to redeliver it. + public var expiration: APNSNotificationExpiration + + /// The priority of the notification. + public var priority: APNSPriority + + /// Your custom payload. + public var payload: Payload + + /// Initializes a new ``APNSPushToTalkNotification``. + /// + /// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs. + /// It is **important** that you do not encode anything with the key `aps` + /// + /// - Parameters: + /// - expiration: The date when the notification is no longer valid and can be discarded. Defaults to `.immediately` + /// - priority: The priority of the notification. Defaults to `.immediately` + /// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.voip-ptt`. + /// - payload: Payload contain speaker data like name of active speaker or indication the session has ended. + /// - apnsID: A canonical UUID that identifies the notification. + @inlinable + public init( + expiration: APNSNotificationExpiration = .immediately, + priority: APNSPriority = .immediately, + appID: String, + payload: Payload, + apnsID: UUID? = nil + ) { + self.init( + expiration: expiration, + priority: priority, + topic: appID + ".voip-ptt", + payload: payload, + apnsID: apnsID + ) + } + + /// Initializes a new ``APNSPushToTalkNotification``. + /// + /// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs. + /// It is **important** that you do not encode anything with the key `aps` + /// + /// - Parameters: + /// - expiration: The date when the notification is no longer valid and can be discarded. Defaults to `.immediately` + /// - priority: The priority of the notification. Defaults to `.immediately` + /// - topic: The topic for the notification. In general, the topic is your app’s bundle ID/app ID suffixed with `.voip-ptt`. + /// - payload: Payload contain speaker data like name of active speaker or indication the session has ended. + /// - apnsID: A canonical UUID that identifies the notification. + @inlinable + public init( + expiration: APNSNotificationExpiration = .immediately, + priority: APNSPriority = .immediately, + topic: String, + payload: Payload, + apnsID: UUID? = nil + ) { + self.expiration = expiration + self.priority = priority + self.topic = topic + self.payload = payload + self.apnsID = apnsID + } +} + +extension APNSPushToTalkNotification where Payload == EmptyPayload { + /// Initializes a new ``APNSPushToTalkNotification`` with an EmptyPayload. + /// + /// - Important: Your dynamic payload will get encoded to the root of the JSON payload that is send to APNs. + /// It is **important** that you do not encode anything with the key `aps` + /// + /// - Parameters: + /// - expiration: The date when the notification is no longer valid and can be discarded. Defaults to `.immediately` + /// - priority: The priority of the notification. Defaults to `.immediately` + /// - appID: Your app’s bundle ID/app ID. This will be suffixed with `.voip-ptt`. + /// - payload: Empty Payload. + /// - apnsID: A canonical UUID that identifies the notification. + public init( + expiration: APNSNotificationExpiration = .immediately, + priority: APNSPriority = .immediately, + appID: String, + apnsID: UUID? = nil + ) { + self.init( + expiration: expiration, + priority: priority, + topic: appID + ".voip-ptt", + payload: EmptyPayload(), + apnsID: apnsID + ) + } +} diff --git a/Sources/APNSExample/Program.swift b/Sources/APNSExample/Program.swift index 3ec6afdd..3b553e2a 100644 --- a/Sources/APNSExample/Program.swift +++ b/Sources/APNSExample/Program.swift @@ -22,6 +22,7 @@ struct Main { /// To use this example app please provide proper values for variable below. static let deviceToken = "" static let pushKitDeviceToken = "" + static let ephemeralPushToken = "" // PTT static let fileProviderDeviceToken = "" static let appBundleID = "" static let privateKey = """ @@ -52,6 +53,7 @@ struct Main { try await Self.sendBackground(with: client) try await Self.sendVoIP(with: client) try await Self.sendFileProvider(with: client) + try await Self.sendPushToTalk(with: client) } } @@ -201,3 +203,21 @@ extension Main { ) } } + + +// MARK: Push to Talk (PTT) + +@available(macOS 11.0, *) +extension Main { + static func sendPushToTalk(with client: some APNSClientProtocol) async throws { + try await client.sendPushToTalkNotification( + .init( + expiration: .immediately, + priority: .immediately, + appID: self.appBundleID, + payload: EmptyPayload() + ), + deviceToken: self.ephemeralPushToken + ) + } +} diff --git a/Tests/APNSTests/PushToTalk/APNSPushToTalkNotificationTests.swift b/Tests/APNSTests/PushToTalk/APNSPushToTalkNotificationTests.swift new file mode 100644 index 00000000..c3ab13d4 --- /dev/null +++ b/Tests/APNSTests/PushToTalk/APNSPushToTalkNotificationTests.swift @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the APNSwift open source project +// +// Copyright (c) 2022 the APNSwift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of APNSwift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import APNSCore +import XCTest + +final class APNSPushToTalkNotificationTests: XCTestCase { + func testAppID() { + struct Payload: Encodable { + let foo = "bar" + } + let voipNotification = APNSPushToTalkNotification( + appID: "com.example.app", + payload: Payload() + ) + + XCTAssertEqual(voipNotification.topic, "com.example.app.voip-ptt") + } +}