-
-
Notifications
You must be signed in to change notification settings - Fork 648
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Fix] - Store Kit 1 race condition (#2604)
**Fixes:** #1696, #2372 Similar to #2518, this fixes race condition crashes on `validProducts` and `productsRequest` in `RNIapIos.swift` when using Store Kit 1. ## React Native IAP ### What was done: - **Created `ThreadSafe` class:** This now wraps `validProducts`. - **Created `LatestPromiseKeeper`:** - Maintains only the latest promise to fix racing conditions when multiple `productsRequest` are made in parallel. - Resolves only one promise at any given time (for `productsRequest`), eliminating the possibility of racing conditions or execution exceptions while storing the resolved data. - Discards older promises if more than one is made, returning a rejection for the discarded promises. ## IAPExample (iOS) - Fixed target iOS deployment being loaded from an undefined variable. It now fetches the project's target iOS from the Xcode project and applies it during pod install. - Updated GitHub Action to use Node.js 20. - Changed GitHub Action runner from M1 architecture to Intel-based macOS (fixes crash during build).
- Loading branch information
1 parent
4fdae65
commit 0a4236c
Showing
8 changed files
with
130 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
# | ||
.DS_Store | ||
|
||
.yarn* | ||
# XDE | ||
# | ||
.expo/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import StoreKit | ||
|
||
// Only keeps latest promise, assumes older promises are not needed | ||
// Avoids racing conditions by storing latestPromise in a thread safe var | ||
// Cancels previous promises when new ones are added | ||
// Should not be used when all promises are relevant (e.g. Purchases) | ||
class LatestPromiseKeeper { | ||
private var latestPromise: ThreadSafe<(RCTPromiseResolveBlock, RCTPromiseRejectBlock)?> = ThreadSafe(nil) | ||
private var latestRequest: ThreadSafe<SKProductsRequest?> = ThreadSafe(nil) | ||
|
||
func setLatestPromise(request: SKProductsRequest, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { | ||
// Cancel the ongoing request and reject the existing promise before setting a new one | ||
cancelOngoingRequest() | ||
|
||
latestRequest.atomically { $0 = request } | ||
latestPromise.atomically { $0 = (resolve, reject) } | ||
} | ||
|
||
func cancelOngoingRequest() { | ||
latestPromise.atomically { promiseResolvers in | ||
if let (_, reject) = promiseResolvers { | ||
// Reject the promise with an error indicating that it was cancelled due to a new request | ||
reject("E_CANCELED", "Previous request was cancelled due to a new request", nil) | ||
} | ||
} | ||
|
||
latestRequest.atomically { ongoingRequest in | ||
ongoingRequest?.cancel() | ||
ongoingRequest = nil | ||
} | ||
|
||
// Clear the latestPromise after rejecting it | ||
latestPromise.atomically { $0 = nil } | ||
} | ||
|
||
func resolveIfRequestMatches(matchingRequest: SKProductsRequest, items: [[String: Any?]], operation: (RCTPromiseResolveBlock, [[String: Any?]]) -> Void) { | ||
latestPromise.atomically { promiseResolvers in | ||
guard let (resolve, _) = promiseResolvers else { return } | ||
|
||
latestRequest.atomically { ongoingRequest in | ||
guard ongoingRequest === matchingRequest else { return } | ||
|
||
operation(resolve, items) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.