Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support Apple push live activity notifications #184

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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>";
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand Down
109 changes: 109 additions & 0 deletions Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
40 changes: 40 additions & 0 deletions Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
21 changes: 13 additions & 8 deletions Sources/ParseSwift/Types/ParsePush.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ 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.
An expiry of **0** indicates that the notification expires immediately, therefore
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?

Expand All @@ -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 {
Expand All @@ -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?

Expand Down Expand Up @@ -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")
Expand All @@ -222,6 +226,7 @@ extension ParsePush {
throw ParseError(code: .otherCause, message: "Push was unsuccessful")
}
}
return command
}
}

Expand Down
51 changes: 51 additions & 0 deletions Sources/ParseSwift/Types/ParsePushNotificationBody.swift
Original file line number Diff line number Diff line change
@@ -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
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Foundation
for more information.
*/
public struct ParsePushAppleAlert: ParseTypeable {

/**
The content of the alert message.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}

}
Loading
Loading