Skip to content

Commit

Permalink
feat: add visionOS support (#2644)
Browse files Browse the repository at this point in the history
This PR adds visionOS support. 

I've refactored some parts of the code to take the currently presented
scene by wrapping built-in utility `RCTKeyWindow()` to work in async
scenarios.

## Testing

1. `npx @callstack/react-native-visionos init App`
2. Add react-native-iap
3. Check out if it works

## Demo

https://github.com/dooboolab-community/react-native-iap/assets/52801365/c2391f3a-c8b9-4fd8-acba-e2871c7aaae7
  • Loading branch information
okwasniewski authored Jun 12, 2024
1 parent 8f6455b commit aff3726
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 17 deletions.
2 changes: 1 addition & 1 deletion RNIap.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Pod::Spec.new do |s|
s.license = package["license"]
s.authors = package["author"]

s.platforms = { :ios => "10.0" , :tvos => "10.0"}
s.platforms = { :ios => "10.0" , :tvos => "10.0", :visionos => "1.0" }
s.source = { :git => "https://github.com/dooboolab-community/react-native-iap.git", :tag => "#{s.version}" }

s.source_files = "ios/*.{h,m,mm,swift}"
Expand Down
2 changes: 2 additions & 0 deletions ios/IapSerializationUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,12 @@ func serialize(_ t: Transaction) -> [String: Any?] {
if #available(iOS 16.0, tvOS 16.0, *) {
environment = t.environment.rawValue
} else {
#if !os(visionOS)
let env = t.environmentStringRepresentation
if ["Production", "Sandbox", "Xcode"].contains(env) {
environment = env
}
#endif
}

return ["appAccountToken": t.appAccountToken?.uuidString,
Expand Down
10 changes: 10 additions & 0 deletions ios/IapUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import StoreKit
import React

public func debugMessage(_ object: Any...) {
#if DEBUG
Expand All @@ -28,3 +29,12 @@ func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
return safe
}
}

@available(iOS 15.0, *)
func currentWindow() async -> UIWindow? {
await withCheckedContinuation { continuation in
DispatchQueue.main.async {
continuation.resume(returning: RCTKeyWindow())
}
}
}
5 changes: 1 addition & 4 deletions ios/RNIapIosSk2.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
#import <React/RCTBridgeModule.h>
#ifdef __IPHONE_15_0

// From: https://stackoverflow.com/a/5337804/570612
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)


@interface RCT_EXTERN_MODULE (RNIapIosSk2, NSObject)

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isAvailable){
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"15.0")) {
if (@available(iOS 15.0, *)) {
return [NSNumber numberWithInt:1];
}else{
return [NSNumber numberWithInt:0];
Expand Down
37 changes: 25 additions & 12 deletions ios/RNIapIosSk2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,20 @@ class RNIapIosSk2iOS15: Sk2Delegate {
}
debugMessage("Purchase Started")

let result = try await product.purchase(options: options)
guard let windowScene = await currentWindow()?.windowScene else {
reject(IapErrors.E_DEVELOPER_ERROR.rawValue, "Could not find window scene", nil)
return
}

var result: Product.PurchaseResult?

if #available(iOS 17.0, tvOS 17.0, *) {
result = try await product.purchase(confirmIn: windowScene, options: options)
} else {
#if !os(visionOS)
result = try await product.purchase(options: options)
#endif
}
switch result {
case .success(let verification):
debugMessage("Purchase Successful")
Expand Down Expand Up @@ -947,22 +960,20 @@ class RNIapIosSk2iOS15: Sk2Delegate {
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
) {
#if !os(tvOS)
DispatchQueue.main.async {
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
Task {
guard let scene = await currentWindow()?.windowScene as? UIWindowScene,
!ProcessInfo.processInfo.isiOSAppOnMac else {
return
}

Task {
do {
try await AppStore.showManageSubscriptions(in: scene)
} catch {
print("Error:(error)")
}
do {
try await AppStore.showManageSubscriptions(in: scene)
} catch {
print("Error:(error)")
}

resolve(nil)
}

resolve(nil)
#else
reject(IapErrors.E_USER_CANCELLED.rawValue, "This method is not available on tvOS", nil)
#endif
Expand Down Expand Up @@ -998,8 +1009,10 @@ class RNIapIosSk2iOS15: Sk2Delegate {
return nil
}
}

Task {
if let windowScene = await UIApplication.shared.keyWindow?.windowScene {
let window = await currentWindow()
if let windowScene = await window?.windowScene {
if let product = await productStore.getProduct(productID: sku) {
if let result = await product.latestTransaction {
do {
Expand Down

0 comments on commit aff3726

Please sign in to comment.