-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add Live Activity support for iOS #23
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
Conversation
Add native iOS module and JS service to listen for LiveActivity push-to-start tokens and register them with the Clix API. - Add ClixLiveActivityModule.swift for iOS native token listening - Add ClixLiveActivityModule.m for React Native bridge - Add ClixLiveActivityBridge.ts for JS event subscription - Add LiveActivityAPIService.ts for API calls - Add LiveActivityService.ts for token handling - Initialize LiveActivityService in Clix.initialize() Co-Authored-By: Claude <noreply@anthropic.com>
…loyment target to 15.6
- Add ClixLiveActivity public wrapper class for SDK users - Add sample app LiveActivity widget extension - Add DeliveryActivityAttributes and LiveActivityManager - Configure AppDelegate to call ClixLiveActivitySetup Co-Authored-By: Claude <noreply@anthropic.com>
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds iOS Live Activity support: a public ClixLiveActivity helper and React Native bridge for push‑to‑start tokens, sample Delivery Live Activity widget and manager with app wiring, JS services to forward tokens to the backend, and a package bump to 1.3.0. Changes
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
✏️ Tip: You can disable this entire section by setting Comment |
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@ios/ClixLiveActivityModule.swift`:
- Line 92: The log statement print("[Clix] Received pushToStartToken for
\(activityTypeName): \(token)") exposes sensitive push-to-start tokens; either
remove the token from logs or limit logging to non-production builds. Replace
that print with a redacted message (e.g. show only first 4–6 chars of token) or
wrap it in a compile-time check (`#if` DEBUG) so it only logs in debug builds,
referencing the existing print call in ClixLiveActivityModule.swift to locate
and update the statement.
In `@src/core/Clix.ts`:
- Around line 76-82: The Clix initialization currently starts
liveActivityService before ensuring device registration and never calls
liveActivityService.cleanup(); fix by reordering and adding cleanup: initialize
device first by awaiting this.shared.deviceService.initialize() (or ensure
deviceService.registers with the API / getCurrentDeviceId completes) before
calling this.shared.liveActivityService.initialize(), and add a matching
teardown that calls this.shared.liveActivityService.cleanup() (e.g., in
Clix.dispose/stop/teardown or the method that reverses
this.shared.storageService.set) so liveActivityService subscriptions are removed
when the SDK is torn down; reference this.shared.deviceService.initialize(),
this.shared.liveActivityService.initialize(), and
this.shared.liveActivityService.cleanup() when making changes.
🧹 Nitpick comments (7)
samples/BasicApp/ios/DeliveryActivityAttributes.swift (1)
4-12: Consider access level for cross-target usage.The
DeliveryActivityAttributesstruct has internal (default) access, but it's used by both the main app and the widget extension target. If both targets compile this file (shared source), internal access works. Otherwise, you may need to make the structpublicfor cross-module visibility.Also, consider using
letinstead ofvarfor immutable properties in both the outer struct andContentState, since these values typically don't change after initialization.Suggested improvement
-struct DeliveryActivityAttributes: ActivityAttributes { - public struct ContentState: Codable, Hashable { - var status: String - var estimatedDeliveryTime: String +public struct DeliveryActivityAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + public let status: String + public let estimatedDeliveryTime: String } - var orderNumber: String - var restaurantName: String + public let orderNumber: String + public let restaurantName: String }src/services/LiveActivityService.ts (1)
25-32: Consider logging when subscription is unavailable.When
subscribeToPushToStartTokenreturnsundefined(e.g., on Android or when the native module is unavailable), the service silently remains uninitialized. Adding a debug log for this case would improve debuggability.♻️ Suggested improvement
this.subscription = subscribeToPushToStartToken( this.handlePushToStartToken.bind(this) ); if (this.subscription) { this.isInitialized = true; ClixLogger.debug('LiveActivityService initialized successfully'); + } else { + ClixLogger.debug('LiveActivityService not initialized - subscription unavailable'); }src/services/LiveActivityAPIService.ts (1)
17-23: Consider URL-encoding the deviceId for defense in depth.While
deviceIdis typically a UUID generated byDeviceService, encoding it prevents potential path traversal issues if the ID source ever changes.♻️ Suggested improvement
const response = await this.apiClient.post( - `/devices/${deviceId}/live-activity-start-tokens`, + `/devices/${encodeURIComponent(deviceId)}/live-activity-start-tokens`, { attributes_type: activityType, push_to_start_token: token, } );samples/BasicApp/ios/DeliveryLiveActivityWidget/DeliveryLiveActivityWidgetLiveActivity.swift (1)
75-86: Consider using constants or an enum for status strings.The status strings ("Preparing", "On the way", "Delivered") are duplicated between this widget and
LiveActivityManager.swift. Extracting them to a shared enum would prevent typos and improve maintainability.♻️ Example enum definition
// Could be added to DeliveryActivityAttributes.swift enum DeliveryStatus: String { case preparing = "Preparing" case onTheWay = "On the way" case delivered = "Delivered" }samples/BasicApp/ios/LiveActivityManager.swift (1)
67-74: RedundantMainActor.runinside Task on a@MainActorclass.Since
LiveActivityManageris annotated with@MainActor, theTaskinherits the actor context. The explicitMainActor.runblock is unnecessary.♻️ Simplified implementation
func end() { guard let activity = currentActivity else { return } Task { await activity.end(nil, dismissalPolicy: .immediate) - await MainActor.run { - currentActivity = nil - isActive = false - } + currentActivity = nil + isActive = false print("[Clix] Live Activity ended") } }samples/BasicApp/ios/ClixSample.xcodeproj/project.pbxproj (1)
728-728: Unusually high deployment target (iOS 18.5) for widget extension.The
DeliveryLiveActivityWidgetExtensionrequires iOS 18.5, but Live Activities are available from iOS 16.1 and push-to-start tokens from iOS 17.2. This significantly limits user reach. Consider lowering to iOS 17.2 or 16.2 (when Dynamic Island was introduced on more devices).Suggested change for both Debug and Release configurations
- IPHONEOS_DEPLOYMENT_TARGET = 18.5; + IPHONEOS_DEPLOYMENT_TARGET = 17.2;Also applies to: 773-773
ios/ClixLiveActivityModule.swift (1)
89-98: Task lifecycle not managed—potential memory leak and no cancellation mechanism.The
Taskcreated instartListening()is not stored, so there's no way to cancel it. If the module is deallocated or you need to stop listening, the task continues running. Additionally,selfis captured strongly without[weak self], which could keep the module alive.Suggested fix to store and manage Task lifecycle
+ private var tokenUpdateTasks: [String: Task<Void, Never>] = [:] + `@available`(iOS 17.2, *) private func startListening<Attributes: ActivityAttributes>( _ activityType: Attributes.Type, activityTypeName: String ) { guard !activeListeners.contains(activityTypeName) else { print("[Clix] Already listening for pushToStartToken: \(activityTypeName)") return } activeListeners.insert(activityTypeName) print("[Clix] Starting pushToStartToken listener: \(activityTypeName)") - Task { + let task = Task { [weak self] in + guard let self = self else { return } for await tokenData in Activity<Attributes>.pushToStartTokenUpdates { let token = tokenData.map { String(format: "%02x", $0) }.joined() print("[Clix] Received pushToStartToken for \(activityTypeName): \(token)") await MainActor.run { self.emitToken(activityType: activityTypeName, token: token) } } } + tokenUpdateTasks[activityTypeName] = task }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
ios/ClixLiveActivity.swiftios/ClixLiveActivityModule.mios/ClixLiveActivityModule.swiftsamples/BasicApp/ios/ClixLiveActivitySetup.swiftsamples/BasicApp/ios/ClixSample-Bridging-Header.hsamples/BasicApp/ios/ClixSample.xcodeproj/project.pbxprojsamples/BasicApp/ios/ClixSample/AppDelegate.mmsamples/BasicApp/ios/ClixSample/Info.plistsamples/BasicApp/ios/DeliveryActivityAttributes.swiftsamples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/AccentColor.colorset/Contents.jsonsamples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/AppIcon.appiconset/Contents.jsonsamples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/Contents.jsonsamples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/WidgetBackground.colorset/Contents.jsonsamples/BasicApp/ios/DeliveryLiveActivityWidget/DeliveryLiveActivityWidgetBundle.swiftsamples/BasicApp/ios/DeliveryLiveActivityWidget/DeliveryLiveActivityWidgetLiveActivity.swiftsamples/BasicApp/ios/DeliveryLiveActivityWidget/Info.plistsamples/BasicApp/ios/DeliveryLiveActivityWidgetExtension.entitlementssamples/BasicApp/ios/LiveActivityManager.swiftsrc/core/Clix.tssrc/native/ClixLiveActivityBridge.tssrc/services/LiveActivityAPIService.tssrc/services/LiveActivityService.ts
🧰 Additional context used
🧬 Code graph analysis (8)
ios/ClixLiveActivity.swift (2)
ios/ClixLiveActivityModule.swift (2)
iOS(55-74)iOS(76-99)samples/BasicApp/ios/ClixLiveActivitySetup.swift (1)
setup(5-9)
samples/BasicApp/ios/LiveActivityManager.swift (2)
ios/ClixLiveActivity.swift (1)
iOS(13-16)ios/ClixLiveActivityModule.swift (2)
iOS(55-74)iOS(76-99)
src/core/Clix.ts (2)
src/services/LiveActivityService.ts (1)
LiveActivityService(10-68)src/services/LiveActivityAPIService.ts (1)
LiveActivityAPIService(4-42)
src/services/LiveActivityService.ts (4)
src/services/DeviceService.ts (1)
DeviceService(12-185)src/services/LiveActivityAPIService.ts (1)
LiveActivityAPIService(4-42)src/utils/logging/ClixLogger.ts (2)
ClixLogger(9-61)error(46-48)src/native/ClixLiveActivityBridge.ts (2)
subscribeToPushToStartToken(50-60)PushToStartTokenEvent(11-14)
src/native/ClixLiveActivityBridge.ts (1)
src/utils/logging/ClixLogger.ts (1)
ClixLogger(9-61)
src/services/LiveActivityAPIService.ts (2)
src/services/ClixAPIClient.ts (1)
ClixAPIClient(13-188)src/utils/logging/ClixLogger.ts (2)
ClixLogger(9-61)error(46-48)
ios/ClixLiveActivityModule.swift (2)
ios/ClixLiveActivity.swift (1)
iOS(13-16)samples/BasicApp/ios/ClixLiveActivitySetup.swift (1)
setup(5-9)
samples/BasicApp/ios/ClixLiveActivitySetup.swift (2)
ios/ClixLiveActivity.swift (1)
iOS(13-16)ios/ClixLiveActivityModule.swift (2)
iOS(55-74)iOS(76-99)
🔇 Additional comments (28)
samples/BasicApp/ios/DeliveryLiveActivityWidgetExtension.entitlements (1)
1-5: Verify if App Groups entitlement is needed.The entitlements file is empty. For Live Activity widgets that need to share data with the main app (e.g., delivery status updates), an App Groups entitlement is typically required:
<key>com.apple.security.application-groups</key> <array> <string>group.$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array>If the widget only receives updates via push notifications and doesn't need shared UserDefaults or file containers, the empty entitlements file is acceptable.
samples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/Contents.json (1)
1-6: LGTM!Standard Xcode-generated asset catalog metadata file.
samples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/AccentColor.colorset/Contents.json (1)
1-11: LGTM!The accent color set uses the system default by not specifying explicit color components. This is fine for a sample app; consider defining explicit RGBA values if custom branding is needed later.
samples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json (1)
1-11: LGTM!Standard widget background color set using system defaults. Works well with Dynamic Island and Lock Screen presentations.
samples/BasicApp/ios/ClixSample/Info.plist (1)
40-41: LGTM!Correct configuration to enable Live Activities. The existing
remote-notificationbackground mode (line 45) is already present, which is required for push-to-start token delivery.samples/BasicApp/ios/DeliveryLiveActivityWidget/Assets.xcassets/AppIcon.appiconset/Contents.json (1)
1-35: Standard asset catalog structure, but no icon images linked yet.The JSON structure is valid for an iOS asset catalog with dark/tinted appearance support. Note that no
"filename"keys are present, meaning no actual icon images are linked. For a sample app widget extension, this is acceptable as placeholder configuration, but actual icon images should be added before production release.samples/BasicApp/ios/ClixSample-Bridging-Header.h (1)
1-12: Standard bridging header scaffold.This is a standard empty Objective-C bridging header, which is required by Xcode for Swift/Objective-C interoperability configuration even when no Objective-C headers need to be imported into Swift. The structure is correct.
samples/BasicApp/ios/DeliveryLiveActivityWidget/Info.plist (1)
1-11: Correct minimal Info.plist for WidgetKit extension.The extension point identifier
com.apple.widgetkit-extensionis correct for Live Activity widgets. This minimal configuration is sufficient for the extension to function properly.samples/BasicApp/ios/ClixSample/AppDelegate.mm (1)
6-6: LGTM - Live Activity setup integration looks correct.The Swift bridging header import and
ClixLiveActivitySetup.setup()call are properly placed. Calling setup early in the app lifecycle (after Firebase configuration) ensures push-to-start tokens are registered promptly. The setup method correctly includes the@available(iOS 16.1, *)check to guard the Live Activity registration.ios/ClixLiveActivityModule.m (1)
1-5: LGTM!Standard React Native bridge module export for a Swift
RCTEventEmittersubclass. The declaration correctly inherits fromRCTEventEmitterto enable event emission to JavaScript.samples/BasicApp/ios/DeliveryLiveActivityWidget/DeliveryLiveActivityWidgetBundle.swift (1)
1-9: LGTM!Standard WidgetKit bundle structure with the correct
@mainentry point. This properly references theDeliveryLiveActivityWidgetdefined in the companion file.src/core/Clix.ts (2)
6-7: LGTM!Import additions follow the existing pattern for service imports.
33-33: LGTM!The optional field declaration follows the existing pattern for other services.
samples/BasicApp/ios/ClixLiveActivitySetup.swift (1)
4-9: LGTM!Clean Objective-C-compatible wrapper for AppDelegate integration. The
@available(iOS 16.1, *)check here complements the internaliOS 17.2+guard inClixLiveActivityModule.setup()for push-to-start token functionality, allowing graceful degradation across iOS versions.ios/ClixLiveActivity.swift (1)
1-17: LGTM!Well-structured public API with:
- Proper conditional import of
ActivityKitusing#if canImport- Appropriate
@available(iOS 16.1, *)guard- Clean delegation to the internal module
- Good documentation for SDK consumers
src/services/LiveActivityService.ts (1)
42-67: LGTM!The async event handler correctly retrieves the device ID, forwards the token to the API service, and has appropriate error handling that logs failures without propagating exceptions to the event emitter.
src/native/ClixLiveActivityBridge.ts (2)
34-48: Module-level singleton may not reset if native module state changes.The
eventEmitteris cached at module level. If the module is initially unavailable but becomes available later (unlikely but possible in hot-reload scenarios), the cachedundefinedcheck won't re-resolve. This is likely acceptable for production use but worth noting.
50-59: LGTM!The subscription function correctly handles the case where the emitter is unavailable, returning
undefinedto signal the caller that subscription wasn't established. The iOS-only behavior is appropriate for this Live Activity feature.src/services/LiveActivityAPIService.ts (1)
25-40: LGTM!The manual status code validation is necessary given the API client design. Error handling correctly logs context and propagates the error to the caller for appropriate handling.
samples/BasicApp/ios/DeliveryLiveActivityWidget/DeliveryLiveActivityWidgetLiveActivity.swift (1)
5-73: LGTM!The widget implementation properly handles both lock screen and Dynamic Island presentations with appropriate layouts for expanded, compact, and minimal modes. The use of system colors and fonts ensures good accessibility and platform consistency.
samples/BasicApp/ios/LiveActivityManager.swift (2)
14-36: LGTM!The
start()method correctly requests a Live Activity withpushType: .tokenfor push-to-start support. Error handling is appropriate for sample code, and the state management properly tracks the current activity.
4-6: Verify iOS version requirement for push-to-start tokens.The class is marked
@available(iOS 16.2, *), and whileActivity.request(pushType: .token)is available from iOS 16.1+, receiving push-to-start tokens viapushToStartTokenUpdatesrequires iOS 17.2+ (as noted inClixLiveActivityModule.swift). This is fine for sample code, but document this limitation for users.samples/BasicApp/ios/ClixSample.xcodeproj/project.pbxproj (3)
17-27: New widget extension target with frameworks wired correctly.The addition of WidgetKit.framework, SwiftUI.framework, and the DeliveryLiveActivityWidgetExtension.appex product references is properly configured for Live Activity support.
285-304: Widget extension native target configuration looks complete.The PBXNativeTarget for
DeliveryLiveActivityWidgetExtensioncorrectly includes Sources, Frameworks, and Resources build phases, and sharesDeliveryActivityAttributes.swiftwith the main app target (line 540), which is necessary for Live Activity data sharing between the app and widget.Also applies to: 536-543
571-571: Deployment target update to iOS 15.6 is appropriate.The main app's deployment target increase from 14 to 15.6 aligns with the SDK's Live Activity requirements (iOS 16.1+ for Live Activities), though 15.6 itself doesn't include Live Activities—runtime checks handle availability.
Also applies to: 601-601
ios/ClixLiveActivityModule.swift (3)
36-46: Potential race condition when flushing pending tokens.If
emitToken()is called from the async Task whilestartObserving()is iterating and clearingpendingTokens, tokens could be lost. SincestartObserving()runs on the main thread andemitToken()is dispatched viaMainActor.run, this should be safe in practice, but the lack of explicit synchronization makes this fragile.Verify that all paths to
emitToken()andstartObserving()are guaranteed to execute on the main thread. If not, consider adding explicit synchronization.
52-74: Setup method correctly handles deferred initialization.The pattern of queueing setups when
sharedInstanceis nil and executing them upon initialization is a reasonable approach for handling the timing mismatch between native module creation and app startup calls.
1-9: React Native bridge structure is correctly implemented.The module properly inherits from
RCTEventEmitter, implementsrequiresMainQueueSetup(),supportedEvents(), and the observer lifecycle methods. The event emission pattern with pending token buffering is a good approach for handling the JS listener registration timing.Also applies to: 28-34, 102-113
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be3feb09b6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Co-Authored-By: Claude <noreply@anthropic.com>
Summary
Adds iOS Live Activity support to Clix React Native SDK with push-to-start token management. Includes native Swift module, TypeScript bridge, and sample delivery tracking widget.
Changes
Test Plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.