feat(ios): add tvOS, macOS, and watchOS platform support#3145
Conversation
- Add tvOS 15.0, macOS 14.0 to podspec platforms alongside iOS 15 and visionOS - Update Swift @available annotation to include all Apple platforms - Add platform detection helpers: isTVOS(), isMacOS(), isStandardIOS(), isNitroReady() - Skip promoted product listener on tvOS/macOS (not supported) - Register listeners AFTER initConnection to ensure Nitro runtime is ready - Add comprehensive platform detection tests This enables react-native-iap to work on Apple TV and Mac apps in addition to iPhone, iPad, and Vision Pro. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds platform-detection APIs and Nitro readiness checks, delays listener registration until initConnection succeeds, extends native platform availability and podspec targets, adds tests for platform detection and Nitro readiness, and adds internal docs for commit and PR-review workflows. Changes
Sequence DiagramsequenceDiagram
participant App as Application
participant Hook as useIAP / initConnection()
participant Nitro as Nitro Module (HybridObject)
participant Platform as Platform helpers
participant Listeners as Event listeners
App->>Hook: call initConnection()
Hook->>Nitro: lazy-initialize HybridObject / check isNitroReady()
alt Nitro Ready
Nitro-->>Hook: ready
Hook->>Platform: evaluate isTVOS/isMacOS/isStandardIOS
alt Standard iOS
Platform-->>Hook: standard iOS
Hook->>Listeners: register promotedProductListenerIOS
Listeners->>Nitro: addPromotedProductListenerIOS
else tvOS/macOS/other
Platform-->>Hook: non-standard iOS
Hook-->>Listeners: skip promotedProductListener
end
Hook->>Listeners: register purchaseUpdate & purchaseError (+ Android-specific if applicable)
Listeners-->>Hook: listeners registered
Hook-->>App: connected
else Nitro Not Ready
Nitro-->>Hook: not ready
Hook-->>App: warn and exit without listeners
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 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. Comment |
Summary of ChangesHello @hyochan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly expands the library's compatibility by adding official support for tvOS, macOS, and watchOS platforms. It introduces robust platform detection utilities and refines the initialization sequence for the Nitro runtime, ensuring stable operation across all supported Apple environments. These changes enhance the library's versatility and reliability for developers targeting a broader range of Apple devices. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3145 +/- ##
==========================================
+ Coverage 68.18% 68.54% +0.35%
==========================================
Files 9 9
Lines 1751 1777 +26
Branches 580 587 +7
==========================================
+ Hits 1194 1218 +24
- Misses 552 554 +2
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request effectively adds platform support for tvOS, macOS, and watchOS. The changes are well-structured, including updates to the podspec, Swift availability annotations, and the addition of platform detection helpers in TypeScript. A significant improvement is the fix for a race condition by initializing the IAP connection before registering listeners, which is crucial for platforms like tvOS. The new unit tests for platform detection are also a great addition. I have identified a few areas for improvement: a minor formatting issue in a markdown file, a discrepancy in the podspec regarding watchOS support, and an opportunity to refactor a condition in the useIAP hook by using a newly created helper function.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/hooks/useIAP.ts`:
- Around line 386-469: The race condition occurs because listeners
(purchaseUpdatedListener, purchaseErrorListener, promotedProductListenerIOS,
userChoiceBillingListenerAndroid) are registered after awaiting initConnection,
allowing an unmount/cleanup to run before listeners are set and later leak; fix
by tracking a mounted/cancelled ref (e.g., isMountedRef) set true on mount and
false on cleanup and check it immediately after initConnection resolves before
registering any listeners (or bail out and remove any listeners if isMountedRef
is false); also ensure the catch cleanup still clears subscriptionsRef.current
and that connectedRef and optionsRef checks remain correct before invoking
callbacks (use the same isMountedRef guard when attaching listeners and before
calling optionsRef.current callbacks).
🧹 Nitpick comments (3)
NitroIap.podspec (1)
26-27: Inconsistent quoting forvisionosplatform version.All other platform versions are quoted strings (
'15.0','14.0') but:visionosuses a bare numeric1.0. While CocoaPods accepts both, this is inconsistent and could cause confusion.Also, the comment on Line 26 mentions
watchOS 8+, but watchOS is not listed in thes.platformsmap (presumably intentional since React Native doesn't target watchOS). Consider updating the comment to clarify that watchOS is only annotated at the Swift level.Suggested fix
- # Platform versions match OpenIAP requirements: iOS 15+, macOS 14+, tvOS 15+, watchOS 8+ - s.platforms = { :ios => '15.0', :tvos => '15.0', :macos => '14.0', :visionos => 1.0 } + # Platform versions match OpenIAP requirements: iOS 15+, macOS 14+, tvOS 15+, visionOS 1+ + # watchOS 8+ is annotated in Swift `@available` but not as a podspec target (no RN support) + s.platforms = { :ios => '15.0', :tvos => '15.0', :macos => '14.0', :visionos => '1.0' }src/index.ts (1)
133-146:isNitroReady()silently initializes the IAP singleton as a side effect.The function name and JSDoc imply a read-only check, but it creates and caches
iapRefon success. A caller using it as a harmless probe (e.g., in a polling loop or conditional UI) will unknowingly trigger full initialization. Consider documenting this prominently, or renaming to something likeensureNitroReady()to make the side effect explicit.src/__tests__/platform-detection.test.ts (1)
178-178: RedundantmockClear()calls —beforeEachalready runsjest.clearAllMocks().The explicit
mockIap.addPromotedProductListenerIOS.mockClear()calls at lines 178, 192, 210, and 224 are unnecessary sincejest.clearAllMocks()on line 29 already resets all mock state. Not harmful, just noise.Also applies to: 192-192, 210-210, 224-224
- Add watchOS 8.0 to podspec platforms (matches comment and Swift annotation) - Fix markdown table formatting in commit.md - Use isStandardIOS() helper instead of duplicating logic - Add isMountedRef guard to prevent listener leaks on early unmount Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @.claude/commands/commit.md:
- Line 237: Replace the mistakenly bolded markdown string "**tests**/" with the
literal directory name "__tests__/" in the header (currently "### Tests
(src/**tests**/)") so the documentation shows the actual directory path; locate
the header line containing "**tests**/" and change it to "__tests__/".
In `@src/hooks/useIAP.ts`:
- Around line 422-440: The code double-normalizes the error code inside the
purchaseErrorListener callback: purchaseErrorListener already returns a
PurchaseError with a normalized code, so in the callback (where
subscriptionsRef.current.purchaseError is assigned) remove the extra
normalizeErrorCodeFromNative call and use the provided error.code directly when
building mappedError; keep the other fields (message, productId) and existing
init-error ignore check that references connectedRef.current and the
optionsRef.current?.onPurchaseError invocation unchanged.
🧹 Nitpick comments (4)
.claude/commands/commit.md (2)
7-9: Add language specifier to fenced code block.The code block should specify
bashas the language for proper syntax highlighting and to satisfy the markdownlint rule.📝 Proposed fix
-``` +```bash /commit [options]</details> --- `177-188`: **Add language specifiers to fenced code blocks.** The three example commit message code blocks should specify a language (e.g., `text` or `gitcommit`) for proper rendering and to satisfy the markdownlint rule. <details> <summary>📝 Proposed fix</summary> ```diff **Feature addition:** -``` +```text feat(ios): add tvOS and macOS platform support - Add tvOS 15.0 and macOS 14.0 to podspec platforms**Bug fix:** -``` +```text fix(hooks): register listeners after initConnection Move listener registration after initConnection succeeds**Chore/maintenance:** -``` +```text chore(deps): update OpenIAP to 1.3.11 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>Also applies to: 192-200, 204-208
NitroIap.podspec (1)
26-27: Platform versions look correct per OpenIAP/StoreKit 2 requirements.The added platforms (tvOS 15.0, macOS 14.0, watchOS 8.0) align with Apple's StoreKit 2 minimum versions and match the
@availableannotation inHybridRnIap.swift.One minor note:
:visionos => 1.0is a numeric literal while all others are strings ('15.0'). This is pre-existing, but worth harmonizing for consistency if CocoaPods treats them differently.Also, since React Native doesn't officially support watchOS, consider whether declaring
:watchos => '8.0'here could cause confusion for consumers. If it's purely to match the Swift@availableannotation and the underlying OpenIAP dependency, a brief comment clarifying that would help.src/hooks/useIAP.ts (1)
485-499: Cleanup correctly handles unmount; note a subtle edge case for future maintainers.The captured
currentSubscriptionsworks becausesubscriptionsRef.currentis mutated in-place (properties assigned), not replaced with a new object. Combined withisMountedRef, this prevents listener leaks on unmount.One subtlety:
isMountedRefis shared across effect re-runs. IfinitIapWithSubscriptionsever became unstable (causing the effect to re-fire), a stale in-flightinitConnectioncould resolve afterisMountedRef.currentis set back totrueby the new effect invocation, bypassing the guard and potentially double-registering listeners. This isn't an issue today since alluseCallbackdependencies are stable, but a generation counter orAbortControllerwould be more robust if the deps ever change.
- Fix __tests__ directory name formatting in commit.md (was bolded) - Remove double normalization of error code in useIAP hook (purchaseErrorListener already normalizes via normalizeErrorCodeFromNative) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In @.claude/commands/commit.md:
- Around line 7-9: The fenced code block containing "/commit [options]" in
.claude/commands/commit.md should include a language identifier to satisfy
markdownlint MD040 and improve readability; update the fence around the snippet
(the block showing "/commit [options]") to use a language token such as "text"
or "bash" (e.g., replace ``` with ```text) so the linter no longer flags the
fence.
- Around line 192-200: Add a language tag ("text") to the triple-backtick fenced
code block that contains the commit message starting with "fix(hooks): register
listeners after initConnection" in .claude/commands/commit.md so the fence
becomes ```text; update the opening fence only (leave the commit message content
unchanged) to satisfy MD040 and ensure consistent linting/document rendering.
- Around line 177-188: The fenced commit-message example in
.claude/commands/commit.md triggers MD040 because it lacks a language; update
the triple-backtick fence that contains the example commit (the block starting
with "feat(ios): add tvOS and macOS platform support") to include a language
token such as text (e.g., ```text) so the linter stops complaining; ensure any
other similar commit example fences in that file are updated the same way.
- Around line 204-208: Add a language tag ("text") to the fenced commit-example
block in .claude/commands/commit.md so the example fence uses ```text instead of
```; update the specific fenced block that contains the commit message
"chore(deps): update OpenIAP to 1.3.11" to begin with ```text and keep the
content unchanged to satisfy MD040 linting.
Add 'text' language tag to commit message example blocks for markdownlint MD040 compliance. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for tvOS, macOS, and watchOS platforms, which is a significant enhancement. The changes include updating the podspec and Swift availability annotations, adding robust platform detection helpers in TypeScript, and refining the Nitro initialization and listener registration logic within the useIAP hook. The new platform detection tests are well-structured and ensure the correctness of the new helpers. The adjustment to listener registration after initConnection is a critical fix for potential race conditions, especially on non-iOS platforms like tvOS, and the inclusion of isMountedRef for cleanup is a good practice for React hooks. Overall, the changes are well-implemented and contribute positively to the library's cross-platform capabilities and stability.
Summary
Closes #3141 #3143
Changes
Podspec (NitroIap.podspec)
:tvos => '15.0'and:macos => '14.0'platforms alongside existing iOS 15 and visionOSiOS Native (ios/HybridRnIap.swift)
@availableannotation to include all Apple platforms: iOS 15.0, macOS 14.0, tvOS 15.0, watchOS 8.0TypeScript (src/index.ts)
isTVOS()- Detects tvOS (Platform.OS='ios' with isTV=true)isMacOS()- Detects macOS and Mac CatalystisStandardIOS()- Detects iPhone/iPad (not tvOS or Mac Catalyst)isNitroReady()- Check if Nitro runtime is initializedpromotedProductListenerIOSon tvOS/macOS (promoted products not supported)Hooks (src/hooks/useIAP.ts)
initConnectionsucceeds instead of beforeTests (src/tests/)
platform-detection.test.tsindex.test.tsTest plan
yarn typecheckpassesyarn lintpassesyarn testpasses (236 tests)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores