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 5 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
73 changes: 73 additions & 0 deletions Sources/ParseSwift/Protocols/ParsePushApplePayload.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// ParsePushApplePayload.swift
// ParseSwift
//
// Created by Corey Baker on 9/16/24.
// Copyright © 2024 Network Reconnaissance Lab. All rights reserved.
//

import Foundation

// swiftlint:disable line_length

protocol ParsePushAppleHeader {

/**
The unique ID for the notification.
*/
var id: UUID? { get set }
/**
The unique ID for this request.
- note: Used for broadcast push notifications.
*/
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.
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 }
/**
The destination topic for the notification.
*/
var topic: String? { get set }
/**
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.
*/
var pushType: ParsePushPayloadApple.PushType? { get set }
}

public 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 }
}
77 changes: 62 additions & 15 deletions Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -78,8 +89,44 @@ public protocol ParsePushApplePayloadable: ParsePushPayloadable {
notification summary. See [relevanceScore](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/3821031-relevancescore).
*/
var relevanceScore: Double? { get set }

init()
}

public extension ParsePushApplePayloadable {

/**
Specify for the `mdm` field where applicable.
The content of the alert message.
*/
var mdm: String? { get set }
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
Loading
Loading