Skip to content

feat: Support Apple push live activity notifications#184

Draft
cbaker6 wants to merge 8 commits intomainfrom
liveActivity
Draft

feat: Support Apple push live activity notifications#184
cbaker6 wants to merge 8 commits intomainfrom
liveActivity

Conversation

@cbaker6
Copy link
Member

@cbaker6 cbaker6 commented Oct 2, 2024

New Pull Request Checklist

Issue Description

Approach

TODOs before merging

  • Add tests
  • Add entry to changelog
  • Add changes to documentation (guides, repository pages, in-code descriptions)

@cbaker6 cbaker6 marked this pull request as draft October 2, 2024 23:56
@cbaker6 cbaker6 changed the title fix: Apple push notificaiton not working for live activities feat: Support Apple push live activity notifications Oct 2, 2024
@coderabbitai
Copy link

coderabbitai bot commented Oct 8, 2025

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for Apple Live Activity push notifications
    • Added helper methods for managing push notification properties (sound, badge)
  • Refactor

    • Reorganized Apple push notification payload structure with improved header information handling
    • Updated notification body construction and serialization logic

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

PLUS ULTRA! These changes fortify the Apple push notification infrastructure by introducing structured protocols and types for payload management. Four new Swift files wire protocols ParsePushAppleHeader and ParsePushApplePayload, types ParsePushNotificationBody, ParsePushAppleNotification, and ParsePushPayloadAppleLiveActivity into the codebase, alongside refactoring existing payload types with enhanced serialization support!

Changes

Cohort / File(s) Summary
Xcode Project Configuration
ParseSwift.xcodeproj/project.pbxproj
Added build file references and source phase entries for four new Swift files (ParsePushApplePayload.swift, ParsePushAppleNotification.swift, ParsePushPayloadAppleLiveActivity.swift, ParsePushNotificationBody.swift); updated group structures to organize files under Apple and related hierarchies.
Apple Push Protocols
Sources/ParseSwift/Protocols/ParsePushApplePayload.swift, Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift
Introduced ParsePushAppleHeader protocol with header fields (id, requestId, channelId, collapseId, priority, topic, pushType); refactored ParsePushApplePayloadable by removing topic/collapseId, adding contentAvailable/mutableContent, and introducing computed body property with convenience initializers.
Apple Push Notification Types
Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleNotification.swift, Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift, Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift
Created ParsePushAppleNotification generic wrapper modeling APNs with header fields and APS payload embedding; expanded ParsePushPayloadApple to conform to ParsePushApplePayload with PushType enum (eight cases including liveactivity) and helper methods (setSound, setBadge, incrementBadge); introduced ParsePushPayloadAppleLiveActivity with Event enum and custom decoding logic.
Push Notification Body Wrapper
Sources/ParseSwift/Types/ParsePushNotificationBody.swift
New struct encapsulating push data (where, channels, data, pushTime, expirationTime, expirationInterval) with generic initializers for ParsePushApplePayload and ParsePushPayloadable, wrapping payloads in AnyCodable for serialization.
Core Push Type Updates
Sources/ParseSwift/Types/ParsePush.swift, Sources/ParseSwift/Types/ParsePushPayload/ParsePushPayloadAny.swift, Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushAppleAlert.swift
Modified ParsePush.sendCommand() to return command with ParsePushNotificationBody instead of Self; removed mdm property from ParsePushPayloadAny and its decoding/conversion logic; minor formatting in ParsePushAppleAlert.
Test Updates
Tests/ParseSwiftTests/ParsePushAsyncTests.swift, Tests/ParseSwiftTests/ParsePushCombineTests.swift, Tests/ParseSwiftTests/ParsePushPayloadAppleTests.swift
Updated payload initialization to explicitly set pushType; refactored assertions to use ParsePushAppleNotification wrapper; adjusted expected JSON encoding to reflect new key mappings (kebab-case for interruption-level, target-content-id, thread-id, content-available) and removed expectations for top-level push_type, collapse_id, priority when unset.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes


NEW HEROES RISE IN THE NOTIFICATION REALM!

Apple's push powers now shine so bright,
With protocols and types arranged just right,
LiveActivity events reach their grand height,
Plus Ultra refactoring—a MAGNIFICENT SIGHT! 🦸‍♂️✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete with empty placeholders. Issue Description and Approach sections are blank, and all three TODO items remain unchecked without evidence of completion. Fill in the Issue Description with the problem context, provide a detailed Approach explaining the implementation strategy, and confirm completion of tests, changelog, and documentation updates.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Support Apple push live activity notifications' clearly and specifically describes the main feature being added—Apple push live activity notification support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch liveActivity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cbaker6
Copy link
Member Author

cbaker6 commented Jan 19, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Sources/ParseSwift/Types/ParsePushPayload/ParsePushPayloadAny.swift (1)

15-38: YOUNG HERO! THE PIPELINE IS CRYING OUT FOR HELP!

The build is failing because ParsePushPayloadAny no longer conforms to ParsePushApplePayloadable! The protocol now requires contentAvailable to be of type Int?, but this struct declares it as AnyCodable? on line 37.

This is a CRITICAL issue that must be resolved before this PR can be merged, PLUS ULTRA!

You have several options to fix this:

  1. Add a computed property that satisfies the protocol requirement while keeping the internal AnyCodable for decoding flexibility
  2. Update the protocol conformance approach to handle the type mismatch
  3. Refactor to use Int? directly and adjust the decoding logic
💡 Potential approach using computed property
// Keep internal storage as AnyCodable for flexible decoding
private var _contentAvailable: AnyCodable?

// Satisfy protocol with computed property
public var contentAvailable: Int? {
    get { _contentAvailable?.value as? Int }
    set { _contentAvailable = newValue.map { AnyCodable($0) } }
}
Sources/ParseSwift/Types/ParsePush.swift (1)

211-225: PLUS ULTRA: Fix the typo in the Boolean response error.
“resturn” → “return” to keep error text crisp.

🛠️ Proposed fix
-                throw ParseError(code: .otherCause, message: "The server did not resturn a Boolean response")
+                throw ParseError(code: .otherCause, message: "The server did not return a Boolean response")
🤖 Fix all issues with AI agents
In `@Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift`:
- Around line 34-47: The new requirements (contentAvailable, mutableContent, and
init()) were added to the public protocol ParsePushApplePayloadable which is
source-breaking; to avoid breaking external conformers, remove these
requirements from ParsePushApplePayloadable and instead add them as stored
properties and an initializer on the concrete type ParsePushApplePayload (or
create a new opt-in protocol e.g., ParsePushApplePayloadWithFlags that declares
contentAvailable, mutableContent, and init()), and keep
ParsePushApplePayloadable unchanged so existing conformers continue to compile.

In `@Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift`:
- Around line 35-51: The documentation comment for the PushType enum exceeds the
120-character limit and several enum cases lack per-case docs; split the long
enum docstring into shorter lines (<=120 chars) and add brief documentation
comments for each case in enum PushType (e.g., alert, background, location,
voip, complication, fileprovider, mdm, pushtotalk, liveactivity) describing
their purpose so each case is documented and the header comment lines stay under
the limit.

In
`@Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift`:
- Around line 55-91: The CodingKeys enum and the initializer init(from decoder:)
in ParsePushPayloadAppleLiveActivity are missing keys and decode paths for
event, priority, collapseId, pushType, and mdm; update the CodingKeys enum to
include case event, priority, collapseId = "collapse-id", pushType =
"push-type", and mdm, then in init(from decoder:) decode those fields (using
decodeIfPresent where appropriate) and set defaults: ensure priority defaults to
5 and pushType defaults to .liveactivity if nil; also add decoding for event,
collapseId and mdm to match their stored types so Live Activity fields are
serialized/deserialized correctly.

Comment on lines +34 to +47
/**
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 }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "ParsePushApplePayloadable.swift" -type f

Repository: netreconlab/Parse-Swift

Length of output: 129


🏁 Script executed:

cat -n ./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift

Repository: netreconlab/Parse-Swift

Length of output: 6648


🏁 Script executed:

cd ./Sources/ParseSwift/Protocols && git diff HEAD~1 ParsePushApplePayloadable.swift

Repository: netreconlab/Parse-Swift

Length of output: 255


🏁 Script executed:

git log --oneline --max-count=20 -- ./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift

Repository: netreconlab/Parse-Swift

Length of output: 112


🏁 Script executed:

git diff 24c9d60^..24c9d60 -- ./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift

Repository: netreconlab/Parse-Swift

Length of output: 108


🏁 Script executed:

git show 24c9d60:./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift | head -100

Repository: netreconlab/Parse-Swift

Length of output: 4977


🏁 Script executed:

rg "ParsePushApplePayloadable" --type swift -A 3 | head -80

Repository: netreconlab/Parse-Swift

Length of output: 2097


🏁 Script executed:

rg "ParsePushApplePayloadable" --type swift | grep -E "(struct|class).*ParsePushApplePayloadable"

Repository: netreconlab/Parse-Swift

Length of output: 227


🏁 Script executed:

git log --all --oneline -- ./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift | head -5

Repository: netreconlab/Parse-Swift

Length of output: 237


🏁 Script executed:

git show 2410ff6:./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift 2>/dev/null | grep -A 2 -E "(contentAvailable|mutableContent|^\s+init\(\))" | head -20

Repository: netreconlab/Parse-Swift

Length of output: 49


🏁 Script executed:

git show 51a44ef:./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift 2>/dev/null | head -100

Repository: netreconlab/Parse-Swift

Length of output: 4058


🏁 Script executed:

git show 51a44ef:./Sources/ParseSwift/Protocols/ParsePushPayloadable.swift 2>/dev/null | head -50

Repository: netreconlab/Parse-Swift

Length of output: 1081


🏁 Script executed:

git diff 51a44ef..2410ff6 -- ./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift

Repository: netreconlab/Parse-Swift

Length of output: 598


🏁 Script executed:

git diff 51a44ef..HEAD -- ./Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift | head -150

Repository: netreconlab/Parse-Swift

Length of output: 4392


🏁 Script executed:

cat -n ./Sources/ParseSwift/Types/ParsePushPayload/ParsePushPayloadAny.swift | head -80

Repository: netreconlab/Parse-Swift

Length of output: 3160


🏁 Script executed:

rg "ParsePushApplePayloadable\|ParsePushApplePayload" --type swift | grep -E ":\s*(ParsePushApplePayloadable|ParsePushApplePayload)"

Repository: netreconlab/Parse-Swift

Length of output: 49


Source-breaking changes to public protocol require careful consideration!
Adding contentAvailable, mutableContent, and init() as new requirements to the public ParsePushApplePayloadable protocol breaks external conformers. This is a legitimate source-breaking change. If this isn't accompanied by a major-version bump, consider moving these requirements to ParsePushApplePayload instead, or introduce them as an opt-in protocol to maintain backward compatibility. I'm ready to help draft an alternative approach that keeps the protocol stable!

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Protocols/ParsePushApplePayloadable.swift` around lines 34
- 47, The new requirements (contentAvailable, mutableContent, and init()) were
added to the public protocol ParsePushApplePayloadable which is source-breaking;
to avoid breaking external conformers, remove these requirements from
ParsePushApplePayloadable and instead add them as stored properties and an
initializer on the concrete type ParsePushApplePayload (or create a new opt-in
protocol e.g., ParsePushApplePayloadWithFlags that declares contentAvailable,
mutableContent, and init()), and keep ParsePushApplePayloadable unchanged so
existing conformers continue to compile.

Comment on lines 35 to +51
/// 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

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Fear not, young hero! But this documentation could use MORE POWER!

The static analysis has detected that Line 36 exceeds the 120 character limit (currently 151 characters)! Additionally, several enum cases lack documentation comments which would help fellow heroes understand their purpose!

📝 Proposed documentation and line length fix
     /// The type of notification.
-    /// For more details, see [Apple Documentation](https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns).
+    /// For more details, see Apple's documentation on
+    /// [sending notification requests to APNs](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.
+        /// Send as a location notification.
         case location
+        /// Send as a VoIP notification.
         case voip
+        /// Send as a watchOS complication notification.
         case complication
+        /// Send as a file provider notification.
         case fileprovider
+        /// Send as an MDM notification.
         case mdm
+        /// Send as a Push to Talk notification.
         case pushtotalk
         /// Send as a Live Activity notification.
         case liveactivity
🧰 Tools
🪛 GitHub Check: Codacy Static Code Analysis

[notice] 36-36: Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift#L36
Line should be 120 characters or less; currently it has 151 characters

🤖 Prompt for AI Agents
In `@Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadApple.swift`
around lines 35 - 51, The documentation comment for the PushType enum exceeds
the 120-character limit and several enum cases lack per-case docs; split the
long enum docstring into shorter lines (<=120 chars) and add brief documentation
comments for each case in enum PushType (e.g., alert, background, location,
voip, complication, fileprovider, mdm, pushtotalk, liveactivity) describing
their purpose so each case is documented and the header comment lines stay under
the limit.

Comment on lines +55 to +91
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)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

PLUS ULTRA: Live Activity fields are dropped from coding.
event, priority, collapseId, pushType, and mdm are missing from CodingKeys and decode logic, so they never serialize—risking broken Live Activity pushes. Add keys and decode them (keeping .liveactivity as the default).

🛠️ Proposed fix
 enum CodingKeys: String, CodingKey {
+    case event
+    case priority
+    case collapseId = "collapse_id"
+    case pushType = "push_type"
+    case mdm = "_mdm"
     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(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)
             }
         }
+        event = try values.decodeIfPresent(Event.self, forKey: .event)
+        priority = try values.decodeIfPresent(Int.self, forKey: .priority)
+        collapseId = try values.decodeIfPresent(String.self, forKey: .collapseId)
+        pushType = try values.decodeIfPresent(ParsePushPayloadApple.PushType.self,
+                                              forKey: .pushType) ?? .liveactivity
+        mdm = try values.decodeIfPresent(String.self, forKey: .mdm)
         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)
     }
🤖 Prompt for AI Agents
In
`@Sources/ParseSwift/Types/ParsePushPayload/Apple/ParsePushPayloadAppleLiveActivity.swift`
around lines 55 - 91, The CodingKeys enum and the initializer init(from
decoder:) in ParsePushPayloadAppleLiveActivity are missing keys and decode paths
for event, priority, collapseId, pushType, and mdm; update the CodingKeys enum
to include case event, priority, collapseId = "collapse-id", pushType =
"push-type", and mdm, then in init(from decoder:) decode those fields (using
decodeIfPresent where appropriate) and set defaults: ensure priority defaults to
5 and pushType defaults to .liveactivity if nil; also add decoding for event,
collapseId and mdm to match their stored types so Live Activity fields are
serialized/deserialized correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant