From 8a5fe1ab7c469f94727133f1143dde74a3493beb Mon Sep 17 00:00:00 2001
From: Corey Baker <coreyearleon@icloud.com>
Date: Wed, 2 Oct 2024 16:31:53 -0700
Subject: [PATCH 1/3] fix: Apple push notificaiton not working for live
 activities

---
 ParseSwift.xcodeproj/project.pbxproj          |  16 ++
 .../Protocols/ParsePushApplePayload.swift     | 109 ++++++++++++++
 .../Protocols/ParsePushApplePayloadable.swift |  40 +++++
 Sources/ParseSwift/Types/ParsePush.swift      |  21 ++-
 .../Types/ParsePushNotificationBody.swift     |  51 +++++++
 .../Apple/ParsePushAppleAlert.swift           |   1 +
 .../Apple/ParsePushAppleNotification.swift    |  33 ++++
 .../Apple/ParsePushPayloadApple.swift         | 141 ++----------------
 .../ParsePushPayloadAppleLiveActivity.swift   |  92 ++++++++++++
 .../ParsePushPayloadAppleTests.swift          |  43 ++++--
 10 files changed, 394 insertions(+), 153 deletions(-)
 create mode 100644 Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
 create mode 100644 Sources/ParseSwift/Types/ParsePushNotificationBody.swift
 create mode 100644 Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift
 create mode 100644 Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift

diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj
index 4ba8a3933..652b66084 100644
--- a/ParseSwift.xcodeproj/project.pbxproj
+++ b/ParseSwift.xcodeproj/project.pbxproj
@@ -202,6 +202,10 @@
 		70D41D6728B0235100613510 /* MigrateObjCSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */; };
 		70D41D6B28B294C100613510 /* MigrateObjCSDKCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */; };
 		70D41D8028B520E200613510 /* ParseKeychainAccessGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */; };
+		70DDD0752C99079500C92D34 /* ParsePushPayloadAppleLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DDD0742C99077D00C92D34 /* ParsePushPayloadAppleLiveActivity.swift */; };
+		70DDD0772C990F6C00C92D34 /* ParsePushApplePayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DDD0762C990F5E00C92D34 /* ParsePushApplePayload.swift */; };
+		70DDD0792C99535F00C92D34 /* ParsePushAppleNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DDD0782C99535200C92D34 /* ParsePushAppleNotification.swift */; };
+		70DDD07B2C99F85D00C92D34 /* ParsePushNotificationBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DDD07A2C99F85600C92D34 /* ParsePushNotificationBody.swift */; };
 		70DFEA8A2618E77800F8EB4B /* InitializeSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */; };
 		70E09E1C262F0634002DD451 /* ParsePointerCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70E09E1B262F0634002DD451 /* ParsePointerCombineTests.swift */; };
 		70E6B016286120E00043EC4A /* ParseHookFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70E6B015286120E00043EC4A /* ParseHookFunctionTests.swift */; };
@@ -552,6 +556,10 @@
 		70D41D6628B0235100613510 /* MigrateObjCSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateObjCSDKTests.swift; sourceTree = "<group>"; };
 		70D41D6A28B294C100613510 /* MigrateObjCSDKCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateObjCSDKCombineTests.swift; sourceTree = "<group>"; };
 		70D41D7F28B520E200613510 /* ParseKeychainAccessGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseKeychainAccessGroup.swift; sourceTree = "<group>"; };
+		70DDD0742C99077D00C92D34 /* ParsePushPayloadAppleLiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePushPayloadAppleLiveActivity.swift; sourceTree = "<group>"; };
+		70DDD0762C990F5E00C92D34 /* ParsePushApplePayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePushApplePayload.swift; sourceTree = "<group>"; };
+		70DDD0782C99535200C92D34 /* ParsePushAppleNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePushAppleNotification.swift; sourceTree = "<group>"; };
+		70DDD07A2C99F85600C92D34 /* ParsePushNotificationBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePushNotificationBody.swift; sourceTree = "<group>"; };
 		70DFEA892618E77800F8EB4B /* InitializeSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializeSDKTests.swift; sourceTree = "<group>"; };
 		70E09E1B262F0634002DD451 /* ParsePointerCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsePointerCombineTests.swift; sourceTree = "<group>"; };
 		70E6B015286120E00043EC4A /* ParseHookFunctionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseHookFunctionTests.swift; sourceTree = "<group>"; };
@@ -949,6 +957,7 @@
 				705025EA285153BC008D6624 /* ParsePushApplePayloadable.swift */,
 				705025EF2851542D008D6624 /* ParsePushFirebasePayloadable.swift */,
 				705025CB284CE4C2008D6624 /* ParsePushPayloadable.swift */,
+				70DDD0762C990F5E00C92D34 /* ParsePushApplePayload.swift */,
 				70A98D812794AB3C009B58F2 /* ParseQueryScorable.swift */,
 				919823642B3A134000E9591A /* ParsePointerable.swift */,
 				700A8A652B4CC1E40087ADBE /* ParsePointerable+async.swift */,
@@ -1045,8 +1054,10 @@
 			isa = PBXGroup;
 			children = (
 				705025D0284CFCDE008D6624 /* ParsePushAppleAlert.swift */,
+				70DDD0782C99535200C92D34 /* ParsePushAppleNotification.swift */,
 				705025DA284D0D56008D6624 /* ParsePushAppleSound.swift */,
 				705025D5284D0C1D008D6624 /* ParsePushPayloadApple.swift */,
+				70DDD0742C99077D00C92D34 /* ParsePushPayloadAppleLiveActivity.swift */,
 			);
 			path = Apple;
 			sourceTree = "<group>";
@@ -1254,6 +1265,7 @@
 				7044C19E25C4FA870011F6E7 /* ParseOperation+combine.swift */,
 				91285B1B26990D7F0051B544 /* ParsePolygon.swift */,
 				705025BC284C610C008D6624 /* ParsePush.swift */,
+				70DDD07A2C99F85600C92D34 /* ParsePushNotificationBody.swift */,
 				705025C1284C7841008D6624 /* ParsePush+async.swift */,
 				705025C6284C7883008D6624 /* ParsePush+combine.swift */,
 				705025B22845C302008D6624 /* ParsePushStatus.swift */,
@@ -1534,6 +1546,7 @@
 				F97B465F24D9C7B500F4A88B /* KeychainStore.swift in Sources */,
 				70B4E0C12762F313004C9757 /* QueryWhere.swift in Sources */,
 				70170A442656B02D0070C905 /* ParseAnalytics.swift in Sources */,
+				70DDD0792C99535F00C92D34 /* ParsePushAppleNotification.swift in Sources */,
 				70110D52250680140091CC1D /* ParseConstants.swift in Sources */,
 				91B79AC326EE3A4E00073F2C /* API+NonParseBodyCommand.swift in Sources */,
 				708EF0BD28D5F4140052EF35 /* API+Command+async.swift in Sources */,
@@ -1567,6 +1580,7 @@
 				704E781C28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */,
 				7045769826BD917500F86F71 /* Query+async.swift in Sources */,
 				703B094E26BF47E3005A112F /* ParseTwitter+combine.swift in Sources */,
+				70DDD0752C99079500C92D34 /* ParsePushPayloadAppleLiveActivity.swift in Sources */,
 				70386A3825D998D90048EC1B /* ParseLDAP.swift in Sources */,
 				709A14A02839CABD00BF85E5 /* ParseCLP.swift in Sources */,
 				700A8A662B4CC1E40087ADBE /* ParsePointerable+async.swift in Sources */,
@@ -1642,6 +1656,7 @@
 				70C5509225B4A99100B5DBC2 /* ParseOperationAddRelation.swift in Sources */,
 				708D035225215F9B00646C70 /* Deletable.swift in Sources */,
 				F97B466424D9C88600F4A88B /* SecureStorable.swift in Sources */,
+				70DDD07B2C99F85D00C92D34 /* ParsePushNotificationBody.swift in Sources */,
 				7030E08B29BBBF790021970D /* ParseConfigCodable+async.swift in Sources */,
 				7004C22025B63C7A005E0AD9 /* ParseRelation.swift in Sources */,
 				7003959525A10DFC0052CB31 /* Messages.swift in Sources */,
@@ -1665,6 +1680,7 @@
 				700395D125A147BE0052CB31 /* QuerySubscribable.swift in Sources */,
 				70170A492656E2FE0070C905 /* ParseAnalytics+combine.swift in Sources */,
 				703B092B26BF290B005A112F /* ParseAuthentication+async.swift in Sources */,
+				70DDD0772C990F6C00C92D34 /* ParsePushApplePayload.swift in Sources */,
 				70CE0AB7285A83B100DAEA86 /* ParseHookable.swift in Sources */,
 				F97B45F624D9C6F200F4A88B /* ParseError.swift in Sources */,
 				7045769D26BD934000F86F71 /* ParseFile+async.swift in Sources */,
diff --git a/Sources/ParseSwift/Protocols/ParsePushApplePayload.swift b/Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
new file mode 100644
index 000000000..fcd154125
--- /dev/null
+++ b/Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
@@ -0,0 +1,109 @@
+//
+//  ParsePushApplePayload.swift
+//  ParseSwift
+//
+//  Created by Corey Baker on 9/16/24.
+//  Copyright © 2024 Network Reconnaissance Lab. All rights reserved.
+//
+
+// swiftlint:disable line_length
+
+protocol ParsePushApplePayload: ParsePushApplePayloadable {
+    /**
+     The background notification flag. If you are a writing an app using the Remote Notification
+     Background Mode introduced in iOS7 (a.k.a. “Background Push”), set this value to
+     1 to trigger a background update. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app).
+     - warning: For Apple OS's only. You also have to set `pushType` starting iOS 13
+     and watchOS 6.
+     */
+    var contentAvailable: Int? { get set }
+    /**
+     The notification service app extension flag. Set this value to 1 to trigger the system to pass the notification to your notification service app extension before delivery. Use your extension to modify the notification’s content. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications).
+     - warning: You also have to set `pushType` starting iOS 13
+     and watchOS 6.
+     */
+    var mutableContent: Int? { get set }
+    /**
+     The priority of the notification. Specify 10 to send the notification immediately.
+     Specify 5 to send the notification based on power considerations on the user’s device.
+     See Apple's [documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns)
+     for more information.
+     - warning: For Apple OS's only.
+     */
+    var priority: Int? { get set }
+
+    var pushType: ParsePushPayloadApple.PushType? { get set }
+
+    var badge: AnyCodable? { get set }
+    var sound: AnyCodable? { get set }
+}
+
+extension ParsePushApplePayload {
+
+    /**
+     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
+     of your app’s container directory. For information about how to prepare sounds, see
+     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
+     - parameter sound: An instance of `ParsePushAppleSound`.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     - warning: For Apple OS's only.
+     */
+    public func setSound(_ sound: ParsePushAppleSound) -> Self {
+        var mutablePayload = self
+        mutablePayload.sound = AnyCodable(sound)
+        return mutablePayload
+    }
+
+    /**
+     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
+     of your app’s container directory. Specify the string “default” to play the system
+     sound. Pass a string for **regular** notifications. For critical alerts, pass the sound
+     `ParsePushAppleSound` instead. For information about how to prepare sounds, see
+     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
+     - parameter sound: A `String` or any `Codable` object that can be sent to APN.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     - warning: For Apple OS's only.
+     */
+    public func setSound<V>(_ sound: V) -> Self where V: Codable {
+        var mutablePayload = self
+        mutablePayload.sound = AnyCodable(sound)
+        return mutablePayload
+    }
+
+    /**
+     Get the sound using any type that conforms to `Codable`.
+     - returns: The sound casted to the inferred type.
+     - throws: An error of type `ParseError`.
+     */
+    public func getSound<V>() throws -> V where V: Codable {
+        guard let sound = sound?.value as? V else {
+            throw ParseError(code: .otherCause,
+                             message: "Cannot be casted to the inferred type")
+        }
+        return sound
+    }
+
+    /**
+     Set the badge to a specific value to display on your app's icon.
+     - parameter badge: The number to display in a badge on your app’s icon.
+     Specify 0 to remove the current badge, if any.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     - warning: For Apple OS's only.
+     */
+    public func setBadge(_ number: Int) -> Self {
+        var mutablePayload = self
+        mutablePayload.badge = AnyCodable(number)
+        return mutablePayload
+    }
+
+    /**
+     Increment the badge value by 1 to display on your app's icon.
+     - warning: For Apple OS's only.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     */
+    public func incrementBadge() -> Self {
+        var mutablePayload = self
+        mutablePayload.badge = AnyCodable(ParseOperationIncrement(amount: 1))
+        return mutablePayload
+    }
+}
diff --git a/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift b/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
index 71826cac2..1ab9ec23c 100644
--- a/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
+++ b/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
@@ -82,4 +82,44 @@ public protocol ParsePushApplePayloadable: ParsePushPayloadable {
      Specify for the `mdm` field where applicable.
      */
     var mdm: String? { get set }
+
+    init()
+}
+
+public extension ParsePushApplePayloadable {
+
+    /**
+     The content of the alert message.
+     */
+    var body: String? {
+        get {
+            alert?.body
+        }
+        set {
+            if alert != nil {
+                alert?.body = newValue
+            } else if let newBody = newValue {
+                alert = .init(body: newBody)
+            }
+        }
+    }
+
+    /**
+     Create an instance of `ParsePushPayloadApple` .
+     - parameter alert: The alert payload for the Apple push notification.
+     */
+    init(alert: ParsePushAppleAlert) {
+        self.init()
+        self.alert = alert
+    }
+
+    /**
+     Create an instance of `ParsePushPayloadApple` .
+     - parameter body: The body message to display for the Apple push notification.
+     */
+    init(body: String) {
+        self.init()
+        self.body = body
+    }
+
 }
diff --git a/Sources/ParseSwift/Types/ParsePush.swift b/Sources/ParseSwift/Types/ParsePush.swift
index d9bf97860..ff963445b 100644
--- a/Sources/ParseSwift/Types/ParsePush.swift
+++ b/Sources/ParseSwift/Types/ParsePush.swift
@@ -30,6 +30,7 @@ public struct ParsePush<V: ParsePushPayloadable>: ParseTypeable {
     public var payload: V?
     /// When to send the notification.
     public var pushTime: Date?
+
     /**
      The UNIX timestamp when the notification should expire.
      If the notification cannot be delivered to the device, will retry until it expires.
@@ -37,7 +38,7 @@ public struct ParsePush<V: ParsePushPayloadable>: ParseTypeable {
      no retries will be attempted.
      - note: This should not be set directly using a **Date** type. Instead it should
      be set using `expirationDate`.
-     - warning: Cannot send a notification with this valuel and `expirationInterval` both set.
+     - warning: Cannot send a notification with this value and `expirationInterval` both set.
      */
     var expirationTime: TimeInterval?
 
@@ -46,7 +47,7 @@ public struct ParsePush<V: ParsePushPayloadable>: ParseTypeable {
      If the notification cannot be delivered to the device, will retry until it expires.
      - note: This takes any date and turns it into a UNIX timestamp and sets the
      value of `expirationTime`.
-     - warning: Cannot send a notification with this valuel and `expirationInterval` both set.
+     - warning: Cannot send a notification with this value and `expirationInterval` both set.
      */
     var expirationDate: Date? {
         get {
@@ -59,9 +60,10 @@ public struct ParsePush<V: ParsePushPayloadable>: ParseTypeable {
             expirationTime = newValue?.timeIntervalSince1970
         }
     }
+
     /**
      The seconds from now to expire the notification.
-     - warning: Cannot send a notification with this valuel and `expirationTime` both set.
+     - warning: Cannot send a notification with this value and `expirationTime` both set.
      */
     public var expirationInterval: Int?
 
@@ -203,11 +205,13 @@ extension ParsePush {
         }
     }
 
-    func sendCommand() -> API.NonParseBodyCommand<Self, String> {
-
-        return API.NonParseBodyCommand(method: .POST,
-                                       path: .push,
-                                       body: self) { (data) -> String in
+    func sendCommand() -> API.NonParseBodyCommand<ParsePushNotificationBody, String> {
+        let body = ParsePushNotificationBody(push: self)
+        let command = API.NonParseBodyCommand(
+            method: .POST,
+            path: .push,
+            body: body
+        ) { (data) -> String in
             guard let response = try? ParseCoding.jsonDecoder().decode(PushResponse.self, from: data) else {
                 throw ParseError(code: .otherCause,
                                  message: "The server is missing \"X-Parse-Push-Status-Id\" in its header response")
@@ -222,6 +226,7 @@ extension ParsePush {
                 throw ParseError(code: .otherCause, message: "Push was unsuccessful")
             }
         }
+        return command
     }
 }
 
diff --git a/Sources/ParseSwift/Types/ParsePushNotificationBody.swift b/Sources/ParseSwift/Types/ParsePushNotificationBody.swift
new file mode 100644
index 000000000..ba358b21e
--- /dev/null
+++ b/Sources/ParseSwift/Types/ParsePushNotificationBody.swift
@@ -0,0 +1,51 @@
+//
+//  ParsePushNotificationBody.swift
+//  ParseSwift
+//
+//  Created by Corey Baker on 9/17/24.
+//  Copyright © 2024 Network Reconnaissance Lab. All rights reserved.
+//
+
+import Foundation
+
+struct ParsePushNotificationBody: ParseTypeable {
+    var `where`: QueryWhere?
+    var channels: Set<String>?
+    var data: AnyCodable?
+    var pushTime: Date?
+    var expirationTime: TimeInterval?
+    var expirationInterval: Int?
+
+    enum CodingKeys: String, CodingKey {
+        case pushTime = "push_time"
+        case expirationTime = "expiration_time"
+        case expirationInterval = "expiration_interval"
+        case `where`, channels, data
+    }
+
+    init<T: ParsePushApplePayload>(push: ParsePush<T>) {
+        self.where = push.where
+        self.channels = push.channels
+        self.pushTime = push.pushTime
+        self.expirationTime = push.expirationTime
+        self.expirationInterval = push.expirationInterval
+        if let payload = push.payload {
+            self.data = AnyCodable(
+                ParsePushAppleNotification(payload: payload)
+            )
+        }
+    }
+
+    init<T: ParsePushPayloadable>(push: ParsePush<T>) {
+        self.where = push.where
+        self.channels = push.channels
+        self.pushTime = push.pushTime
+        self.expirationTime = push.expirationTime
+        self.expirationInterval = push.expirationInterval
+        if let payload = push.payload {
+            self.data = AnyCodable(
+                payload
+            )
+        }
+    }
+}
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleAlert.swift b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleAlert.swift
index 2127ae650..d71543006 100644
--- a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleAlert.swift
+++ b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleAlert.swift
@@ -15,6 +15,7 @@ import Foundation
  for more information.
  */
 public struct ParsePushAppleAlert: ParseTypeable {
+
     /**
      The content of the alert message.
      */
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift
new file mode 100644
index 000000000..b295b475f
--- /dev/null
+++ b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift
@@ -0,0 +1,33 @@
+//
+//  ParsePushAppleNotification.swift
+//  ParseSwift
+//
+//  Created by Corey Baker on 9/16/24.
+//  Copyright © 2024 Network Reconnaissance Lab. All rights reserved.
+//
+
+struct ParsePushAppleNotification<P: ParsePushApplePayload>: ParsePushPayloadable {
+
+    var aps: P?
+    var collapseId: String?
+    var pushType: ParsePushPayloadApple.PushType?
+    var priority: Int?
+    var mdm: String?
+    public init() {}
+
+    public init(payload: P) {
+        self.aps = payload
+        self.collapseId = payload.collapseId
+        self.pushType = payload.pushType
+        self.priority = payload.priority
+        self.mdm = payload.mdm
+    }
+
+    enum CodingKeys: String, CodingKey {
+        case pushType = "push_type"
+        case collapseId = "collapse_id"
+        case mdm = "_mdm"
+        case aps, priority
+    }
+
+}
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift
index e058b7dc5..e19bf4c63 100644
--- a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift
+++ b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift
@@ -8,31 +8,13 @@
 
 import Foundation
 
-// swiftlint:disable line_length
-
 /// The payload data for an Apple push notification.
-public struct ParsePushPayloadApple: ParsePushApplePayloadable {
-    /**
-     The background notification flag. If you are a writing an app using the Remote Notification
-     Background Mode introduced in iOS7 (a.k.a. “Background Push”), set this value to
-     1 to trigger a background update. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app).
-     - warning: For Apple OS's only. You also have to set `pushType` starting iOS 13
-     and watchOS 6.
-     */
+public struct ParsePushPayloadApple: ParsePushApplePayload {
+
     public var contentAvailable: Int?
-    /**
-     The notification service app extension flag. Set this value to 1 to trigger the system to pass the notification to your notification service app extension before delivery. Use your extension to modify the notification’s content. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications).
-     - warning: You also have to set `pushType` starting iOS 13
-     and watchOS 6.
-     */
+
     public var mutableContent: Int?
-    /**
-     The priority of the notification. Specify 10 to send the notification immediately.
-     Specify 5 to send the notification based on power considerations on the user’s device.
-     See Apple's [documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns)
-     for more information.
-     - warning: For Apple OS's only.
-     */
+
     public var priority: Int?
 
     public var topic: String?
@@ -57,21 +39,6 @@ public struct ParsePushPayloadApple: ParsePushApplePayloadable {
 
     public var alert: ParsePushAppleAlert?
 
-    /**
-     The content of the alert message.
-     */
-    public var body: String? {
-        get {
-            alert?.body
-        }
-        set {
-            if alert != nil {
-                alert?.body = newValue
-            } else if let newBody = newValue {
-                alert = .init(body: newBody)
-            }
-        }
-    }
     var badge: AnyCodable?
     var sound: AnyCodable?
 
@@ -81,38 +48,23 @@ public struct ParsePushPayloadApple: ParsePushApplePayloadable {
         case alert
         /// Send as a background notification.
         case background
+        /// Send as a Live Activity notification.
+        case liveactivity
     }
 
     enum CodingKeys: String, CodingKey {
         case relevanceScore = "relevance-score"
-        case targetContentId = "targetContentIdentifier"
+        case targetContentId = "target-content-id"
         case mutableContent = "mutable-content"
         case contentAvailable = "content-available"
-        case pushType = "push_type"
-        case collapseId = "collapse_id"
-        case category, sound, badge, alert, threadId,
-             mdm, priority, topic, interruptionLevel,
-             urlArgs
+        case interruptionLevel = "interruption-level"
+        case urlArgs = "url-args"
+        case threadId = "thread-id"
+        case category, sound, badge, alert, topic
     }
 
     public init() {}
 
-    /**
-     Create an instance of `ParsePushPayloadApple` .
-     - parameter alert: The alert payload for the Apple push notification.
-     */
-    public init(alert: ParsePushAppleAlert) {
-        self.alert = alert
-    }
-
-    /**
-     Create an instance of `ParsePushPayloadApple` .
-     - parameter body: The body message to display for the Apple push notification.
-     */
-    public init(body: String) {
-        self.body = body
-    }
-
     public init(from decoder: Decoder) throws {
         let values = try decoder.container(keyedBy: CodingKeys.self)
         do {
@@ -126,83 +78,12 @@ public struct ParsePushPayloadApple: ParsePushApplePayloadable {
         targetContentId = try values.decodeIfPresent(String.self, forKey: .targetContentId)
         mutableContent = try values.decodeIfPresent(Int.self, forKey: .mutableContent)
         contentAvailable = try values.decodeIfPresent(Int.self, forKey: .contentAvailable)
-        priority = try values.decodeIfPresent(Int.self, forKey: .priority)
-        pushType = try values.decodeIfPresent(Self.PushType.self, forKey: .pushType)
-        collapseId = try values.decodeIfPresent(String.self, forKey: .collapseId)
         category = try values.decodeIfPresent(String.self, forKey: .category)
         sound = try values.decodeIfPresent(AnyCodable.self, forKey: .sound)
         badge = try values.decodeIfPresent(AnyCodable.self, forKey: .badge)
         threadId = try values.decodeIfPresent(String.self, forKey: .threadId)
-        mdm = try values.decodeIfPresent(String.self, forKey: .mdm)
         topic = try values.decodeIfPresent(String.self, forKey: .topic)
         interruptionLevel = try values.decodeIfPresent(String.self, forKey: .interruptionLevel)
         urlArgs = try values.decodeIfPresent([String].self, forKey: .urlArgs)
     }
-
-    /**
-     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
-     of your app’s container directory. For information about how to prepare sounds, see
-     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
-     - parameter sound: An instance of `ParsePushAppleSound`.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
-     - warning: For Apple OS's only.
-     */
-    public func setSound(_ sound: ParsePushAppleSound) -> Self {
-        var mutablePayload = self
-        mutablePayload.sound = AnyCodable(sound)
-        return mutablePayload
-    }
-
-    /**
-     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
-     of your app’s container directory. Specify the string “default” to play the system
-     sound. Pass a string for **regular** notifications. For critical alerts, pass the sound
-     `ParsePushAppleSound` instead. For information about how to prepare sounds, see
-     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
-     - parameter sound: A `String` or any `Codable` object that can be sent to APN.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
-     - warning: For Apple OS's only.
-     */
-    public func setSound<V>(_ sound: V) -> Self where V: Codable {
-        var mutablePayload = self
-        mutablePayload.sound = AnyCodable(sound)
-        return mutablePayload
-    }
-
-    /**
-     Get the sound using any type that conforms to `Codable`.
-     - returns: The sound casted to the inferred type.
-     - throws: An error of type `ParseError`.
-     */
-    public func getSound<V>() throws -> V where V: Codable {
-        guard let sound = sound?.value as? V else {
-            throw ParseError(code: .otherCause,
-                             message: "Cannot be casted to the inferred type")
-        }
-        return sound
-    }
-
-    /**
-     Set the badge to a specific value to display on your app's icon.
-     - parameter badge: The number to display in a badge on your app’s icon.
-     Specify 0 to remove the current badge, if any.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
-     - warning: For Apple OS's only.
-     */
-    public func setBadge(_ number: Int) -> Self {
-        var mutablePayload = self
-        mutablePayload.badge = AnyCodable(number)
-        return mutablePayload
-    }
-
-    /**
-     Increment the badge value by 1 to display on your app's icon.
-     - warning: For Apple OS's only.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
-     */
-    public func incrementBadge() -> Self {
-        var mutablePayload = self
-        mutablePayload.badge = AnyCodable(ParseOperationIncrement(amount: 1))
-        return mutablePayload
-    }
 }
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift
new file mode 100644
index 000000000..d07345dec
--- /dev/null
+++ b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift
@@ -0,0 +1,92 @@
+//
+//  ParsePushPayloadAppleLiveActivity.swift
+//  ParseSwift
+//
+//  Created by Corey Baker on 9/16/24.
+//  Copyright © 2024 Network Reconnaissance Lab. All rights reserved.
+//
+
+/// The payload data for an Apple LiveActivity push notification.
+public struct ParsePushPayloadAppleLiveActivity: ParsePushApplePayload {
+
+    /// A LiveActivity event.
+    public enum Event: String, Sendable, Codable {
+        /// Start a LiveActivity.
+        case start
+        /// Update a LiveActivity.
+        case update
+        /// End a LiveActivity.
+        case end
+    }
+
+    public var event : Event?
+
+    public var contentAvailable: Int?
+
+    public var mutableContent: Int?
+
+    public var priority: Int?
+
+    public var topic: String?
+
+    public var collapseId: String?
+
+    public var relevanceScore: Double?
+
+    public var targetContentId: String?
+
+    public var interruptionLevel: String?
+
+    public var pushType: ParsePushPayloadApple.PushType? = .liveactivity
+
+    public var category: String?
+
+    public var urlArgs: [String]?
+
+    public var threadId: String?
+
+    public var mdm: String?
+
+    public var alert: ParsePushAppleAlert?
+
+    var badge: AnyCodable?
+    var sound: AnyCodable?
+
+    enum CodingKeys: String, CodingKey {
+        case relevanceScore = "relevance-score"
+        case targetContentId = "target-content-id"
+        case mutableContent = "mutable-content"
+        case contentAvailable = "content-available"
+        case interruptionLevel = "interruption-level"
+        case urlArgs = "url-args"
+        case threadId = "thread-id"
+        case category, sound, badge, alert, topic
+    }
+
+    public init() {
+        // Set to the lowest live activity priority by default.
+        priority = 5
+    }
+
+    public init(from decoder: Decoder) throws {
+        let values = try decoder.container(keyedBy: CodingKeys.self)
+        do {
+            alert = try values.decode(ParsePushAppleAlert.self, forKey: .alert)
+        } catch {
+            if let alertBody = try values.decodeIfPresent(String.self, forKey: .alert) {
+                alert = ParsePushAppleAlert(body: alertBody)
+            }
+        }
+        relevanceScore = try values.decodeIfPresent(Double.self, forKey: .relevanceScore)
+        targetContentId = try values.decodeIfPresent(String.self, forKey: .targetContentId)
+        mutableContent = try values.decodeIfPresent(Int.self, forKey: .mutableContent)
+        contentAvailable = try values.decodeIfPresent(Int.self, forKey: .contentAvailable)
+        category = try values.decodeIfPresent(String.self, forKey: .category)
+        sound = try values.decodeIfPresent(AnyCodable.self, forKey: .sound)
+        badge = try values.decodeIfPresent(AnyCodable.self, forKey: .badge)
+        threadId = try values.decodeIfPresent(String.self, forKey: .threadId)
+        topic = try values.decodeIfPresent(String.self, forKey: .topic)
+        interruptionLevel = try values.decodeIfPresent(String.self, forKey: .interruptionLevel)
+        urlArgs = try values.decodeIfPresent([String].self, forKey: .urlArgs)
+    }
+}
diff --git a/Tests/ParseSwiftTests/ParsePushPayloadAppleTests.swift b/Tests/ParseSwiftTests/ParsePushPayloadAppleTests.swift
index 8ad4f6c64..9ef0f6c5f 100644
--- a/Tests/ParseSwiftTests/ParsePushPayloadAppleTests.swift
+++ b/Tests/ParseSwiftTests/ParsePushPayloadAppleTests.swift
@@ -39,10 +39,12 @@ class ParsePushPayloadAppleTests: XCTestCase {
     func testInitializers() throws {
         let body = "Hello from ParseSwift!"
         var applePayload = ParsePushPayloadApple(body: body)
-        XCTAssertEqual(applePayload.description,
-                       "{\"alert\":{\"body\":\"\(body)\"},\"push_type\":\"alert\"}")
+        let appleNotification = ParsePushAppleNotification(payload: applePayload)
+        XCTAssertEqual(appleNotification.description,
+                       "{\"aps\":{\"alert\":{\"body\":\"\(body)\"}},\"push_type\":\"alert\"}")
         let applePayload2 = ParsePushPayloadApple(alert: .init(body: body))
-        XCTAssertEqual(applePayload, applePayload2)
+        let appleNotification2 = ParsePushAppleNotification(payload: applePayload2)
+        XCTAssertEqual(appleNotification, appleNotification2)
         XCTAssertEqual(applePayload.body, body)
         applePayload.alert = nil
         XCTAssertNil(applePayload.body)
@@ -50,29 +52,43 @@ class ParsePushPayloadAppleTests: XCTestCase {
         XCTAssertEqual(applePayload.alert, applePayload2.alert)
     }
 
+    func testParsePushAppleNotification() throws {
+        let body = "Hello from ParseSwift!"
+        var applePayload = ParsePushPayloadApple(body: body)
+        applePayload.collapseId = "hello"
+        applePayload.pushType = .background
+        applePayload.priority = 1
+        applePayload.mdm = "naw"
+        let appleNotification = ParsePushAppleNotification(payload: applePayload)
+        XCTAssertEqual(
+            appleNotification.description,
+            "{\"_mdm\":\"naw\",\"aps\":{\"alert\":{\"body\":\"\(body)\"}},\"collapse_id\":\"hello\",\"priority\":1,\"push_type\":\"background\"}"
+        )
+    }
+
     func testBadge() throws {
         let applePayload = ParsePushPayloadApple()
             .setBadge(1)
         XCTAssertEqual(applePayload.description,
-                       "{\"badge\":1,\"push_type\":\"alert\"}")
+                       "{\"badge\":1}")
         let applePayload2 = ParsePushPayloadApple()
             .incrementBadge()
         XCTAssertEqual(applePayload2.description,
-                       "{\"badge\":{\"__op\":\"Increment\",\"amount\":1},\"push_type\":\"alert\"}")
+                       "{\"badge\":{\"__op\":\"Increment\",\"amount\":1}}")
     }
 
     func testSound() throws {
         let applePayload = ParsePushPayloadApple()
             .setSound("hello")
         XCTAssertEqual(applePayload.description,
-                       "{\"push_type\":\"alert\",\"sound\":\"hello\"}")
+                       "{\"sound\":\"hello\"}")
         let soundString: String = try applePayload.getSound()
         XCTAssertEqual(soundString, "hello")
         let sound = ParsePushAppleSound(critical: true, name: "hello", volume: 7)
         let applePayload2 = ParsePushPayloadApple()
             .setSound(sound)
         XCTAssertEqual(applePayload2.description,
-                       "{\"push_type\":\"alert\",\"sound\":{\"critical\":true,\"name\":\"hello\",\"volume\":7}}")
+                       "{\"sound\":{\"critical\":true,\"name\":\"hello\",\"volume\":7}}")
         let soundObject: ParsePushAppleSound = try applePayload2.getSound()
         XCTAssertEqual(soundObject, sound)
         XCTAssertThrowsError(try applePayload2.getSound() as String)
@@ -100,9 +116,9 @@ class ParsePushPayloadAppleTests: XCTestCase {
         applePayload.interruptionLevel = "yolo"
         applePayload.topic = "naw"
         applePayload.threadId = "yep"
-        applePayload.collapseId = "nope"
-        applePayload.pushType = .background
-        applePayload.priority = 6
+        // applePayload.collapseId = "nope"
+        // applePayload.pushType = .background
+        // applePayload.priority = 6
         applePayload.contentAvailable = 1
         applePayload.mutableContent = 1
         applePayload.targetContentId = "press"
@@ -111,7 +127,7 @@ class ParsePushPayloadAppleTests: XCTestCase {
         let decoded = try ParseCoding.jsonDecoder().decode(ParsePushPayloadApple.self, from: encoded)
         XCTAssertEqual(applePayload, decoded)
         XCTAssertEqual(applePayload.description,
-                       "{\"alert\":{\"action\":\"to\",\"action-loc-key\":\"icon\",\"body\":\"pull up\",\"launch-image\":\"it\",\"loc-args\":[\"mother\"],\"loc-key\":\"cousin\",\"subtitle\":\"trip\",\"subtitle-loc-args\":[\"gone\"],\"subtitle-loc-key\":\"far\",\"title\":\"you\",\"title-loc-args\":[\"arg\"],\"title-loc-key\":\"it\"},\"badge\":1,\"collapse_id\":\"nope\",\"content-available\":1,\"interruptionLevel\":\"yolo\",\"mutable-content\":1,\"priority\":6,\"push_type\":\"background\",\"relevance-score\":2,\"sound\":{\"critical\":true,\"name\":\"hello\",\"volume\":7},\"targetContentIdentifier\":\"press\",\"threadId\":\"yep\",\"topic\":\"naw\",\"urlArgs\":[\"help\"]}")
+                       "{\"alert\":{\"action\":\"to\",\"action-loc-key\":\"icon\",\"body\":\"pull up\",\"launch-image\":\"it\",\"loc-args\":[\"mother\"],\"loc-key\":\"cousin\",\"subtitle\":\"trip\",\"subtitle-loc-args\":[\"gone\"],\"subtitle-loc-key\":\"far\",\"title\":\"you\",\"title-loc-args\":[\"arg\"],\"title-loc-key\":\"it\"},\"badge\":1,\"content-available\":1,\"interruption-level\":\"yolo\",\"mutable-content\":1,\"relevance-score\":2,\"sound\":{\"critical\":true,\"name\":\"hello\",\"volume\":7},\"target-content-id\":\"press\",\"thread-id\":\"yep\",\"topic\":\"naw\",\"url-args\":[\"help\"]}")
         XCTAssertEqual(alert.description, "{\"action\":\"to\",\"action-loc-key\":\"icon\",\"body\":\"pull up\",\"launch-image\":\"it\",\"loc-args\":[\"mother\"],\"loc-key\":\"cousin\",\"subtitle\":\"trip\",\"subtitle-loc-args\":[\"gone\"],\"subtitle-loc-key\":\"far\",\"title\":\"you\",\"title-loc-args\":[\"arg\"],\"title-loc-key\":\"it\"}")
         let alert2 = ParsePushAppleAlert()
         XCTAssertNotEqual(alert, alert2)
@@ -144,15 +160,12 @@ class ParsePushPayloadAppleTests: XCTestCase {
         applePayload.interruptionLevel = "yolo"
         applePayload.topic = "naw"
         applePayload.threadId = "yep"
-        applePayload.collapseId = "nope"
-        applePayload.pushType = .background
         applePayload.targetContentId = "press"
         applePayload.relevanceScore = 2.0
-        applePayload.priority = 6
         applePayload.contentAvailable = 1
         applePayload.mutableContent = 1
 
-        guard let jsonData = "{\"alert\":\"pull up\",\"badge\":1,\"collapse_id\":\"nope\",\"content-available\":1,\"interruptionLevel\":\"yolo\",\"mutable-content\":1,\"priority\":6,\"push_type\":\"background\",\"relevance-score\":2,\"sound\":{\"critical\":true,\"name\":\"hello\",\"volume\":7},\"targetContentIdentifier\":\"press\",\"threadId\":\"yep\",\"topic\":\"naw\",\"urlArgs\":[\"help\"]}".data(using: .utf8) else {
+        guard let jsonData = "{\"alert\":\"pull up\",\"badge\":1,\"content-available\":1,\"interruption-level\":\"yolo\",\"mutable-content\":1,\"relevance-score\":2,\"sound\":{\"critical\":true,\"name\":\"hello\",\"volume\":7},\"target-content-id\":\"press\",\"thread-id\":\"yep\",\"topic\":\"naw\",\"url-args\":[\"help\"]}".data(using: .utf8) else {
             XCTFail("Should have unwrapped")
             return
         }

From 093542eb2b96f034ea3efe758790580a7e09aada Mon Sep 17 00:00:00 2001
From: Corey Baker <coreyearleon@icloud.com>
Date: Wed, 2 Oct 2024 17:02:35 -0700
Subject: [PATCH 2/3] lint

---
 .swiftlint.yml                                         |  1 +
 Sources/ParseSwift/API/API+Command.swift               |  4 ++--
 Sources/ParseSwift/API/API+NonParseBodyCommand.swift   |  2 +-
 Sources/ParseSwift/Extensions/URLSession.swift         |  8 ++++----
 Sources/ParseSwift/Storage/ParseFileManager.swift      | 10 +++++-----
 .../Apple/ParsePushPayloadAppleLiveActivity.swift      |  2 +-
 6 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/.swiftlint.yml b/.swiftlint.yml
index bc875a404..223f29c23 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -3,6 +3,7 @@ disabled_rules:
   - identifier_name
   - blanket_disable_command
   - non_optional_string_data_conversion
+  - optional_data_string_conversion
 excluded: # paths to ignore during linting. Takes precedence over `included`.
   - Tests/ParseSwiftTests/ParseEncoderTests
   - DerivedData
diff --git a/Sources/ParseSwift/API/API+Command.swift b/Sources/ParseSwift/API/API+Command.swift
index e65003857..1d7bec4e7 100644
--- a/Sources/ParseSwift/API/API+Command.swift
+++ b/Sources/ParseSwift/API/API+Command.swift
@@ -100,7 +100,7 @@ internal extension API {
                      allowIntermediateResponses: Bool = false,
                      uploadProgress: ((URLSessionTask, Int64, Int64, Int64) -> Void)? = nil,
                      downloadProgress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? = nil,
-                     completion: @escaping(Result<U, ParseError>) -> Void) async {
+                     completion: @escaping (Result<U, ParseError>) -> Void) async {
             let currentNotificationQueue: DispatchQueue!
             if let notificationQueue = notificationQueue {
                 currentNotificationQueue = notificationQueue
@@ -257,7 +257,7 @@ internal extension API {
                                batching: Bool = false,
                                childObjects: [String: PointerType]? = nil,
                                childFiles: [String: ParseFile]? = nil,
-                               completion: @escaping(Result<URLRequest, ParseError>) -> Void) {
+                               completion: @escaping (Result<URLRequest, ParseError>) -> Void) {
             let params = self.params?.getURLQueryItems()
             Task {
                 do {
diff --git a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift
index 77265d773..c5ac841d7 100644
--- a/Sources/ParseSwift/API/API+NonParseBodyCommand.swift
+++ b/Sources/ParseSwift/API/API+NonParseBodyCommand.swift
@@ -37,7 +37,7 @@ internal extension API {
         func execute(options: API.Options,
                      callbackQueue: DispatchQueue,
                      allowIntermediateResponses: Bool = false,
-                     completion: @escaping(Result<U, ParseError>) -> Void) async {
+                     completion: @escaping (Result<U, ParseError>) -> Void) async {
 
             switch await self.prepareURLRequest(options: options) {
             case .success(let urlRequest):
diff --git a/Sources/ParseSwift/Extensions/URLSession.swift b/Sources/ParseSwift/Extensions/URLSession.swift
index 78cba485c..6edaebdeb 100644
--- a/Sources/ParseSwift/Extensions/URLSession.swift
+++ b/Sources/ParseSwift/Extensions/URLSession.swift
@@ -143,7 +143,7 @@ internal extension URLSession {
         attempts: Int = 1,
         allowIntermediateResponses: Bool,
         mapper: @escaping (Data) async throws -> U,
-        completion: @escaping(Result<U, ParseError>) -> Void
+        completion: @escaping (Result<U, ParseError>) -> Void
     ) async {
         do {
             let (responseData, urlResponse) = try await dataTask(for: request)
@@ -288,7 +288,7 @@ internal extension URLSession {
         from file: URL?,
         progress: ((URLSessionTask, Int64, Int64, Int64) -> Void)?,
         mapper: @escaping (Data) async throws -> U,
-        completion: @escaping(Result<U, ParseError>) -> Void
+        completion: @escaping (Result<U, ParseError>) -> Void
     ) {
         var task: URLSessionTask?
         if let data = data {
@@ -354,7 +354,7 @@ internal extension URLSession {
         with request: URLRequest,
         progress: ((URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?,
         mapper: @escaping (Data) async throws -> U,
-        completion: @escaping(Result<U, ParseError>) -> Void
+        completion: @escaping (Result<U, ParseError>) -> Void
     ) async {
         let task = downloadTask(with: request) { (location, urlResponse, responseError) in
             Task {
@@ -374,7 +374,7 @@ internal extension URLSession {
     func downloadTask<U>(
         with request: URLRequest,
         mapper: @escaping (Data) async throws -> U,
-        completion: @escaping(Result<U, ParseError>) -> Void
+        completion: @escaping (Result<U, ParseError>) -> Void
     ) {
         Task {
             do {
diff --git a/Sources/ParseSwift/Storage/ParseFileManager.swift b/Sources/ParseSwift/Storage/ParseFileManager.swift
index 357d2b2b1..8a2dadbad 100644
--- a/Sources/ParseSwift/Storage/ParseFileManager.swift
+++ b/Sources/ParseSwift/Storage/ParseFileManager.swift
@@ -112,7 +112,7 @@ extension ParseFileManager {
         }
     }
 
-    func writeString(_ string: String, filePath: URL, completion: @escaping(Error?) -> Void) {
+    func writeString(_ string: String, filePath: URL, completion: @escaping (Error?) -> Void) {
         synchronizationQueue.async {
             do {
                 guard let data = string.data(using: .utf8) else {
@@ -127,7 +127,7 @@ extension ParseFileManager {
         }
     }
 
-    func writeData(_ data: Data, filePath: URL, completion: @escaping(Error?) -> Void) {
+    func writeData(_ data: Data, filePath: URL, completion: @escaping (Error?) -> Void) {
         synchronizationQueue.async {
             do {
                 try data.write(to: filePath, options: self.defaultDataWritingOptions)
@@ -138,7 +138,7 @@ extension ParseFileManager {
         }
     }
 
-    func copyItem(_ fromPath: URL, toPath: URL, completion: @escaping(Error?) -> Void) {
+    func copyItem(_ fromPath: URL, toPath: URL, completion: @escaping (Error?) -> Void) {
         synchronizationQueue.async {
             do {
                 try FileManager.default.copyItem(at: fromPath, to: toPath)
@@ -149,7 +149,7 @@ extension ParseFileManager {
         }
     }
 
-    func moveItem(_ fromPath: URL, toPath: URL, completion: @escaping(Error?) -> Void) {
+    func moveItem(_ fromPath: URL, toPath: URL, completion: @escaping (Error?) -> Void) {
         synchronizationQueue.async {
             if fromPath != toPath {
                 do {
@@ -164,7 +164,7 @@ extension ParseFileManager {
         }
     }
 
-    func moveContentsOfDirectory(_ fromPath: URL, toPath: URL, completion: @escaping(Error?) -> Void) {
+    func moveContentsOfDirectory(_ fromPath: URL, toPath: URL, completion: @escaping (Error?) -> Void) {
         synchronizationQueue.async {
             do {
                 if fromPath == toPath {
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift
index d07345dec..03b6f294a 100644
--- a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift
+++ b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift
@@ -19,7 +19,7 @@ public struct ParsePushPayloadAppleLiveActivity: ParsePushApplePayload {
         case end
     }
 
-    public var event : Event?
+    public var event: Event?
 
     public var contentAvailable: Int?
 

From b7489c58dd8f5fb49ae8c7af8dd4a1d3e23260b3 Mon Sep 17 00:00:00 2001
From: Corey Baker <coreyearleon@icloud.com>
Date: Thu, 27 Feb 2025 08:27:22 -0800
Subject: [PATCH 3/3] WIP

---
 .../Protocols/ParsePushApplePayload.swift     | 118 ++++++----------
 .../Protocols/ParsePushApplePayloadable.swift |  41 +++---
 .../Apple/ParsePushAppleNotification.swift    |  66 +++++++--
 .../Apple/ParsePushPayloadApple.swift         | 126 +++++++++++++++---
 .../ParsePushPayloadAny.swift                 |  59 +++++---
 .../ParseSwiftTests/ParsePushAsyncTests.swift |   2 +
 .../ParsePushCombineTests.swift               |   1 +
 7 files changed, 273 insertions(+), 140 deletions(-)

diff --git a/Sources/ParseSwift/Protocols/ParsePushApplePayload.swift b/Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
index fcd154125..099541597 100644
--- a/Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
+++ b/Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
@@ -6,23 +6,32 @@
 //  Copyright © 2024 Network Reconnaissance Lab. All rights reserved.
 //
 
+import Foundation
+
 // swiftlint:disable line_length
 
-protocol ParsePushApplePayload: ParsePushApplePayloadable {
+protocol ParsePushAppleHeader {
+
     /**
-     The background notification flag. If you are a writing an app using the Remote Notification
-     Background Mode introduced in iOS7 (a.k.a. “Background Push”), set this value to
-     1 to trigger a background update. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app).
-     - warning: For Apple OS's only. You also have to set `pushType` starting iOS 13
-     and watchOS 6.
+     The unique ID for the notification.
      */
-    var contentAvailable: Int? { get set }
+    var id: UUID? { get set }
     /**
-     The notification service app extension flag. Set this value to 1 to trigger the system to pass the notification to your notification service app extension before delivery. Use your extension to modify the notification’s content. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications).
-     - warning: You also have to set `pushType` starting iOS 13
-     and watchOS 6.
+     The unique ID for this request.
+     - note: Used for broadcast push notifications.
      */
-    var mutableContent: Int? { get set }
+    var requestId: UUID? { get set }
+    /**
+     A base64-encoded string that identifies the channel to publish the payload.
+     The channel ID is generated by sending channel creation request to APNs.
+     - note: Used for broadcast push notifications.
+     */
+    var channelId: String? { get set }
+    /**
+     Multiple notifications with same collapse identifier are displayed to the user as a single
+     notification. The value should not exceed 64 bytes.
+     */
+    var collapseId: String? { get set }
     /**
      The priority of the notification. Specify 10 to send the notification immediately.
      Specify 5 to send the notification based on power considerations on the user’s device.
@@ -31,79 +40,34 @@ protocol ParsePushApplePayload: ParsePushApplePayloadable {
      - warning: For Apple OS's only.
      */
     var priority: Int? { get set }
-
-    var pushType: ParsePushPayloadApple.PushType? { get set }
-
-    var badge: AnyCodable? { get set }
-    var sound: AnyCodable? { get set }
-}
-
-extension ParsePushApplePayload {
-
     /**
-     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
-     of your app’s container directory. For information about how to prepare sounds, see
-     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
-     - parameter sound: An instance of `ParsePushAppleSound`.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
-     - warning: For Apple OS's only.
-     */
-    public func setSound(_ sound: ParsePushAppleSound) -> Self {
-        var mutablePayload = self
-        mutablePayload.sound = AnyCodable(sound)
-        return mutablePayload
-    }
-
-    /**
-     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
-     of your app’s container directory. Specify the string “default” to play the system
-     sound. Pass a string for **regular** notifications. For critical alerts, pass the sound
-     `ParsePushAppleSound` instead. For information about how to prepare sounds, see
-     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
-     - parameter sound: A `String` or any `Codable` object that can be sent to APN.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
-     - warning: For Apple OS's only.
+     The destination topic for the notification.
      */
-    public func setSound<V>(_ sound: V) -> Self where V: Codable {
-        var mutablePayload = self
-        mutablePayload.sound = AnyCodable(sound)
-        return mutablePayload
-    }
-
+    var topic: String? { get set }
     /**
-     Get the sound using any type that conforms to `Codable`.
-     - returns: The sound casted to the inferred type.
-     - throws: An error of type `ParseError`.
+     The type of the notification. The value is alert or background. Specify alert when the
+     delivery of your notification displays an alert, plays a sound, or badges your app’s icon.
+     Specify background for silent notifications that do not interact with the user.
+     Defaults to alert if no value is set.
+     - warning: Required when delivering notifications to
+     devices running iOS 13 and later, or watchOS 6 and later. Ignored on earlier OS versions.
      */
-    public func getSound<V>() throws -> V where V: Codable {
-        guard let sound = sound?.value as? V else {
-            throw ParseError(code: .otherCause,
-                             message: "Cannot be casted to the inferred type")
-        }
-        return sound
-    }
+    var pushType: ParsePushPayloadApple.PushType? { get set }
+}
 
+public protocol ParsePushApplePayload: ParsePushApplePayloadable {
     /**
-     Set the badge to a specific value to display on your app's icon.
-     - parameter badge: The number to display in a badge on your app’s icon.
-     Specify 0 to remove the current badge, if any.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
-     - warning: For Apple OS's only.
+     The background notification flag. If you are a writing an app using the Remote Notification
+     Background Mode introduced in iOS7 (a.k.a. “Background Push”), set this value to
+     1 to trigger a background update. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app).
+     - warning: For Apple OS's only. You also have to set `pushType` starting iOS 13
+     and watchOS 6.
      */
-    public func setBadge(_ number: Int) -> Self {
-        var mutablePayload = self
-        mutablePayload.badge = AnyCodable(number)
-        return mutablePayload
-    }
-
+    var contentAvailable: Int? { get set }
     /**
-     Increment the badge value by 1 to display on your app's icon.
-     - warning: For Apple OS's only.
-     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     The notification service app extension flag. Set this value to 1 to trigger the system to pass the notification to your notification service app extension before delivery. Use your extension to modify the notification’s content. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications).
+     - warning: You also have to set `pushType` starting iOS 13
+     and watchOS 6.
      */
-    public func incrementBadge() -> Self {
-        var mutablePayload = self
-        mutablePayload.badge = AnyCodable(ParseOperationIncrement(amount: 1))
-        return mutablePayload
-    }
+    var mutableContent: Int? { get set }
 }
diff --git a/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift b/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
index 1ab9ec23c..5867bc53f 100644
--- a/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
+++ b/Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
@@ -17,19 +17,8 @@ import Foundation
  need to implement `CodingKeys`, see `ParsePushPayloadApple` for an example.
  */
 public protocol ParsePushApplePayloadable: ParsePushPayloadable {
-    /**
-     The payload for displaying an alert.
-     */
-    var alert: ParsePushAppleAlert? { get set }
-    /**
-     The destination topic for the notification.
-     */
-    var topic: String? { get set }
-    /**
-     Multiple notifications with same collapse identifier are displayed to the user as a single
-     notification. The value should not exceed 64 bytes.
-     */
-    var collapseId: String? { get set }
+
+    // MARK: Header and other high level information.
     /**
      The type of the notification. The value is alert or background. Specify alert when the
      delivery of your notification displays an alert, plays a sound, or badges your app’s icon.
@@ -39,6 +28,28 @@ public protocol ParsePushApplePayloadable: ParsePushPayloadable {
      devices running iOS 13 and later, or watchOS 6 and later. Ignored on earlier OS versions.
      */
     var pushType: ParsePushPayloadApple.PushType? { get set }
+
+    // MARK: APS information.
+
+    /**
+     The background notification flag. If you are a writing an app using the Remote Notification
+     Background Mode introduced in iOS7 (a.k.a. “Background Push”), set this value to
+     1 to trigger a background update. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app).
+     - warning: For Apple OS's only. You also have to set `pushType` starting iOS 13
+     and watchOS 6.
+     */
+    var contentAvailable: Int? { get set }
+    /**
+     The notification service app extension flag. Set this value to 1 to trigger the system to pass the notification to your notification service app extension before delivery. Use your extension to modify the notification’s content. For more informaiton, see [Apple's documentation](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications).
+     - warning: You also have to set `pushType` starting iOS 13
+     and watchOS 6.
+     */
+    var mutableContent: Int? { get set }
+
+    /**
+     The payload for displaying an alert.
+     */
+    var alert: ParsePushAppleAlert? { get set }
     /**
      The identifier of the `UNNotification​Category` for this push notification.
      See Apple's
@@ -78,10 +89,6 @@ public protocol ParsePushApplePayloadable: ParsePushPayloadable {
      notification summary. See [relevanceScore](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/3821031-relevancescore).
      */
     var relevanceScore: Double? { get set }
-    /**
-     Specify for the `mdm` field where applicable.
-     */
-    var mdm: String? { get set }
 
     init()
 }
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift
index b295b475f..2a9efaca0 100644
--- a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift
+++ b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift
@@ -6,28 +6,70 @@
 //  Copyright © 2024 Network Reconnaissance Lab. All rights reserved.
 //
 
-struct ParsePushAppleNotification<P: ParsePushApplePayload>: ParsePushPayloadable {
+import Foundation
 
-    var aps: P?
+struct ParsePushAppleNotification<P: ParsePushApplePayload>: ParsePushAppleHeader, ParsePushPayloadable {
+
+    struct APS: ParseTypeable {
+        var payload: P
+
+        enum CodingKeys: String, CodingKey {
+            case payload = "aps"
+        }
+    }
+
+    // Notification Header properties set by parse-server-push-adapter.
     var collapseId: String?
-    var pushType: ParsePushPayloadApple.PushType?
     var priority: Int?
-    var mdm: String?
+    var pushType: ParsePushPayloadApple.PushType?
+    var topic: String?
+
+    // Notification Header properties set directly.
+    var id: UUID?
+    var requestId: UUID?
+    var channelId: String?
+    var payload: APS?
+
     public init() {}
 
-    public init(payload: P) {
-        self.aps = payload
-        self.collapseId = payload.collapseId
+    init(
+        id: UUID? = nil,
+        collapseId: String? = nil,
+        requestId: UUID? = nil,
+        channelId: String? = nil,
+        priority: Int? = nil,
+        topic: String? = nil,
+        payload: P
+    ) {
+        self.id = id
+        self.collapseId = collapseId
+        self.requestId = requestId
+        self.channelId = channelId
+        self.priority = priority
+        self.topic = topic
         self.pushType = payload.pushType
-        self.priority = payload.priority
-        self.mdm = payload.mdm
+        self.payload = APS(payload: payload)
     }
 
     enum CodingKeys: String, CodingKey {
         case pushType = "push_type"
-        case collapseId = "collapse_id"
-        case mdm = "_mdm"
-        case aps, priority
+        case payload = "rawPayload"
+        case collapseId, priority, topic, id, requestId, channelId
     }
 
 }
+
+extension ParsePushAppleNotification where P: ParsePushApplePayload & ParsePushAppleHeader {
+
+    init(
+        id: UUID? = nil,
+        payload: P
+    ) {
+        self.id = id
+        self.collapseId = payload.collapseId
+        self.pushType = payload.pushType
+        self.priority = payload.priority
+        self.topic = payload.topic
+        self.payload = APS(payload: payload)
+    }
+}
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift
index e19bf4c63..cde8eb86d 100644
--- a/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift
+++ b/Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift
@@ -11,45 +11,64 @@ import Foundation
 /// The payload data for an Apple push notification.
 public struct ParsePushPayloadApple: ParsePushApplePayload {
 
-    public var contentAvailable: Int?
-
-    public var mutableContent: Int?
-
+    // MARK: Header and other high level information.
+    public var collapseId: String?
+    public var mdm: String?
     public var priority: Int?
-
+    public var pushType: PushType? = .alert
     public var topic: String?
 
-    public var collapseId: String?
-
+    // MARK: APS information.
+    public var contentAvailable: Int?
+    public var mutableContent: Int?
     public var relevanceScore: Double?
-
     public var targetContentId: String?
-
     public var interruptionLevel: String?
-
-    public var pushType: PushType? = .alert
-
     public var category: String?
-
     public var urlArgs: [String]?
-
     public var threadId: String?
-
-    public var mdm: String?
-
     public var alert: ParsePushAppleAlert?
 
     var badge: AnyCodable?
     var sound: AnyCodable?
 
     /// The type of notification.
+    /// For more details, see [Apple Documentation](https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns).
     public enum PushType: String, Codable, Sendable {
         /// Send as an alert.
         case alert
         /// Send as a background notification.
         case background
+        /// Send as a Push to Talk notification.
+        case location
+        case voip
+        case complication
+        case fileprovider
+        case mdm
+        case pushtotalk
         /// Send as a Live Activity notification.
         case liveactivity
+
+        func appendRequiredInformationToTopic(
+            _ topic: String
+        ) -> String {
+            switch self {
+            case .location:
+                return "\(topic).location-query"
+            case .voip:
+                return "\(topic).voip"
+            case .complication:
+                return "\(topic).complication"
+            case .fileprovider:
+                return "\(topic).pushkit.fileprovider"
+            case .liveactivity:
+                return "\(topic).push-type.liveactivity"
+            case .pushtotalk:
+                return "\(topic).voip-ptt"
+            default:
+                return topic
+            }
+        }
     }
 
     enum CodingKeys: String, CodingKey {
@@ -60,7 +79,7 @@ public struct ParsePushPayloadApple: ParsePushApplePayload {
         case interruptionLevel = "interruption-level"
         case urlArgs = "url-args"
         case threadId = "thread-id"
-        case category, sound, badge, alert, topic
+        case category, sound, badge, alert
     }
 
     public init() {}
@@ -82,8 +101,77 @@ public struct ParsePushPayloadApple: ParsePushApplePayload {
         sound = try values.decodeIfPresent(AnyCodable.self, forKey: .sound)
         badge = try values.decodeIfPresent(AnyCodable.self, forKey: .badge)
         threadId = try values.decodeIfPresent(String.self, forKey: .threadId)
-        topic = try values.decodeIfPresent(String.self, forKey: .topic)
         interruptionLevel = try values.decodeIfPresent(String.self, forKey: .interruptionLevel)
         urlArgs = try values.decodeIfPresent([String].self, forKey: .urlArgs)
     }
 }
+
+extension ParsePushPayloadApple {
+
+    /**
+     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
+     of your app’s container directory. For information about how to prepare sounds, see
+     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
+     - parameter sound: An instance of `ParsePushAppleSound`.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     - warning: For Apple OS's only.
+     */
+    public func setSound(_ sound: ParsePushAppleSound) -> Self {
+        var mutablePayload = self
+        mutablePayload.sound = AnyCodable(sound)
+        return mutablePayload
+    }
+
+    /**
+     Set the name of a sound file in your app’s main bundle or in the Library/Sounds folder
+     of your app’s container directory. Specify the string “default” to play the system
+     sound. Pass a string for **regular** notifications. For critical alerts, pass the sound
+     `ParsePushAppleSound` instead. For information about how to prepare sounds, see
+     [UNNotificationSound](https://developer.apple.com/documentation/usernotifications/unnotificationsound).
+     - parameter sound: A `String` or any `Codable` object that can be sent to APN.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     - warning: For Apple OS's only.
+     */
+    public func setSound<V>(_ sound: V) -> Self where V: Codable {
+        var mutablePayload = self
+        mutablePayload.sound = AnyCodable(sound)
+        return mutablePayload
+    }
+
+    /**
+     Get the sound using any type that conforms to `Codable`.
+     - returns: The sound casted to the inferred type.
+     - throws: An error of type `ParseError`.
+     */
+    public func getSound<V>() throws -> V where V: Codable {
+        guard let sound = sound?.value as? V else {
+            throw ParseError(code: .otherCause,
+                             message: "Cannot be casted to the inferred type")
+        }
+        return sound
+    }
+
+    /**
+     Set the badge to a specific value to display on your app's icon.
+     - parameter badge: The number to display in a badge on your app’s icon.
+     Specify 0 to remove the current badge, if any.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     - warning: For Apple OS's only.
+     */
+    public func setBadge(_ number: Int) -> Self {
+        var mutablePayload = self
+        mutablePayload.badge = AnyCodable(number)
+        return mutablePayload
+    }
+
+    /**
+     Increment the badge value by 1 to display on your app's icon.
+     - warning: For Apple OS's only.
+     - returns: A mutated instance of `ParsePushPayloadApple` for easy chaining.
+     */
+    public func incrementBadge() -> Self {
+        var mutablePayload = self
+        mutablePayload.badge = AnyCodable(ParseOperationIncrement(amount: 1))
+        return mutablePayload
+    }
+}
diff --git a/Sources/ParseSwift/Types/ParsePushPayload/ParsePushPayloadAny.swift b/Sources/ParseSwift/Types/ParsePushPayload/ParsePushPayloadAny.swift
index c4949864c..1e039e8e8 100644
--- a/Sources/ParseSwift/Types/ParsePushPayload/ParsePushPayloadAny.swift
+++ b/Sources/ParseSwift/Types/ParsePushPayload/ParsePushPayloadAny.swift
@@ -22,7 +22,6 @@ public struct ParsePushPayloadAny: ParsePushApplePayloadable, ParsePushFirebaseP
     public var threadId: String?
     public var interruptionLevel: String?
     public var relevanceScore: Double?
-    public var mdm: String?
     public var uri: URL?
     public var title: String?
     public var collapseKey: String?
@@ -39,30 +38,62 @@ public struct ParsePushPayloadAny: ParsePushApplePayloadable, ParsePushFirebaseP
     var mutableContent: AnyCodable?
 
     public init(from decoder: Decoder) throws {
-        let values = try decoder.container(keyedBy: RawCodingKey.self)
-        relevanceScore = try values.decodeIfPresent(Double.self, forKey: .key("relevance-score"))
-        targetContentId = try values.decodeIfPresent(String.self, forKey: .key("targetContentIdentifier"))
+        let values = try decoder.container(
+            keyedBy: RawCodingKey.self
+        )
+        relevanceScore = try values.decodeIfPresent(
+            Double.self,
+            forKey: .key("relevance-score")
+        )
+        targetContentId = try values.decodeIfPresent(
+            String.self,
+            forKey: .key("targetContentIdentifier")
+        )
         do {
-            mutableContent = try values.decode(AnyCodable.self, forKey: .key("mutable-content"))
+            mutableContent = try values.decode(
+                AnyCodable.self,
+                forKey: .key("mutable-content")
+            )
         } catch {
-            mutableContent = try values.decodeIfPresent(AnyCodable.self, forKey: .key("mutableContent"))
+            mutableContent = try values.decodeIfPresent(
+                AnyCodable.self,
+                forKey: .key("mutableContent")
+            )
         }
         do {
-            contentAvailable = try values.decode(AnyCodable.self, forKey: .key("content-available"))
+            contentAvailable = try values.decode(
+                AnyCodable.self,
+                forKey: .key("content-available")
+            )
         } catch {
-            contentAvailable = try values.decodeIfPresent(AnyCodable.self, forKey: .key("contentAvailable"))
+            contentAvailable = try values.decodeIfPresent(
+                AnyCodable.self,
+                forKey: .key("contentAvailable")
+            )
         }
         do {
-            let priorityInt = try values.decode(Int.self, forKey: .key("priority"))
+            let priorityInt = try values.decode(
+                Int.self,
+                forKey: .key("priority")
+            )
             priority = AnyCodable(priorityInt)
         } catch {
-            if let priorityString = try values.decodeIfPresent(String.self, forKey: .key("priority")),
-               let priorityEnum = ParsePushPayloadFirebase.PushPriority(rawValue: priorityString) {
+            if let priorityString = try values.decodeIfPresent(
+                String.self,
+                forKey: .key("priority")
+            ),
+                let priorityEnum = ParsePushPayloadFirebase.PushPriority(
+                    rawValue: priorityString
+                ) {
                 priority = AnyCodable(priorityEnum)
             }
         }
-        pushType = try values.decodeIfPresent(ParsePushPayloadApple.PushType.self, forKey: .key("push_type"))
-        collapseId = try values.decodeIfPresent(String.self, forKey: .key("collapse_id"))
+        pushType = try values.decodeIfPresent(
+            ParsePushPayloadApple.PushType.self,
+            forKey: .key("push_type")
+        )
+        collapseId = try values.decodeIfPresent(
+            String.self, forKey: .key("collapse_id"))
         category = try values.decodeIfPresent(String.self, forKey: .key("category"))
         sound = try values.decodeIfPresent(AnyCodable.self, forKey: .key("sound"))
         badge = try values.decodeIfPresent(AnyCodable.self, forKey: .key("badge"))
@@ -74,7 +105,6 @@ public struct ParsePushPayloadAny: ParsePushApplePayloadable, ParsePushFirebaseP
             }
         }
         threadId = try values.decodeIfPresent(String.self, forKey: .key("threadId"))
-        mdm = try values.decodeIfPresent(String.self, forKey: .key("mdm"))
         topic = try values.decodeIfPresent(String.self, forKey: .key("topic"))
         interruptionLevel = try values.decodeIfPresent(String.self, forKey: .key("interruptionLevel"))
         urlArgs = try values.decodeIfPresent([String].self, forKey: .key("urlArgs"))
@@ -105,7 +135,6 @@ public struct ParsePushPayloadAny: ParsePushApplePayloadable, ParsePushFirebaseP
         payload.threadId = threadId
         payload.interruptionLevel = interruptionLevel
         payload.relevanceScore = relevanceScore
-        payload.mdm = mdm
         payload.alert = alert
         payload.badge = badge
         payload.sound = sound
diff --git a/Tests/ParseSwiftTests/ParsePushAsyncTests.swift b/Tests/ParseSwiftTests/ParsePushAsyncTests.swift
index 655e94187..31b8fbf39 100644
--- a/Tests/ParseSwiftTests/ParsePushAsyncTests.swift
+++ b/Tests/ParseSwiftTests/ParsePushAsyncTests.swift
@@ -275,6 +275,7 @@ class ParsePushAsyncTests: XCTestCase {
         let appleAlert = ParsePushAppleAlert(body: "hello world")
         var anyPayload = ParsePushPayloadAny()
         anyPayload.alert = appleAlert
+        anyPayload.pushType = .alert
         var statusOnServer = ParsePushStatus<ParsePushPayloadAny>()
         statusOnServer.payload = anyPayload
         statusOnServer.objectId = objectId
@@ -304,6 +305,7 @@ class ParsePushAsyncTests: XCTestCase {
         let appleAlert = ParsePushAppleAlert(body: "hello world")
         var anyPayload = ParsePushPayloadAny()
         anyPayload.alert = appleAlert
+        anyPayload.pushType = .alert
         let query = Installation.query("peace" == "out")
         var statusOnServer = try ParsePushStatusResponse()
             .setPayload(anyPayload)
diff --git a/Tests/ParseSwiftTests/ParsePushCombineTests.swift b/Tests/ParseSwiftTests/ParsePushCombineTests.swift
index 131fe3623..a84bb445f 100644
--- a/Tests/ParseSwiftTests/ParsePushCombineTests.swift
+++ b/Tests/ParseSwiftTests/ParsePushCombineTests.swift
@@ -309,6 +309,7 @@ class ParsePushCombineTests: XCTestCase {
         let appleAlert = ParsePushAppleAlert(body: "hello world")
         var anyPayload = ParsePushPayloadAny()
         anyPayload.alert = appleAlert
+        anyPayload.pushType = .alert
         var statusOnServer = ParsePushStatus<ParsePushPayloadAny>()
         statusOnServer.payload = anyPayload
         statusOnServer.objectId = objectId