react-native-iap 16.4.0 → 16.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +200 -142
- package/android/src/main/java/com/margelo/nitro/iap/TokenizedListenerRegistry.kt +0 -1
- package/ios/HybridRnIap.swift +832 -165
- package/ios/RnIapHelper.swift +1 -1
- package/lib/module/hooks/useIAP.js +48 -32
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +206 -152
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.kepler.js +1 -0
- package/lib/module/index.kepler.js.map +1 -1
- package/lib/module/types.js +1 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/error.js +5 -4
- package/lib/module/utils/error.js.map +1 -1
- package/lib/module/vega-adapter.js +4 -1
- package/lib/module/vega-adapter.js.map +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +31 -15
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +28 -14
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.kepler.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +10 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +11 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/error.d.ts.map +1 -1
- package/lib/typescript/src/vega-adapter.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JNitroPurchaseRequest.hpp +7 -1
- package/nitrogen/generated/android/c++/JNitroPurchaseRequestType.hpp +58 -0
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitHorizonProps.hpp +66 -0
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitProps.hpp +8 -1
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithProviderProps.hpp +3 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.hpp +74 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitProps.hpp +3 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseRequest.kt +8 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseRequestType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitHorizonProps.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitProps.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.kt +62 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +65 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +6 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +6 -0
- package/nitrogen/generated/ios/swift/NitroPurchaseRequest.swift +13 -2
- package/nitrogen/generated/ios/swift/NitroPurchaseRequestType.swift +40 -0
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitHorizonProps.swift +66 -0
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitProps.swift +38 -1
- package/nitrogen/generated/ios/swift/Variant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.swift +30 -0
- package/nitrogen/generated/shared/c++/NitroPurchaseRequest.hpp +9 -2
- package/nitrogen/generated/shared/c++/NitroPurchaseRequestType.hpp +76 -0
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitHorizonProps.hpp +90 -0
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitProps.hpp +8 -1
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/src/hooks/useIAP.ts +96 -58
- package/src/index.kepler.ts +1 -0
- package/src/index.ts +248 -195
- package/src/specs/RnIap.nitro.ts +12 -0
- package/src/types.ts +12 -2
- package/src/utils/error.ts +7 -3
- package/src/vega-adapter.ts +9 -5
package/ios/HybridRnIap.swift
CHANGED
|
@@ -2,8 +2,54 @@ import Foundation
|
|
|
2
2
|
import NitroModules
|
|
3
3
|
import OpenIAP
|
|
4
4
|
|
|
5
|
+
private final class PendingEventBuffer<Event> {
|
|
6
|
+
private let capacity: Int
|
|
7
|
+
private let label: String
|
|
8
|
+
private var events: [Event] = []
|
|
9
|
+
private var isFlushing = false
|
|
10
|
+
|
|
11
|
+
init(capacity: Int, label: String) {
|
|
12
|
+
precondition(capacity > 0)
|
|
13
|
+
self.capacity = capacity
|
|
14
|
+
self.label = label
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func enqueueIfNeeded(hasListeners: Bool, event: Event) -> Bool {
|
|
18
|
+
guard !hasListeners || isFlushing else { return false }
|
|
19
|
+
if events.count >= capacity {
|
|
20
|
+
events.removeFirst()
|
|
21
|
+
RnIapLog.warn("\(label) overflow; dropping oldest")
|
|
22
|
+
}
|
|
23
|
+
events.append(event)
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func beginFlushIfNeeded() -> Bool {
|
|
28
|
+
guard !isFlushing, !events.isEmpty else { return false }
|
|
29
|
+
isFlushing = true
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func takeBatchOrFinish(hasListeners: Bool) -> [Event]? {
|
|
34
|
+
guard hasListeners, !events.isEmpty else {
|
|
35
|
+
isFlushing = false
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
let batch = events
|
|
39
|
+
events.removeAll()
|
|
40
|
+
return batch
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func clear() {
|
|
44
|
+
events.removeAll()
|
|
45
|
+
isFlushing = false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
5
49
|
@available(iOS 15.0, macOS 14.0, tvOS 15.0, watchOS 8.0, *)
|
|
6
50
|
class HybridRnIap: HybridRnIapSpec {
|
|
51
|
+
private static let maxPendingEvents = 200
|
|
52
|
+
|
|
7
53
|
private enum PurchaseUpdatedListenerBucket {
|
|
8
54
|
case deduping
|
|
9
55
|
case nonDeduping
|
|
@@ -35,6 +81,22 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
35
81
|
private var lastPurchaseErrorKey: String? = nil
|
|
36
82
|
private var lastPurchaseErrorTimestamp: TimeInterval = 0
|
|
37
83
|
private var purchasePayloadById: [String: [String: Any]] = [:]
|
|
84
|
+
private var deliveredPurchaseUpdateIds = Set<String>()
|
|
85
|
+
private var pendingDuplicatePurchaseUpdateSuppressions: [String: Int] = [:]
|
|
86
|
+
private var pendingRequestPurchaseErrorSuppressions: [String: Int] = [:]
|
|
87
|
+
private var pendingOnDemandInitErrorSuppressions: [String: Int] = [:]
|
|
88
|
+
private let pendingPurchaseUpdates = PendingEventBuffer<NitroPurchase>(
|
|
89
|
+
capacity: HybridRnIap.maxPendingEvents,
|
|
90
|
+
label: "pendingPurchaseUpdates"
|
|
91
|
+
)
|
|
92
|
+
private let pendingDuplicatePurchaseUpdates = PendingEventBuffer<NitroPurchase>(
|
|
93
|
+
capacity: HybridRnIap.maxPendingEvents,
|
|
94
|
+
label: "pendingDuplicatePurchaseUpdates"
|
|
95
|
+
)
|
|
96
|
+
private let pendingPurchaseErrors = PendingEventBuffer<NitroPurchaseResult>(
|
|
97
|
+
capacity: HybridRnIap.maxPendingEvents,
|
|
98
|
+
label: "pendingPurchaseErrors"
|
|
99
|
+
)
|
|
38
100
|
// Thread safety lock for listener arrays and error dedup state
|
|
39
101
|
private let listenerLock = NSLock()
|
|
40
102
|
private let lifecycleLock = NSLock()
|
|
@@ -51,22 +113,54 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
51
113
|
|
|
52
114
|
|
|
53
115
|
func initConnection(config: Variant_NullType_InitConnectionConfig?) throws -> Promise<Bool> {
|
|
54
|
-
let
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
116
|
+
let configValue: InitConnectionConfig? = {
|
|
117
|
+
if case .second(let c) = config { return c }
|
|
118
|
+
return nil
|
|
119
|
+
}()
|
|
120
|
+
let operation = enqueueConnect(configValue)
|
|
121
|
+
return Promise.async { try await operation.value }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// StoreKit initialization still owns caches and observers despite having no persistent connection.
|
|
125
|
+
private func ensureConnection() async throws {
|
|
126
|
+
if isConnectionInitialized() { return }
|
|
127
|
+
_ = try await enqueueConnect(nil, reuseExistingConnection: true).value
|
|
128
|
+
// Never run a store operation without listeners installed by the same lifecycle path.
|
|
129
|
+
guard isConnectionInitialized() else {
|
|
130
|
+
throw OpenIapException.make(code: .initConnection, message: "Connection not initialized. Call initConnection() first.")
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func enqueueConnect(
|
|
135
|
+
_ configValue: InitConnectionConfig?,
|
|
136
|
+
reuseExistingConnection: Bool = false,
|
|
137
|
+
connect: (() async throws -> Bool)? = nil
|
|
138
|
+
) -> Task<Bool, Error> {
|
|
139
|
+
enqueueLifecycleOperation {
|
|
140
|
+
if reuseExistingConnection, self.isConnectionInitialized() { return true }
|
|
59
141
|
RnIapLog.payload("initConnection", configValue)
|
|
60
142
|
let epoch = self.listenerLock.withLock { self.connectionEpoch }
|
|
61
143
|
|
|
62
144
|
do {
|
|
63
145
|
// Note: iOS doesn't support alternative billing config parameter
|
|
64
146
|
// Config is ignored on iOS platform
|
|
65
|
-
|
|
147
|
+
self.attachCoreListenersIfNeeded(epoch: epoch)
|
|
148
|
+
let ok: Bool
|
|
149
|
+
if let connect {
|
|
150
|
+
ok = try await connect()
|
|
151
|
+
} else {
|
|
152
|
+
ok = try await OpenIapModule.shared.initConnection()
|
|
153
|
+
}
|
|
66
154
|
RnIapLog.result("initConnection", ok)
|
|
67
155
|
let isCurrent = self.listenerLock.withLock {
|
|
68
156
|
guard self.connectionEpoch == epoch else { return false }
|
|
69
157
|
self.isInitialized = ok
|
|
158
|
+
if reuseExistingConnection, !ok {
|
|
159
|
+
self.pendingOnDemandInitErrorSuppressions[
|
|
160
|
+
ErrorCode.iapNotAvailable.rawValue,
|
|
161
|
+
default: 0
|
|
162
|
+
] += 1
|
|
163
|
+
}
|
|
70
164
|
return true
|
|
71
165
|
}
|
|
72
166
|
guard isCurrent else { return false }
|
|
@@ -80,7 +174,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
80
174
|
self.isInitialized = false
|
|
81
175
|
return true
|
|
82
176
|
}
|
|
83
|
-
if isCurrent {
|
|
177
|
+
if isCurrent, !reuseExistingConnection {
|
|
84
178
|
RnIapLog.failure("initConnection", error: error)
|
|
85
179
|
let err = RnIapHelper.makePurchaseErrorResult(
|
|
86
180
|
code: .initConnection,
|
|
@@ -91,28 +185,47 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
91
185
|
return false
|
|
92
186
|
}
|
|
93
187
|
}
|
|
94
|
-
return Promise.async { try await operation.value }
|
|
95
188
|
}
|
|
96
|
-
|
|
189
|
+
|
|
190
|
+
func enqueueConnectOperation(
|
|
191
|
+
_ connect: @escaping () async throws -> Bool
|
|
192
|
+
) -> Task<Bool, Error> {
|
|
193
|
+
enqueueConnect(nil, connect: connect)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func enqueueOnDemandConnectOperation(
|
|
197
|
+
_ connect: @escaping () async throws -> Bool
|
|
198
|
+
) -> Task<Bool, Error> {
|
|
199
|
+
enqueueConnect(nil, reuseExistingConnection: true, connect: connect)
|
|
200
|
+
}
|
|
201
|
+
|
|
97
202
|
func endConnection() throws -> Promise<Bool> {
|
|
203
|
+
let operation = enqueueEndOperation {
|
|
204
|
+
try await OpenIapModule.shared.endConnection()
|
|
205
|
+
}
|
|
206
|
+
return Promise.async { try await operation.value }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
func enqueueEndOperation(
|
|
210
|
+
_ endConnection: @escaping () async throws -> Bool
|
|
211
|
+
) -> Task<Bool, Error> {
|
|
98
212
|
let operation = enqueueLifecycleOperation {
|
|
213
|
+
RnIapLog.payload("endConnection", nil)
|
|
214
|
+
let result = try await endConnection()
|
|
99
215
|
let subscriptions = self.detachConnectionState()
|
|
100
216
|
self.removeSubscriptions(subscriptions)
|
|
101
217
|
await MainActor.run {
|
|
102
218
|
self.productTypeBySku.removeAll()
|
|
103
219
|
self.purchasePayloadById.removeAll()
|
|
104
220
|
}
|
|
105
|
-
RnIapLog.payload("endConnection", nil)
|
|
106
|
-
let result = try await OpenIapModule.shared.endConnection()
|
|
107
221
|
RnIapLog.result("endConnection", result)
|
|
108
222
|
return result
|
|
109
223
|
}
|
|
110
|
-
return
|
|
224
|
+
return operation
|
|
111
225
|
}
|
|
112
226
|
|
|
113
227
|
func fetchProducts(skus: [String], type: String) throws -> Promise<[NitroProduct]> {
|
|
114
228
|
return Promise.async {
|
|
115
|
-
try self.ensureConnection()
|
|
116
229
|
RnIapLog.payload("fetchProducts", [
|
|
117
230
|
"skus": skus,
|
|
118
231
|
"type": type
|
|
@@ -122,7 +235,6 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
122
235
|
throw OpenIapException.make(code: .emptySkuList)
|
|
123
236
|
}
|
|
124
237
|
|
|
125
|
-
var productsById: [String: NitroProduct] = [:]
|
|
126
238
|
let normalizedType = type.lowercased()
|
|
127
239
|
let queryTypes: [ProductQueryType]
|
|
128
240
|
if normalizedType == "all" {
|
|
@@ -131,21 +243,27 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
131
243
|
queryTypes = [RnIapHelper.parseProductQueryType(type)]
|
|
132
244
|
}
|
|
133
245
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
246
|
+
let productsById = try await self.runConnectedOperation {
|
|
247
|
+
var fetched: [String: NitroProduct] = [:]
|
|
248
|
+
for queryType in queryTypes {
|
|
249
|
+
let request = try OpenIapSerialization.productRequest(skus: skus, type: queryType)
|
|
250
|
+
RnIapLog.payload(
|
|
251
|
+
"fetchProducts.native", [
|
|
252
|
+
"skus": skus,
|
|
253
|
+
"type": queryType.rawValue
|
|
254
|
+
]
|
|
255
|
+
)
|
|
256
|
+
let result = try await OpenIapModule.shared.fetchProducts(request)
|
|
257
|
+
let payloads = RnIapHelper.sanitizeArray(
|
|
258
|
+
OpenIapSerialization.products(result)
|
|
259
|
+
)
|
|
260
|
+
RnIapLog.result("fetchProducts.native", payloads)
|
|
261
|
+
for payload in payloads {
|
|
262
|
+
let product = RnIapHelper.convertProductDictionary(payload)
|
|
263
|
+
fetched[product.id] = product
|
|
264
|
+
}
|
|
148
265
|
}
|
|
266
|
+
return fetched
|
|
149
267
|
}
|
|
150
268
|
|
|
151
269
|
var products: [NitroProduct] = []
|
|
@@ -200,16 +318,6 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
200
318
|
return defaultResult
|
|
201
319
|
}
|
|
202
320
|
|
|
203
|
-
guard self.isConnectionInitialized() else {
|
|
204
|
-
let err = RnIapHelper.makePurchaseErrorResult(
|
|
205
|
-
code: .initConnection,
|
|
206
|
-
message: "IAP store connection not initialized",
|
|
207
|
-
iosRequest.sku
|
|
208
|
-
)
|
|
209
|
-
self.sendPurchaseError(err, productId: iosRequest.sku)
|
|
210
|
-
return defaultResult
|
|
211
|
-
}
|
|
212
|
-
|
|
213
321
|
do {
|
|
214
322
|
var iosPayload: [String: Any] = ["sku": iosRequest.sku]
|
|
215
323
|
if case .second(let quantity) = iosRequest.quantity { iosPayload["quantity"] = Int(quantity) }
|
|
@@ -242,9 +350,14 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
242
350
|
iosPayload["winBackOffer"] = ["offerId": winBackOffer.offerId]
|
|
243
351
|
}
|
|
244
352
|
|
|
245
|
-
let
|
|
246
|
-
let
|
|
247
|
-
|
|
353
|
+
let purchaseType: ProductQueryType
|
|
354
|
+
if let requestType = request.type {
|
|
355
|
+
purchaseType = requestType == .subs ? .subs : .inApp
|
|
356
|
+
} else {
|
|
357
|
+
let cachedType = await MainActor.run { self.productTypeBySku[iosRequest.sku] }
|
|
358
|
+
let resolvedType = RnIapHelper.parseProductQueryType(cachedType)
|
|
359
|
+
purchaseType = resolvedType == .all ? .inApp : resolvedType
|
|
360
|
+
}
|
|
248
361
|
await MainActor.run {
|
|
249
362
|
self.productTypeBySku[iosRequest.sku] = purchaseType.rawValue
|
|
250
363
|
}
|
|
@@ -258,7 +371,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
258
371
|
"requestPurchase.native", iosPayload
|
|
259
372
|
)
|
|
260
373
|
|
|
261
|
-
let result = try await
|
|
374
|
+
let result = try await self.runRequestPurchaseOperation {
|
|
375
|
+
try await OpenIapModule.shared.requestPurchase(props)
|
|
376
|
+
}
|
|
262
377
|
if result != nil {
|
|
263
378
|
RnIapLog.result("requestPurchase", "delegated to OpenIAP")
|
|
264
379
|
} else {
|
|
@@ -268,8 +383,15 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
268
383
|
return defaultResult
|
|
269
384
|
} catch let purchaseError as PurchaseError {
|
|
270
385
|
RnIapLog.failure("requestPurchase", error: purchaseError)
|
|
271
|
-
|
|
272
|
-
|
|
386
|
+
return defaultResult
|
|
387
|
+
} catch let connectionError as OpenIapException {
|
|
388
|
+
RnIapLog.failure("requestPurchase", error: connectionError)
|
|
389
|
+
let err = RnIapHelper.makePurchaseErrorResult(
|
|
390
|
+
code: .initConnection,
|
|
391
|
+
message: "IAP store connection ended before the purchase started",
|
|
392
|
+
iosRequest.sku
|
|
393
|
+
)
|
|
394
|
+
self.sendPurchaseError(err, productId: iosRequest.sku)
|
|
273
395
|
return defaultResult
|
|
274
396
|
} catch {
|
|
275
397
|
RnIapLog.failure("requestPurchase", error: error)
|
|
@@ -286,7 +408,6 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
286
408
|
|
|
287
409
|
func getAvailablePurchases(options: NitroAvailablePurchasesOptions?) throws -> Promise<[NitroPurchase]> {
|
|
288
410
|
return Promise.async {
|
|
289
|
-
try self.ensureConnection()
|
|
290
411
|
do {
|
|
291
412
|
// Unwrap Variant ios options
|
|
292
413
|
let iosOpts: NitroAvailablePurchasesIosOptions?
|
|
@@ -310,13 +431,18 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
310
431
|
]
|
|
311
432
|
let purchaseOptions = try OpenIapSerialization.purchaseOptions(from: optionsDictionary)
|
|
312
433
|
RnIapLog.payload("getAvailablePurchases", optionsDictionary)
|
|
313
|
-
let purchases = try await
|
|
434
|
+
let purchases = try await self.runConnectedOperation {
|
|
435
|
+
try await OpenIapModule.shared.getAvailablePurchases(purchaseOptions)
|
|
436
|
+
}
|
|
314
437
|
let payloads = RnIapHelper.sanitizeArray(try RnIapHelper.purchasesRequired(purchases))
|
|
315
438
|
RnIapLog.result("getAvailablePurchases", payloads)
|
|
316
439
|
return payloads.map { RnIapHelper.convertPurchaseDictionary($0) }
|
|
317
440
|
} catch let purchaseError as PurchaseError {
|
|
318
441
|
RnIapLog.failure("getAvailablePurchases", error: purchaseError)
|
|
319
442
|
throw OpenIapException.from(purchaseError)
|
|
443
|
+
} catch let connectionError as OpenIapException {
|
|
444
|
+
RnIapLog.failure("getAvailablePurchases", error: connectionError)
|
|
445
|
+
throw connectionError
|
|
320
446
|
} catch {
|
|
321
447
|
RnIapLog.failure("getAvailablePurchases", error: error)
|
|
322
448
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -326,11 +452,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
326
452
|
|
|
327
453
|
func getActiveSubscriptions(subscriptionIds: [String]?) throws -> Promise<[NitroActiveSubscription]> {
|
|
328
454
|
return Promise.async {
|
|
329
|
-
try self.ensureConnection()
|
|
330
455
|
do {
|
|
331
456
|
RnIapLog.payload("getActiveSubscriptions", subscriptionIds ?? [])
|
|
332
457
|
// Call OpenIAP's native getActiveSubscriptions - includes renewalInfoIOS!
|
|
333
|
-
let subscriptions = try await
|
|
458
|
+
let subscriptions = try await self.runConnectedOperation {
|
|
459
|
+
try await OpenIapModule.shared.getActiveSubscriptions(subscriptionIds)
|
|
460
|
+
}
|
|
334
461
|
let payloads = RnIapHelper.sanitizeArray(
|
|
335
462
|
try subscriptions.map { try RnIapHelper.encodeRequired($0) }
|
|
336
463
|
)
|
|
@@ -339,6 +466,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
339
466
|
} catch let purchaseError as PurchaseError {
|
|
340
467
|
RnIapLog.failure("getActiveSubscriptions", error: purchaseError)
|
|
341
468
|
throw OpenIapException.from(purchaseError)
|
|
469
|
+
} catch let connectionError as OpenIapException {
|
|
470
|
+
RnIapLog.failure("getActiveSubscriptions", error: connectionError)
|
|
471
|
+
throw connectionError
|
|
342
472
|
} catch {
|
|
343
473
|
RnIapLog.failure("getActiveSubscriptions", error: error)
|
|
344
474
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -348,15 +478,19 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
348
478
|
|
|
349
479
|
func hasActiveSubscriptions(subscriptionIds: [String]?) throws -> Promise<Bool> {
|
|
350
480
|
return Promise.async {
|
|
351
|
-
try self.ensureConnection()
|
|
352
481
|
do {
|
|
353
482
|
RnIapLog.payload("hasActiveSubscriptions", subscriptionIds ?? [])
|
|
354
|
-
let hasActive = try await
|
|
483
|
+
let hasActive = try await self.runConnectedOperation {
|
|
484
|
+
try await OpenIapModule.shared.hasActiveSubscriptions(subscriptionIds)
|
|
485
|
+
}
|
|
355
486
|
RnIapLog.result("hasActiveSubscriptions", hasActive)
|
|
356
487
|
return hasActive
|
|
357
488
|
} catch let purchaseError as PurchaseError {
|
|
358
489
|
RnIapLog.failure("hasActiveSubscriptions", error: purchaseError)
|
|
359
490
|
throw OpenIapException.from(purchaseError)
|
|
491
|
+
} catch let connectionError as OpenIapException {
|
|
492
|
+
RnIapLog.failure("hasActiveSubscriptions", error: connectionError)
|
|
493
|
+
throw connectionError
|
|
360
494
|
} catch {
|
|
361
495
|
RnIapLog.failure("hasActiveSubscriptions", error: error)
|
|
362
496
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -367,7 +501,6 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
367
501
|
func finishTransaction(params: NitroFinishTransactionParams) throws -> Promise<Variant_Bool_NitroPurchaseResult> {
|
|
368
502
|
return Promise.async {
|
|
369
503
|
guard case .second(let iosParams) = params.ios else { return .first(true) }
|
|
370
|
-
try self.ensureConnection()
|
|
371
504
|
do {
|
|
372
505
|
RnIapLog.payload(
|
|
373
506
|
"finishTransaction", ["transactionId": iosParams.transactionId]
|
|
@@ -385,16 +518,26 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
385
518
|
let sanitizedPayload = RnIapHelper.sanitizeDictionary(purchasePayload)
|
|
386
519
|
RnIapLog.payload("finishTransaction.nativePayload", sanitizedPayload)
|
|
387
520
|
let purchaseInput = try OpenIapSerialization.purchaseInput(from: purchasePayload)
|
|
388
|
-
_ = try await
|
|
521
|
+
_ = try await self.runConnectedOperation {
|
|
522
|
+
try await OpenIapModule.shared.finishTransaction(
|
|
523
|
+
purchase: purchaseInput,
|
|
524
|
+
isConsumable: nil
|
|
525
|
+
)
|
|
526
|
+
}
|
|
389
527
|
RnIapLog.result("finishTransaction", true)
|
|
390
528
|
_ = await MainActor.run {
|
|
391
529
|
self.purchasePayloadById.removeValue(forKey: iosParams.transactionId)
|
|
392
530
|
}
|
|
393
531
|
return .first(true)
|
|
532
|
+
} catch let purchaseError as PurchaseError {
|
|
533
|
+
RnIapLog.failure("finishTransaction", error: purchaseError)
|
|
534
|
+
throw OpenIapException.from(purchaseError)
|
|
535
|
+
} catch let openIapException as OpenIapException {
|
|
536
|
+
RnIapLog.failure("finishTransaction", error: openIapException)
|
|
537
|
+
throw openIapException
|
|
394
538
|
} catch {
|
|
395
539
|
RnIapLog.failure("finishTransaction", error: error)
|
|
396
|
-
|
|
397
|
-
throw OpenIapException.make(code: .purchaseError, message: "Transaction not found: \(tid)")
|
|
540
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
398
541
|
}
|
|
399
542
|
}
|
|
400
543
|
}
|
|
@@ -410,7 +553,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
410
553
|
|
|
411
554
|
RnIapLog.payload("verifyPurchase", ["sku": sku])
|
|
412
555
|
let props = try OpenIapSerialization.verifyPurchaseProps(from: ["apple": ["sku": sku]])
|
|
413
|
-
let verifyResult = try await
|
|
556
|
+
let verifyResult = try await self.runConnectedOperation {
|
|
557
|
+
try await OpenIapModule.shared.verifyPurchase(props)
|
|
558
|
+
}
|
|
414
559
|
guard case let .verifyPurchaseResultIos(result) = verifyResult else {
|
|
415
560
|
throw OpenIapException.make(code: .featureNotSupported, message: "Expected iOS validation result")
|
|
416
561
|
}
|
|
@@ -437,6 +582,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
437
582
|
} catch let purchaseError as PurchaseError {
|
|
438
583
|
RnIapLog.failure("verifyPurchase", error: purchaseError)
|
|
439
584
|
throw OpenIapException.from(purchaseError)
|
|
585
|
+
} catch let connectionError as OpenIapException {
|
|
586
|
+
RnIapLog.failure("verifyPurchase", error: connectionError)
|
|
587
|
+
throw connectionError
|
|
440
588
|
} catch {
|
|
441
589
|
RnIapLog.failure("verifyPurchase", error: error)
|
|
442
590
|
throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
|
|
@@ -471,6 +619,13 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
471
619
|
if case .second(let google) = iapkit.google {
|
|
472
620
|
iapkitDict["google"] = ["purchaseToken": google.purchaseToken]
|
|
473
621
|
}
|
|
622
|
+
if case .second(let horizon) = iapkit.horizon {
|
|
623
|
+
var horizonDict: [String: Any] = ["sku": horizon.sku]
|
|
624
|
+
if case .second(let userId) = horizon.userId {
|
|
625
|
+
horizonDict["userId"] = userId
|
|
626
|
+
}
|
|
627
|
+
iapkitDict["horizon"] = horizonDict
|
|
628
|
+
}
|
|
474
629
|
if case .second(let amazon) = iapkit.amazon {
|
|
475
630
|
var amazonDict: [String: Any] = [
|
|
476
631
|
"receiptId": amazon.receiptId
|
|
@@ -491,7 +646,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
491
646
|
// Use JSONSerialization + JSONDecoder like expo-iap does
|
|
492
647
|
let jsonData = try JSONSerialization.data(withJSONObject: propsDict)
|
|
493
648
|
let props = try JSONDecoder().decode(VerifyPurchaseWithProviderProps.self, from: jsonData)
|
|
494
|
-
let result = try await
|
|
649
|
+
let result = try await self.runConnectedOperation {
|
|
650
|
+
try await OpenIapModule.shared.verifyPurchaseWithProvider(props)
|
|
651
|
+
}
|
|
495
652
|
RnIapLog.result("verifyPurchaseWithProvider", ["provider": result.provider, "hasIapkit": result.iapkit != nil])
|
|
496
653
|
// Convert result to Nitro types
|
|
497
654
|
var nitroIapkitResult: NitroVerifyPurchaseWithIapkitResult? = nil
|
|
@@ -537,6 +694,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
537
694
|
// Convert PurchaseError to OpenIapException to preserve message through Nitro bridge
|
|
538
695
|
RnIapLog.failure("verifyPurchaseWithProvider", error: purchaseError)
|
|
539
696
|
throw OpenIapException.from(purchaseError)
|
|
697
|
+
} catch let connectionError as OpenIapException {
|
|
698
|
+
RnIapLog.failure("verifyPurchaseWithProvider", error: connectionError)
|
|
699
|
+
throw connectionError
|
|
540
700
|
} catch {
|
|
541
701
|
RnIapLog.failure("verifyPurchaseWithProvider", error: error)
|
|
542
702
|
throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
|
|
@@ -548,12 +708,17 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
548
708
|
return Promise.async {
|
|
549
709
|
do {
|
|
550
710
|
RnIapLog.payload("getStorefront", nil)
|
|
551
|
-
let storefront = try await
|
|
711
|
+
let storefront = try await self.runConnectedOperation {
|
|
712
|
+
try await OpenIapModule.shared.getStorefront()
|
|
713
|
+
}
|
|
552
714
|
RnIapLog.result("getStorefront", storefront)
|
|
553
715
|
return storefront
|
|
554
716
|
} catch let purchaseError as PurchaseError {
|
|
555
717
|
RnIapLog.failure("getStorefront", error: purchaseError)
|
|
556
718
|
throw OpenIapException.from(purchaseError)
|
|
719
|
+
} catch let connectionError as OpenIapException {
|
|
720
|
+
RnIapLog.failure("getStorefront", error: connectionError)
|
|
721
|
+
throw connectionError
|
|
557
722
|
} catch {
|
|
558
723
|
RnIapLog.failure("getStorefront", error: error)
|
|
559
724
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -567,7 +732,10 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
567
732
|
do {
|
|
568
733
|
RnIapLog.payload("getAppTransactionIOS", nil)
|
|
569
734
|
if #available(iOS 16.0, *) {
|
|
570
|
-
|
|
735
|
+
let appTx = try await self.runConnectedOperation {
|
|
736
|
+
try await OpenIapModule.shared.getAppTransactionIOS()
|
|
737
|
+
}
|
|
738
|
+
if let appTx {
|
|
571
739
|
var result: [String: Any?] = [
|
|
572
740
|
"bundleId": appTx.bundleId,
|
|
573
741
|
"appVersion": appTx.appVersion,
|
|
@@ -597,19 +765,27 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
597
765
|
RnIapLog.result("getAppTransactionIOS", nil)
|
|
598
766
|
return .first(.null)
|
|
599
767
|
}
|
|
768
|
+
} catch let purchaseError as PurchaseError {
|
|
769
|
+
RnIapLog.failure("getAppTransactionIOS", error: purchaseError)
|
|
770
|
+
throw OpenIapException.from(purchaseError)
|
|
771
|
+
} catch let connectionError as OpenIapException {
|
|
772
|
+
RnIapLog.failure("getAppTransactionIOS", error: connectionError)
|
|
773
|
+
throw connectionError
|
|
600
774
|
} catch {
|
|
601
775
|
RnIapLog.failure("getAppTransactionIOS", error: error)
|
|
602
|
-
|
|
776
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
603
777
|
}
|
|
604
778
|
}
|
|
605
779
|
}
|
|
606
780
|
|
|
607
781
|
func getPromotedProductIOS() throws -> Promise<Variant_NullType_NitroProduct> {
|
|
608
782
|
return Promise.async {
|
|
609
|
-
try self.ensureConnection()
|
|
610
783
|
do {
|
|
611
784
|
RnIapLog.payload("getPromotedProductIOS", nil)
|
|
612
|
-
|
|
785
|
+
let product = try await self.runConnectedOperation {
|
|
786
|
+
try await OpenIapModule.shared.getPromotedProductIOS()
|
|
787
|
+
}
|
|
788
|
+
guard let product else {
|
|
613
789
|
RnIapLog.result("getPromotedProductIOS", nil)
|
|
614
790
|
return .first(.null)
|
|
615
791
|
}
|
|
@@ -619,6 +795,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
619
795
|
} catch let purchaseError as PurchaseError {
|
|
620
796
|
RnIapLog.failure("getPromotedProductIOS", error: purchaseError)
|
|
621
797
|
throw OpenIapException.from(purchaseError)
|
|
798
|
+
} catch let connectionError as OpenIapException {
|
|
799
|
+
RnIapLog.failure("getPromotedProductIOS", error: connectionError)
|
|
800
|
+
throw connectionError
|
|
622
801
|
} catch {
|
|
623
802
|
RnIapLog.failure("getPromotedProductIOS", error: error)
|
|
624
803
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -630,7 +809,10 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
630
809
|
return Promise.async {
|
|
631
810
|
do {
|
|
632
811
|
RnIapLog.payload("presentCodeRedemptionSheetIOS", nil)
|
|
633
|
-
|
|
812
|
+
let purchase = try await self.runConnectedOperation {
|
|
813
|
+
try await OpenIapModule.shared.presentCodeRedemptionSheetIOS()
|
|
814
|
+
}
|
|
815
|
+
guard let purchase else {
|
|
634
816
|
RnIapLog.result("presentCodeRedemptionSheetIOS", nil)
|
|
635
817
|
return .first(.null)
|
|
636
818
|
}
|
|
@@ -643,6 +825,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
643
825
|
}
|
|
644
826
|
}
|
|
645
827
|
return .second(RnIapHelper.convertPurchaseDictionary(payload))
|
|
828
|
+
} catch let connectionError as OpenIapException {
|
|
829
|
+
RnIapLog.failure("presentCodeRedemptionSheetIOS", error: connectionError)
|
|
830
|
+
throw connectionError
|
|
646
831
|
} catch {
|
|
647
832
|
RnIapLog.failure("presentCodeRedemptionSheetIOS", error: error)
|
|
648
833
|
throw OpenIapException.make(
|
|
@@ -656,7 +841,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
656
841
|
func clearTransactionIOS() throws -> Promise<Void> {
|
|
657
842
|
return Promise.async {
|
|
658
843
|
RnIapLog.payload("clearTransactionIOS", nil)
|
|
659
|
-
let ok = try await
|
|
844
|
+
let ok = try await self.runConnectedOperation {
|
|
845
|
+
try await OpenIapModule.shared.clearTransactionIOS()
|
|
846
|
+
}
|
|
660
847
|
RnIapLog.result("clearTransactionIOS", ok)
|
|
661
848
|
}
|
|
662
849
|
}
|
|
@@ -665,10 +852,11 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
665
852
|
|
|
666
853
|
func subscriptionStatusIOS(sku: String) throws -> Promise<Variant_NullType__NitroSubscriptionStatus_> {
|
|
667
854
|
return Promise.async {
|
|
668
|
-
try self.ensureConnection()
|
|
669
855
|
do {
|
|
670
856
|
RnIapLog.payload("subscriptionStatusIOS", ["sku": sku])
|
|
671
|
-
let statuses = try await
|
|
857
|
+
let statuses = try await self.runConnectedOperation {
|
|
858
|
+
try await OpenIapModule.shared.subscriptionStatusIOS(sku: sku)
|
|
859
|
+
}
|
|
672
860
|
let payloads = statuses.map { RnIapHelper.sanitizeDictionary(OpenIapSerialization.encode($0)) }
|
|
673
861
|
RnIapLog.result("subscriptionStatusIOS", payloads)
|
|
674
862
|
let result: [NitroSubscriptionStatus] = payloads.map { payload in
|
|
@@ -692,19 +880,26 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
692
880
|
return NitroSubscriptionStatus(state: stateValue, platform: platform, renewalInfo: renewalInfo)
|
|
693
881
|
}
|
|
694
882
|
return .second(result)
|
|
883
|
+
} catch let purchaseError as PurchaseError {
|
|
884
|
+
RnIapLog.failure("subscriptionStatusIOS", error: purchaseError)
|
|
885
|
+
throw OpenIapException.from(purchaseError)
|
|
886
|
+
} catch let connectionError as OpenIapException {
|
|
887
|
+
RnIapLog.failure("subscriptionStatusIOS", error: connectionError)
|
|
888
|
+
throw connectionError
|
|
695
889
|
} catch {
|
|
696
890
|
RnIapLog.failure("subscriptionStatusIOS", error: error)
|
|
697
|
-
|
|
891
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
698
892
|
}
|
|
699
893
|
}
|
|
700
894
|
}
|
|
701
895
|
|
|
702
896
|
func currentEntitlementIOS(sku: String) throws -> Promise<Variant_NullType_NitroPurchase> {
|
|
703
897
|
return Promise.async {
|
|
704
|
-
try self.ensureConnection()
|
|
705
898
|
do {
|
|
706
899
|
RnIapLog.payload("currentEntitlementIOS", ["sku": sku])
|
|
707
|
-
let purchase = try await
|
|
900
|
+
let purchase = try await self.runConnectedOperation {
|
|
901
|
+
try await OpenIapModule.shared.currentEntitlementIOS(sku: sku)
|
|
902
|
+
}
|
|
708
903
|
if let purchase {
|
|
709
904
|
let raw = OpenIapSerialization.encode(purchase)
|
|
710
905
|
let payload = RnIapHelper.sanitizeDictionary(raw)
|
|
@@ -718,19 +913,26 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
718
913
|
}
|
|
719
914
|
RnIapLog.result("currentEntitlementIOS", nil)
|
|
720
915
|
return .first(.null)
|
|
916
|
+
} catch let purchaseError as PurchaseError {
|
|
917
|
+
RnIapLog.failure("currentEntitlementIOS", error: purchaseError)
|
|
918
|
+
throw OpenIapException.from(purchaseError)
|
|
919
|
+
} catch let connectionError as OpenIapException {
|
|
920
|
+
RnIapLog.failure("currentEntitlementIOS", error: connectionError)
|
|
921
|
+
throw connectionError
|
|
721
922
|
} catch {
|
|
722
923
|
RnIapLog.failure("currentEntitlementIOS", error: error)
|
|
723
|
-
throw OpenIapException.make(code: .
|
|
924
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
724
925
|
}
|
|
725
926
|
}
|
|
726
927
|
}
|
|
727
928
|
|
|
728
929
|
func latestTransactionIOS(sku: String) throws -> Promise<Variant_NullType_NitroPurchase> {
|
|
729
930
|
return Promise.async {
|
|
730
|
-
try self.ensureConnection()
|
|
731
931
|
do {
|
|
732
932
|
RnIapLog.payload("latestTransactionIOS", ["sku": sku])
|
|
733
|
-
let purchase = try await
|
|
933
|
+
let purchase = try await self.runConnectedOperation {
|
|
934
|
+
try await OpenIapModule.shared.latestTransactionIOS(sku: sku)
|
|
935
|
+
}
|
|
734
936
|
if let purchase {
|
|
735
937
|
let raw = OpenIapSerialization.encode(purchase)
|
|
736
938
|
let payload = RnIapHelper.sanitizeDictionary(raw)
|
|
@@ -744,9 +946,15 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
744
946
|
}
|
|
745
947
|
RnIapLog.result("latestTransactionIOS", nil)
|
|
746
948
|
return .first(.null)
|
|
949
|
+
} catch let purchaseError as PurchaseError {
|
|
950
|
+
RnIapLog.failure("latestTransactionIOS", error: purchaseError)
|
|
951
|
+
throw OpenIapException.from(purchaseError)
|
|
952
|
+
} catch let connectionError as OpenIapException {
|
|
953
|
+
RnIapLog.failure("latestTransactionIOS", error: connectionError)
|
|
954
|
+
throw connectionError
|
|
747
955
|
} catch {
|
|
748
956
|
RnIapLog.failure("latestTransactionIOS", error: error)
|
|
749
|
-
throw OpenIapException.make(code: .
|
|
957
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
750
958
|
}
|
|
751
959
|
}
|
|
752
960
|
}
|
|
@@ -755,7 +963,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
755
963
|
return Promise.async {
|
|
756
964
|
do {
|
|
757
965
|
RnIapLog.payload("getPendingTransactionsIOS", nil)
|
|
758
|
-
let pending = try await
|
|
966
|
+
let pending = try await self.runConnectedOperation {
|
|
967
|
+
try await OpenIapModule.shared.getPendingTransactionsIOS()
|
|
968
|
+
}
|
|
759
969
|
var unionPurchases: [OpenIAP.Purchase] = []
|
|
760
970
|
for purchase in pending {
|
|
761
971
|
let union = OpenIAP.Purchase.purchaseIos(purchase)
|
|
@@ -773,6 +983,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
773
983
|
} catch let purchaseError as PurchaseError {
|
|
774
984
|
RnIapLog.failure("getPendingTransactionsIOS", error: purchaseError)
|
|
775
985
|
throw OpenIapException.from(purchaseError)
|
|
986
|
+
} catch let connectionError as OpenIapException {
|
|
987
|
+
RnIapLog.failure("getPendingTransactionsIOS", error: connectionError)
|
|
988
|
+
throw connectionError
|
|
776
989
|
} catch {
|
|
777
990
|
RnIapLog.failure("getPendingTransactionsIOS", error: error)
|
|
778
991
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -784,7 +997,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
784
997
|
return Promise.async {
|
|
785
998
|
do {
|
|
786
999
|
RnIapLog.payload("getAllTransactionsIOS", nil)
|
|
787
|
-
let all = try await
|
|
1000
|
+
let all = try await self.runConnectedOperation {
|
|
1001
|
+
try await OpenIapModule.shared.getAllTransactionsIOS()
|
|
1002
|
+
}
|
|
788
1003
|
var unionPurchases: [OpenIAP.Purchase] = []
|
|
789
1004
|
var payloadUpdates: [String: [String: Any]] = [:]
|
|
790
1005
|
for purchase in all {
|
|
@@ -807,6 +1022,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
807
1022
|
} catch let purchaseError as PurchaseError {
|
|
808
1023
|
RnIapLog.failure("getAllTransactionsIOS", error: purchaseError)
|
|
809
1024
|
throw OpenIapException.from(purchaseError)
|
|
1025
|
+
} catch let connectionError as OpenIapException {
|
|
1026
|
+
RnIapLog.failure("getAllTransactionsIOS", error: connectionError)
|
|
1027
|
+
throw connectionError
|
|
810
1028
|
} catch {
|
|
811
1029
|
RnIapLog.failure("getAllTransactionsIOS", error: error)
|
|
812
1030
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -818,12 +1036,17 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
818
1036
|
return Promise.async {
|
|
819
1037
|
do {
|
|
820
1038
|
RnIapLog.payload("syncIOS", nil)
|
|
821
|
-
let ok = try await
|
|
1039
|
+
let ok = try await self.runConnectedOperation {
|
|
1040
|
+
try await OpenIapModule.shared.syncIOS()
|
|
1041
|
+
}
|
|
822
1042
|
RnIapLog.result("syncIOS", ok)
|
|
823
1043
|
return ok
|
|
824
1044
|
} catch let purchaseError as PurchaseError {
|
|
825
1045
|
RnIapLog.failure("syncIOS", error: purchaseError)
|
|
826
1046
|
throw OpenIapException.from(purchaseError)
|
|
1047
|
+
} catch let connectionError as OpenIapException {
|
|
1048
|
+
RnIapLog.failure("syncIOS", error: connectionError)
|
|
1049
|
+
throw connectionError
|
|
827
1050
|
} catch {
|
|
828
1051
|
RnIapLog.failure("syncIOS", error: error)
|
|
829
1052
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -833,10 +1056,11 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
833
1056
|
|
|
834
1057
|
func showManageSubscriptionsIOS() throws -> Promise<[NitroPurchase]> {
|
|
835
1058
|
return Promise.async {
|
|
836
|
-
try self.ensureConnection()
|
|
837
1059
|
do {
|
|
838
1060
|
RnIapLog.payload("showManageSubscriptionsIOS", nil)
|
|
839
|
-
let changedPurchases = try await
|
|
1061
|
+
let changedPurchases = try await self.runConnectedOperation {
|
|
1062
|
+
try await OpenIapModule.shared.showManageSubscriptionsIOS()
|
|
1063
|
+
}
|
|
840
1064
|
let unionPurchases = changedPurchases.map { OpenIAP.Purchase.purchaseIos($0) }
|
|
841
1065
|
let payloads = RnIapHelper.sanitizeArray(try RnIapHelper.purchasesRequired(unionPurchases))
|
|
842
1066
|
RnIapLog.result("showManageSubscriptionsIOS", payloads)
|
|
@@ -844,6 +1068,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
844
1068
|
} catch let purchaseError as PurchaseError {
|
|
845
1069
|
RnIapLog.failure("showManageSubscriptionsIOS", error: purchaseError)
|
|
846
1070
|
throw OpenIapException.from(purchaseError)
|
|
1071
|
+
} catch let connectionError as OpenIapException {
|
|
1072
|
+
RnIapLog.failure("showManageSubscriptionsIOS", error: connectionError)
|
|
1073
|
+
throw connectionError
|
|
847
1074
|
} catch {
|
|
848
1075
|
RnIapLog.failure("showManageSubscriptionsIOS", error: error)
|
|
849
1076
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -853,15 +1080,19 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
853
1080
|
|
|
854
1081
|
func deepLinkToSubscriptionsIOS() throws -> Promise<Bool> {
|
|
855
1082
|
return Promise.async {
|
|
856
|
-
try self.ensureConnection()
|
|
857
1083
|
do {
|
|
858
1084
|
RnIapLog.payload("deepLinkToSubscriptionsIOS", nil)
|
|
859
|
-
try await
|
|
1085
|
+
try await self.runConnectedOperation {
|
|
1086
|
+
try await OpenIapModule.shared.deepLinkToSubscriptions(nil)
|
|
1087
|
+
}
|
|
860
1088
|
RnIapLog.result("deepLinkToSubscriptionsIOS", true)
|
|
861
1089
|
return true
|
|
862
1090
|
} catch let purchaseError as PurchaseError {
|
|
863
1091
|
RnIapLog.failure("deepLinkToSubscriptionsIOS", error: purchaseError)
|
|
864
1092
|
throw OpenIapException.from(purchaseError)
|
|
1093
|
+
} catch let connectionError as OpenIapException {
|
|
1094
|
+
RnIapLog.failure("deepLinkToSubscriptionsIOS", error: connectionError)
|
|
1095
|
+
throw connectionError
|
|
865
1096
|
} catch {
|
|
866
1097
|
RnIapLog.failure("deepLinkToSubscriptionsIOS", error: error)
|
|
867
1098
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -872,7 +1103,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
872
1103
|
func isEligibleForIntroOfferIOS(groupID: String) throws -> Promise<Bool> {
|
|
873
1104
|
return Promise.async {
|
|
874
1105
|
RnIapLog.payload("isEligibleForIntroOfferIOS", ["groupID": groupID])
|
|
875
|
-
let value = try await
|
|
1106
|
+
let value = try await self.runConnectedOperation {
|
|
1107
|
+
try await OpenIapModule.shared.isEligibleForIntroOfferIOS(groupID: groupID)
|
|
1108
|
+
}
|
|
876
1109
|
RnIapLog.result("isEligibleForIntroOfferIOS", value)
|
|
877
1110
|
return value
|
|
878
1111
|
}
|
|
@@ -880,15 +1113,19 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
880
1113
|
|
|
881
1114
|
func getReceiptDataIOS() throws -> Promise<String> {
|
|
882
1115
|
return Promise.async {
|
|
883
|
-
try self.ensureConnection()
|
|
884
1116
|
do {
|
|
885
1117
|
RnIapLog.payload("getReceiptDataIOS", nil)
|
|
886
|
-
let receipt = try await
|
|
1118
|
+
let receipt = try await self.runConnectedOperation {
|
|
1119
|
+
try await RnIapHelper.loadReceiptData(refresh: false)
|
|
1120
|
+
}
|
|
887
1121
|
RnIapLog.result("getReceiptDataIOS", "<receipt>")
|
|
888
1122
|
return receipt
|
|
889
1123
|
} catch let purchaseError as PurchaseError {
|
|
890
1124
|
RnIapLog.failure("getReceiptDataIOS", error: purchaseError)
|
|
891
1125
|
throw OpenIapException.from(purchaseError)
|
|
1126
|
+
} catch let connectionError as OpenIapException {
|
|
1127
|
+
RnIapLog.failure("getReceiptDataIOS", error: connectionError)
|
|
1128
|
+
throw connectionError
|
|
892
1129
|
} catch {
|
|
893
1130
|
RnIapLog.failure("getReceiptDataIOS", error: error)
|
|
894
1131
|
throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
|
|
@@ -898,15 +1135,19 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
898
1135
|
|
|
899
1136
|
func requestReceiptRefreshIOS() throws -> Promise<String> {
|
|
900
1137
|
return Promise.async {
|
|
901
|
-
try self.ensureConnection()
|
|
902
1138
|
do {
|
|
903
1139
|
RnIapLog.payload("requestReceiptRefreshIOS", nil)
|
|
904
|
-
let receipt = try await
|
|
1140
|
+
let receipt = try await self.runConnectedOperation {
|
|
1141
|
+
try await RnIapHelper.loadReceiptData(refresh: true)
|
|
1142
|
+
}
|
|
905
1143
|
RnIapLog.result("requestReceiptRefreshIOS", "<receipt>")
|
|
906
1144
|
return receipt
|
|
907
1145
|
} catch let purchaseError as PurchaseError {
|
|
908
1146
|
RnIapLog.failure("requestReceiptRefreshIOS", error: purchaseError)
|
|
909
1147
|
throw OpenIapException.from(purchaseError)
|
|
1148
|
+
} catch let connectionError as OpenIapException {
|
|
1149
|
+
RnIapLog.failure("requestReceiptRefreshIOS", error: connectionError)
|
|
1150
|
+
throw connectionError
|
|
910
1151
|
} catch {
|
|
911
1152
|
RnIapLog.failure("requestReceiptRefreshIOS", error: error)
|
|
912
1153
|
throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
|
|
@@ -916,9 +1157,10 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
916
1157
|
|
|
917
1158
|
func isTransactionVerifiedIOS(sku: String) throws -> Promise<Bool> {
|
|
918
1159
|
return Promise.async {
|
|
919
|
-
try self.ensureConnection()
|
|
920
1160
|
RnIapLog.payload("isTransactionVerifiedIOS", ["sku": sku])
|
|
921
|
-
let value = try await
|
|
1161
|
+
let value = try await self.runConnectedOperation {
|
|
1162
|
+
try await OpenIapModule.shared.isTransactionVerifiedIOS(sku: sku)
|
|
1163
|
+
}
|
|
922
1164
|
RnIapLog.result("isTransactionVerifiedIOS", value)
|
|
923
1165
|
return value
|
|
924
1166
|
}
|
|
@@ -926,19 +1168,26 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
926
1168
|
|
|
927
1169
|
func getTransactionJwsIOS(sku: String) throws -> Promise<Variant_NullType_String> {
|
|
928
1170
|
return Promise.async {
|
|
929
|
-
try self.ensureConnection()
|
|
930
1171
|
do {
|
|
931
1172
|
RnIapLog.payload("getTransactionJwsIOS", ["sku": sku])
|
|
932
|
-
let jws = try await
|
|
1173
|
+
let jws = try await self.runConnectedOperation {
|
|
1174
|
+
try await OpenIapModule.shared.getTransactionJwsIOS(sku: sku)
|
|
1175
|
+
}
|
|
933
1176
|
let maskedJws: Any? = (jws == nil) ? nil : "<jws>"
|
|
934
1177
|
RnIapLog.result("getTransactionJwsIOS", maskedJws)
|
|
935
1178
|
if let jws {
|
|
936
1179
|
return .second(jws)
|
|
937
1180
|
}
|
|
938
1181
|
return .first(.null)
|
|
1182
|
+
} catch let purchaseError as PurchaseError {
|
|
1183
|
+
RnIapLog.failure("getTransactionJwsIOS", error: purchaseError)
|
|
1184
|
+
throw OpenIapException.from(purchaseError)
|
|
1185
|
+
} catch let connectionError as OpenIapException {
|
|
1186
|
+
RnIapLog.failure("getTransactionJwsIOS", error: connectionError)
|
|
1187
|
+
throw connectionError
|
|
939
1188
|
} catch {
|
|
940
1189
|
RnIapLog.failure("getTransactionJwsIOS", error: error)
|
|
941
|
-
throw OpenIapException.make(code: .
|
|
1190
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
942
1191
|
}
|
|
943
1192
|
}
|
|
944
1193
|
}
|
|
@@ -947,15 +1196,23 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
947
1196
|
return Promise.async {
|
|
948
1197
|
do {
|
|
949
1198
|
RnIapLog.payload("beginRefundRequestIOS", ["sku": sku])
|
|
950
|
-
let result = try await
|
|
1199
|
+
let result = try await self.runConnectedOperation {
|
|
1200
|
+
try await OpenIapModule.shared.beginRefundRequestIOS(sku: sku)
|
|
1201
|
+
}
|
|
951
1202
|
RnIapLog.result("beginRefundRequestIOS", result)
|
|
952
1203
|
if let result {
|
|
953
1204
|
return .second(result)
|
|
954
1205
|
}
|
|
955
1206
|
return .first(.null)
|
|
1207
|
+
} catch let purchaseError as PurchaseError {
|
|
1208
|
+
RnIapLog.failure("beginRefundRequestIOS", error: purchaseError)
|
|
1209
|
+
throw OpenIapException.from(purchaseError)
|
|
1210
|
+
} catch let connectionError as OpenIapException {
|
|
1211
|
+
RnIapLog.failure("beginRefundRequestIOS", error: connectionError)
|
|
1212
|
+
throw connectionError
|
|
956
1213
|
} catch {
|
|
957
1214
|
RnIapLog.failure("beginRefundRequestIOS", error: error)
|
|
958
|
-
|
|
1215
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
959
1216
|
}
|
|
960
1217
|
}
|
|
961
1218
|
}
|
|
@@ -972,7 +1229,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
972
1229
|
Task {
|
|
973
1230
|
guard self.isCurrentConnection(epoch) else { return }
|
|
974
1231
|
RnIapLog.payload("promotedProductListenerIOS.fetch", nil)
|
|
975
|
-
if let product = try? await
|
|
1232
|
+
if let product = try? await self.enqueueConnectedOperation({
|
|
1233
|
+
try await OpenIapModule.shared.getPromotedProductIOS()
|
|
1234
|
+
}).value {
|
|
976
1235
|
guard self.isCurrentConnection(epoch) else { return }
|
|
977
1236
|
let payload = RnIapHelper.sanitizeDictionary(OpenIapSerialization.encode(product))
|
|
978
1237
|
RnIapLog.result("promotedProductListenerIOS.fetch", payload)
|
|
@@ -994,7 +1253,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
994
1253
|
) throws -> Double {
|
|
995
1254
|
let dedupeTransactionIOS = purchaseUpdatedDedupeTransactionIOS(from: options)
|
|
996
1255
|
let receiveDuplicateTransactionUpdatesIOS = !dedupeTransactionIOS
|
|
997
|
-
let (token, epoch) = listenerLock.withLock {
|
|
1256
|
+
let (token, epoch, shouldFlush, flushEpoch) = listenerLock.withLock {
|
|
998
1257
|
let token = nextPurchaseUpdatedListenerToken
|
|
999
1258
|
nextPurchaseUpdatedListenerToken += 1
|
|
1000
1259
|
|
|
@@ -1008,9 +1267,26 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1008
1267
|
purchaseUpdatedListeners.append((token: token, listener: listener))
|
|
1009
1268
|
}
|
|
1010
1269
|
purchaseUpdatedListenerRegistrations.append(registration)
|
|
1011
|
-
|
|
1270
|
+
let shouldFlush: Bool
|
|
1271
|
+
if receiveDuplicateTransactionUpdatesIOS {
|
|
1272
|
+
shouldFlush = pendingDuplicatePurchaseUpdates.beginFlushIfNeeded()
|
|
1273
|
+
} else {
|
|
1274
|
+
shouldFlush = pendingPurchaseUpdates.beginFlushIfNeeded()
|
|
1275
|
+
}
|
|
1276
|
+
return (
|
|
1277
|
+
token,
|
|
1278
|
+
isInitialized ? connectionEpoch : nil,
|
|
1279
|
+
shouldFlush,
|
|
1280
|
+
connectionEpoch
|
|
1281
|
+
)
|
|
1012
1282
|
}
|
|
1013
1283
|
|
|
1284
|
+
if shouldFlush {
|
|
1285
|
+
schedulePendingPurchaseUpdateFlush(
|
|
1286
|
+
includeDuplicateListeners: receiveDuplicateTransactionUpdatesIOS,
|
|
1287
|
+
expectedEpoch: flushEpoch
|
|
1288
|
+
)
|
|
1289
|
+
}
|
|
1014
1290
|
if let epoch {
|
|
1015
1291
|
if receiveDuplicateTransactionUpdatesIOS {
|
|
1016
1292
|
attachDuplicatePurchaseUpdatedSubIfNeeded(expectedEpoch: epoch)
|
|
@@ -1032,9 +1308,13 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1032
1308
|
}
|
|
1033
1309
|
|
|
1034
1310
|
func addPurchaseErrorListener(listener: @escaping (NitroPurchaseResult) -> Void) throws {
|
|
1035
|
-
let epoch = listenerLock.withLock {
|
|
1311
|
+
let (epoch, shouldFlush, flushEpoch) = listenerLock.withLock {
|
|
1036
1312
|
purchaseErrorListeners.append(listener)
|
|
1037
|
-
|
|
1313
|
+
let shouldFlush = pendingPurchaseErrors.beginFlushIfNeeded()
|
|
1314
|
+
return (isInitialized ? connectionEpoch : nil, shouldFlush, connectionEpoch)
|
|
1315
|
+
}
|
|
1316
|
+
if shouldFlush {
|
|
1317
|
+
schedulePendingPurchaseErrorFlush(expectedEpoch: flushEpoch)
|
|
1038
1318
|
}
|
|
1039
1319
|
if let epoch {
|
|
1040
1320
|
attachPurchaseErrorSubIfNeeded(expectedEpoch: epoch)
|
|
@@ -1120,18 +1400,289 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1120
1400
|
}
|
|
1121
1401
|
}
|
|
1122
1402
|
|
|
1123
|
-
|
|
1403
|
+
func enqueueConnectedOperation<T>(
|
|
1404
|
+
_ operation: @escaping () async throws -> T
|
|
1405
|
+
) -> Task<T, Error> {
|
|
1406
|
+
enqueueLifecycleOperation {
|
|
1407
|
+
guard self.isConnectionInitialized() else {
|
|
1408
|
+
throw OpenIapException.make(
|
|
1409
|
+
code: .initConnection,
|
|
1410
|
+
message: "Connection ended before the store operation started."
|
|
1411
|
+
)
|
|
1412
|
+
}
|
|
1413
|
+
return try await operation()
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
private func runConnectedOperation<T>(
|
|
1418
|
+
_ operation: @escaping () async throws -> T
|
|
1419
|
+
) async throws -> T {
|
|
1420
|
+
try await ensureConnection()
|
|
1421
|
+
return try await enqueueConnectedOperation(operation).value
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
func currentConnectionEpoch() -> UInt64 {
|
|
1425
|
+
listenerLock.withLock { connectionEpoch }
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
func runRequestPurchaseOperation(
|
|
1429
|
+
_ operation: @escaping () async throws -> OpenIAP.RequestPurchaseResult?
|
|
1430
|
+
) async throws -> OpenIAP.RequestPurchaseResult? {
|
|
1431
|
+
try await runConnectedOperation {
|
|
1432
|
+
let epoch = self.currentConnectionEpoch()
|
|
1433
|
+
do {
|
|
1434
|
+
let result = try await operation()
|
|
1435
|
+
await self.deliverRequestPurchaseResultIfNeeded(
|
|
1436
|
+
result,
|
|
1437
|
+
expectedEpoch: epoch
|
|
1438
|
+
)
|
|
1439
|
+
return result
|
|
1440
|
+
} catch let purchaseError as PurchaseError {
|
|
1441
|
+
self.deliverRequestPurchaseError(purchaseError)
|
|
1442
|
+
throw purchaseError
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
private func claimPurchaseUpdateDelivery(
|
|
1448
|
+
transactionId: String,
|
|
1449
|
+
expectedEpoch: UInt64
|
|
1450
|
+
) -> Bool {
|
|
1451
|
+
listenerLock.withLock {
|
|
1452
|
+
guard isInitialized, connectionEpoch == expectedEpoch else { return false }
|
|
1453
|
+
return deliveredPurchaseUpdateIds.insert(transactionId).inserted
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
private func claimRequestPurchaseDelivery(
|
|
1458
|
+
transactionId: String,
|
|
1459
|
+
expectedEpoch: UInt64
|
|
1460
|
+
) -> (deduping: Bool, nonDeduping: Bool) {
|
|
1461
|
+
listenerLock.withLock {
|
|
1462
|
+
guard isInitialized, connectionEpoch == expectedEpoch else {
|
|
1463
|
+
return (false, false)
|
|
1464
|
+
}
|
|
1465
|
+
let deduping = deliveredPurchaseUpdateIds.insert(transactionId).inserted
|
|
1466
|
+
let nonDeduping = !purchaseUpdatedDuplicateListeners.isEmpty
|
|
1467
|
+
if nonDeduping {
|
|
1468
|
+
pendingDuplicatePurchaseUpdateSuppressions[transactionId, default: 0] += 1
|
|
1469
|
+
}
|
|
1470
|
+
return (deduping, nonDeduping)
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
private func claimDuplicatePurchaseUpdateDelivery(
|
|
1475
|
+
transactionId: String,
|
|
1476
|
+
expectedEpoch: UInt64
|
|
1477
|
+
) -> Bool {
|
|
1478
|
+
listenerLock.withLock {
|
|
1479
|
+
guard isInitialized, connectionEpoch == expectedEpoch else { return false }
|
|
1480
|
+
if let count = pendingDuplicatePurchaseUpdateSuppressions[transactionId] {
|
|
1481
|
+
if count == 1 {
|
|
1482
|
+
pendingDuplicatePurchaseUpdateSuppressions.removeValue(forKey: transactionId)
|
|
1483
|
+
} else {
|
|
1484
|
+
pendingDuplicatePurchaseUpdateSuppressions[transactionId] = count - 1
|
|
1485
|
+
}
|
|
1486
|
+
return false
|
|
1487
|
+
}
|
|
1488
|
+
return true
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
private func deliverRequestPurchaseResultIfNeeded(
|
|
1493
|
+
_ result: OpenIAP.RequestPurchaseResult?,
|
|
1494
|
+
expectedEpoch: UInt64
|
|
1495
|
+
) async {
|
|
1496
|
+
guard let result else { return }
|
|
1497
|
+
let purchases: [OpenIAP.Purchase]
|
|
1498
|
+
switch result {
|
|
1499
|
+
case .purchase(let purchase):
|
|
1500
|
+
purchases = purchase.map { [$0] } ?? []
|
|
1501
|
+
case .purchases(let values):
|
|
1502
|
+
purchases = values ?? []
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
for purchase in purchases {
|
|
1506
|
+
let rawPayload = OpenIapSerialization.purchase(purchase)
|
|
1507
|
+
guard let transactionId = rawPayload["id"] as? String else { continue }
|
|
1508
|
+
let delivery = claimRequestPurchaseDelivery(
|
|
1509
|
+
transactionId: transactionId,
|
|
1510
|
+
expectedEpoch: expectedEpoch
|
|
1511
|
+
)
|
|
1512
|
+
guard delivery.deduping || delivery.nonDeduping else { continue }
|
|
1513
|
+
let payload = RnIapHelper.sanitizeDictionary(rawPayload)
|
|
1514
|
+
let nitro = RnIapHelper.convertPurchaseDictionary(payload)
|
|
1515
|
+
await MainActor.run {
|
|
1516
|
+
guard self.isCurrentConnection(expectedEpoch) else { return }
|
|
1517
|
+
self.purchasePayloadById[transactionId] = rawPayload
|
|
1518
|
+
if delivery.deduping {
|
|
1519
|
+
self.sendPurchaseUpdate(nitro, includeDuplicateListeners: false)
|
|
1520
|
+
}
|
|
1521
|
+
if delivery.nonDeduping {
|
|
1522
|
+
self.sendPurchaseUpdate(nitro, includeDuplicateListeners: true)
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
private func deliverPurchaseUpdateIfNeeded(
|
|
1529
|
+
_ purchase: OpenIAP.Purchase,
|
|
1530
|
+
expectedEpoch: UInt64
|
|
1531
|
+
) async {
|
|
1532
|
+
let rawPayload = OpenIapSerialization.purchase(purchase)
|
|
1533
|
+
if let transactionId = rawPayload["id"] as? String,
|
|
1534
|
+
!claimPurchaseUpdateDelivery(
|
|
1535
|
+
transactionId: transactionId,
|
|
1536
|
+
expectedEpoch: expectedEpoch
|
|
1537
|
+
) {
|
|
1538
|
+
return
|
|
1539
|
+
}
|
|
1540
|
+
let payload = RnIapHelper.sanitizeDictionary(rawPayload)
|
|
1541
|
+
let nitro = RnIapHelper.convertPurchaseDictionary(payload)
|
|
1542
|
+
await MainActor.run {
|
|
1543
|
+
guard self.isCurrentConnection(expectedEpoch) else { return }
|
|
1544
|
+
RnIapLog.result("purchaseUpdatedListener", payload)
|
|
1545
|
+
if let transactionId = rawPayload["id"] as? String {
|
|
1546
|
+
self.purchasePayloadById[transactionId] = rawPayload
|
|
1547
|
+
}
|
|
1548
|
+
self.sendPurchaseUpdate(nitro, includeDuplicateListeners: false)
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
private func deliverDuplicatePurchaseUpdateIfNeeded(
|
|
1553
|
+
_ purchase: OpenIAP.Purchase,
|
|
1554
|
+
expectedEpoch: UInt64
|
|
1555
|
+
) async {
|
|
1556
|
+
let rawPayload = OpenIapSerialization.purchase(purchase)
|
|
1557
|
+
if let transactionId = rawPayload["id"] as? String,
|
|
1558
|
+
!claimDuplicatePurchaseUpdateDelivery(
|
|
1559
|
+
transactionId: transactionId,
|
|
1560
|
+
expectedEpoch: expectedEpoch
|
|
1561
|
+
) {
|
|
1562
|
+
return
|
|
1563
|
+
}
|
|
1564
|
+
let payload = RnIapHelper.sanitizeDictionary(rawPayload)
|
|
1565
|
+
let nitro = RnIapHelper.convertPurchaseDictionary(payload)
|
|
1566
|
+
await MainActor.run {
|
|
1567
|
+
guard self.isCurrentConnection(expectedEpoch) else { return }
|
|
1568
|
+
RnIapLog.result("purchaseUpdatedListener.duplicates", payload)
|
|
1569
|
+
if let transactionId = rawPayload["id"] as? String {
|
|
1570
|
+
self.purchasePayloadById[transactionId] = rawPayload
|
|
1571
|
+
}
|
|
1572
|
+
self.sendPurchaseUpdate(nitro, includeDuplicateListeners: true)
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
func enqueuePurchaseUpdateDelivery(
|
|
1577
|
+
_ purchase: OpenIAP.Purchase,
|
|
1578
|
+
expectedEpoch: UInt64,
|
|
1579
|
+
includeDuplicateListeners: Bool
|
|
1580
|
+
) -> Task<Void, Error> {
|
|
1581
|
+
enqueueConnectedOperation {
|
|
1582
|
+
if includeDuplicateListeners {
|
|
1583
|
+
await self.deliverDuplicatePurchaseUpdateIfNeeded(
|
|
1584
|
+
purchase,
|
|
1585
|
+
expectedEpoch: expectedEpoch
|
|
1586
|
+
)
|
|
1587
|
+
} else {
|
|
1588
|
+
await self.deliverPurchaseUpdateIfNeeded(
|
|
1589
|
+
purchase,
|
|
1590
|
+
expectedEpoch: expectedEpoch
|
|
1591
|
+
)
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
private func deliverRequestPurchaseError(_ error: PurchaseError) {
|
|
1597
|
+
let result = RnIapHelper.makePurchaseErrorResult(
|
|
1598
|
+
code: error.code,
|
|
1599
|
+
message: error.message,
|
|
1600
|
+
error.productId,
|
|
1601
|
+
debugMessage: error.debugMessage
|
|
1602
|
+
)
|
|
1603
|
+
let key = RnIapHelper.makeErrorDedupKey(
|
|
1604
|
+
code: error.code.rawValue,
|
|
1605
|
+
productId: error.productId
|
|
1606
|
+
)
|
|
1607
|
+
listenerLock.withLock {
|
|
1608
|
+
pendingRequestPurchaseErrorSuppressions[key, default: 0] += 1
|
|
1609
|
+
}
|
|
1610
|
+
sendPurchaseError(result, productId: error.productId, dedupe: false)
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
private func consumeRequestPurchaseErrorSuppression(_ error: PurchaseError) -> Bool {
|
|
1614
|
+
let key = RnIapHelper.makeErrorDedupKey(
|
|
1615
|
+
code: error.code.rawValue,
|
|
1616
|
+
productId: error.productId
|
|
1617
|
+
)
|
|
1618
|
+
return listenerLock.withLock {
|
|
1619
|
+
guard let count = pendingRequestPurchaseErrorSuppressions[key] else {
|
|
1620
|
+
return false
|
|
1621
|
+
}
|
|
1622
|
+
if count == 1 {
|
|
1623
|
+
pendingRequestPurchaseErrorSuppressions.removeValue(forKey: key)
|
|
1624
|
+
} else {
|
|
1625
|
+
pendingRequestPurchaseErrorSuppressions[key] = count - 1
|
|
1626
|
+
}
|
|
1627
|
+
return true
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
func enqueuePurchaseErrorDelivery(
|
|
1632
|
+
_ error: PurchaseError,
|
|
1633
|
+
expectedEpoch: UInt64
|
|
1634
|
+
) -> Task<Void, Error> {
|
|
1635
|
+
return enqueueLifecycleOperation {
|
|
1636
|
+
guard self.isCurrentEpoch(expectedEpoch),
|
|
1637
|
+
!self.consumeOnDemandInitErrorSuppression(error),
|
|
1638
|
+
!self.consumeRequestPurchaseErrorSuppression(error) else {
|
|
1639
|
+
return
|
|
1640
|
+
}
|
|
1641
|
+
let payload = RnIapHelper.sanitizeDictionary(OpenIapSerialization.encode(error))
|
|
1642
|
+
let nitroError = RnIapHelper.makePurchaseErrorResult(
|
|
1643
|
+
code: error.code,
|
|
1644
|
+
message: error.message,
|
|
1645
|
+
error.productId,
|
|
1646
|
+
debugMessage: error.debugMessage
|
|
1647
|
+
)
|
|
1648
|
+
await MainActor.run {
|
|
1649
|
+
guard self.isCurrentEpoch(expectedEpoch) else { return }
|
|
1650
|
+
RnIapLog.result("purchaseErrorListener", payload)
|
|
1651
|
+
self.sendPurchaseError(nitroError, productId: error.productId)
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
private func consumeOnDemandInitErrorSuppression(_ error: PurchaseError) -> Bool {
|
|
1657
|
+
listenerLock.withLock {
|
|
1658
|
+
let key = error.code.rawValue
|
|
1659
|
+
guard let count = pendingOnDemandInitErrorSuppressions[key] else {
|
|
1660
|
+
return false
|
|
1661
|
+
}
|
|
1662
|
+
if count == 1 {
|
|
1663
|
+
pendingOnDemandInitErrorSuppressions.removeValue(forKey: key)
|
|
1664
|
+
} else {
|
|
1665
|
+
pendingOnDemandInitErrorSuppressions[key] = count - 1
|
|
1666
|
+
}
|
|
1667
|
+
return true
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
private func attachCoreListenersIfNeeded(epoch: UInt64) {
|
|
1124
1672
|
attachPurchaseUpdatedSubIfNeeded(expectedEpoch: epoch)
|
|
1125
1673
|
attachDuplicatePurchaseUpdatedSubIfNeeded(expectedEpoch: epoch)
|
|
1126
1674
|
attachPurchaseErrorSubIfNeeded(expectedEpoch: epoch)
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
private func attachListenersIfNeeded(epoch: UInt64) {
|
|
1678
|
+
attachCoreListenersIfNeeded(epoch: epoch)
|
|
1127
1679
|
attachPromotedProductSubIfNeeded(expectedEpoch: epoch)
|
|
1128
1680
|
attachSubscriptionBillingIssueSubIfNeeded(expectedEpoch: epoch)
|
|
1129
1681
|
}
|
|
1130
1682
|
|
|
1131
1683
|
private func attachPurchaseErrorSubIfNeeded(expectedEpoch: UInt64) {
|
|
1132
1684
|
listenerLock.withLock {
|
|
1133
|
-
guard
|
|
1134
|
-
connectionEpoch == expectedEpoch,
|
|
1685
|
+
guard connectionEpoch == expectedEpoch,
|
|
1135
1686
|
purchaseErrorSub == nil else { return }
|
|
1136
1687
|
RnIapLog.payload("purchaseErrorListener.register", nil)
|
|
1137
1688
|
purchaseErrorSub = OpenIapModule.shared.purchaseErrorListener { [weak self] error in
|
|
@@ -1139,17 +1690,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1139
1690
|
RnIapLog.warn("purchaseErrorListener: HybridRnIap deallocated, error event dropped")
|
|
1140
1691
|
return
|
|
1141
1692
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
message: error.message,
|
|
1149
|
-
error.productId,
|
|
1150
|
-
debugMessage: error.debugMessage
|
|
1151
|
-
)
|
|
1152
|
-
self.sendPurchaseError(nitroError, productId: error.productId)
|
|
1693
|
+
let operation = self.enqueuePurchaseErrorDelivery(
|
|
1694
|
+
error,
|
|
1695
|
+
expectedEpoch: expectedEpoch
|
|
1696
|
+
)
|
|
1697
|
+
Task {
|
|
1698
|
+
_ = try? await operation.value
|
|
1153
1699
|
}
|
|
1154
1700
|
}
|
|
1155
1701
|
RnIapLog.result("purchaseErrorListener.register", "attached")
|
|
@@ -1172,7 +1718,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1172
1718
|
RnIapLog.payload("promotedProductListenerIOS", ["productId": productId])
|
|
1173
1719
|
do {
|
|
1174
1720
|
let request = try OpenIapSerialization.productRequest(skus: [productId], type: .all)
|
|
1175
|
-
let result = try await
|
|
1721
|
+
let result = try await self.enqueueConnectedOperation {
|
|
1722
|
+
try await OpenIapModule.shared.fetchProducts(request)
|
|
1723
|
+
}.value
|
|
1176
1724
|
let payloads = RnIapHelper.sanitizeArray(OpenIapSerialization.products(result))
|
|
1177
1725
|
RnIapLog.result("fetchProducts", payloads)
|
|
1178
1726
|
if let payload = payloads.first {
|
|
@@ -1201,8 +1749,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1201
1749
|
|
|
1202
1750
|
private func attachPurchaseUpdatedSubIfNeeded(expectedEpoch: UInt64) {
|
|
1203
1751
|
listenerLock.withLock {
|
|
1204
|
-
guard
|
|
1205
|
-
connectionEpoch == expectedEpoch,
|
|
1752
|
+
guard connectionEpoch == expectedEpoch,
|
|
1206
1753
|
purchaseUpdatedSub == nil else { return }
|
|
1207
1754
|
RnIapLog.payload("purchaseUpdatedListener.register", nil)
|
|
1208
1755
|
purchaseUpdatedSub = OpenIapModule.shared.purchaseUpdatedListener { [weak self] openIapPurchase in
|
|
@@ -1210,16 +1757,13 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1210
1757
|
RnIapLog.warn("purchaseUpdatedListener: HybridRnIap deallocated, purchase event dropped")
|
|
1211
1758
|
return
|
|
1212
1759
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
}
|
|
1221
|
-
let nitro = RnIapHelper.convertPurchaseDictionary(payload)
|
|
1222
|
-
self.sendPurchaseUpdate(nitro, includeDuplicateListeners: false)
|
|
1760
|
+
let operation = self.enqueuePurchaseUpdateDelivery(
|
|
1761
|
+
openIapPurchase,
|
|
1762
|
+
expectedEpoch: expectedEpoch,
|
|
1763
|
+
includeDuplicateListeners: false
|
|
1764
|
+
)
|
|
1765
|
+
Task {
|
|
1766
|
+
_ = try? await operation.value
|
|
1223
1767
|
}
|
|
1224
1768
|
}
|
|
1225
1769
|
RnIapLog.result("purchaseUpdatedListener.register", "attached")
|
|
@@ -1228,8 +1772,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1228
1772
|
|
|
1229
1773
|
private func attachDuplicatePurchaseUpdatedSubIfNeeded(expectedEpoch: UInt64) {
|
|
1230
1774
|
listenerLock.withLock {
|
|
1231
|
-
guard
|
|
1232
|
-
connectionEpoch == expectedEpoch,
|
|
1775
|
+
guard connectionEpoch == expectedEpoch,
|
|
1233
1776
|
purchaseUpdatedDuplicateSub == nil,
|
|
1234
1777
|
!purchaseUpdatedDuplicateListeners.isEmpty else { return }
|
|
1235
1778
|
RnIapLog.payload("purchaseUpdatedListener.register.duplicates", nil)
|
|
@@ -1241,16 +1784,13 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1241
1784
|
RnIapLog.warn("purchaseUpdatedListener: HybridRnIap deallocated, non-deduping purchase event dropped")
|
|
1242
1785
|
return
|
|
1243
1786
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
}
|
|
1252
|
-
let nitro = RnIapHelper.convertPurchaseDictionary(payload)
|
|
1253
|
-
self.sendPurchaseUpdate(nitro, includeDuplicateListeners: true)
|
|
1787
|
+
let operation = self.enqueuePurchaseUpdateDelivery(
|
|
1788
|
+
openIapPurchase,
|
|
1789
|
+
expectedEpoch: expectedEpoch,
|
|
1790
|
+
includeDuplicateListeners: true
|
|
1791
|
+
)
|
|
1792
|
+
Task {
|
|
1793
|
+
_ = try? await operation.value
|
|
1254
1794
|
}
|
|
1255
1795
|
}, options: options)
|
|
1256
1796
|
RnIapLog.result("purchaseUpdatedListener.register.duplicates", "attached")
|
|
@@ -1284,12 +1824,6 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1284
1824
|
}
|
|
1285
1825
|
}
|
|
1286
1826
|
|
|
1287
|
-
private func ensureConnection() throws {
|
|
1288
|
-
guard isConnectionInitialized() else {
|
|
1289
|
-
throw OpenIapException.make(code: .initConnection, message: "Connection not initialized. Call initConnection() first.")
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
1827
|
private func isConnectionInitialized() -> Bool {
|
|
1294
1828
|
listenerLock.withLock { isInitialized }
|
|
1295
1829
|
}
|
|
@@ -1297,13 +1831,29 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1297
1831
|
private func isCurrentConnection(_ epoch: UInt64) -> Bool {
|
|
1298
1832
|
listenerLock.withLock { isInitialized && connectionEpoch == epoch }
|
|
1299
1833
|
}
|
|
1834
|
+
|
|
1835
|
+
private func isCurrentEpoch(_ epoch: UInt64) -> Bool {
|
|
1836
|
+
listenerLock.withLock { connectionEpoch == epoch }
|
|
1837
|
+
}
|
|
1300
1838
|
|
|
1301
1839
|
private func sendPurchaseUpdate(_ purchase: NitroPurchase, includeDuplicateListeners: Bool) {
|
|
1302
1840
|
let snapshot: [(NitroPurchase) -> Void] = listenerLock.withLock {
|
|
1303
1841
|
if includeDuplicateListeners {
|
|
1842
|
+
if pendingDuplicatePurchaseUpdates.enqueueIfNeeded(
|
|
1843
|
+
hasListeners: !purchaseUpdatedDuplicateListeners.isEmpty,
|
|
1844
|
+
event: purchase
|
|
1845
|
+
) {
|
|
1846
|
+
return []
|
|
1847
|
+
}
|
|
1304
1848
|
return purchaseUpdatedDuplicateListeners.map(\.listener)
|
|
1305
1849
|
}
|
|
1306
1850
|
|
|
1851
|
+
if pendingPurchaseUpdates.enqueueIfNeeded(
|
|
1852
|
+
hasListeners: !purchaseUpdatedListeners.isEmpty,
|
|
1853
|
+
event: purchase
|
|
1854
|
+
) {
|
|
1855
|
+
return []
|
|
1856
|
+
}
|
|
1307
1857
|
return purchaseUpdatedListeners.map(\.listener)
|
|
1308
1858
|
}
|
|
1309
1859
|
|
|
@@ -1312,25 +1862,16 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1312
1862
|
}
|
|
1313
1863
|
}
|
|
1314
1864
|
|
|
1315
|
-
private func sendPurchaseError(
|
|
1865
|
+
private func sendPurchaseError(
|
|
1866
|
+
_ error: NitroPurchaseResult,
|
|
1867
|
+
productId: String? = nil,
|
|
1868
|
+
dedupe: Bool = true
|
|
1869
|
+
) {
|
|
1316
1870
|
let dedupIdentifier = productId
|
|
1317
1871
|
?? (error.purchaseToken?.isEmpty == false ? error.purchaseToken : nil)
|
|
1318
1872
|
?? (error.message.isEmpty ? nil : error.message)
|
|
1319
1873
|
let currentKey = RnIapHelper.makeErrorDedupKey(code: error.code, productId: dedupIdentifier)
|
|
1320
1874
|
|
|
1321
|
-
// Protect error dedup state since sendPurchaseError is called from multiple threads
|
|
1322
|
-
let shouldSkip: Bool = listenerLock.withLock {
|
|
1323
|
-
let now = Date().timeIntervalSince1970
|
|
1324
|
-
let withinWindow = (now - lastPurchaseErrorTimestamp) < 0.15
|
|
1325
|
-
if currentKey == lastPurchaseErrorKey && withinWindow {
|
|
1326
|
-
return true
|
|
1327
|
-
}
|
|
1328
|
-
lastPurchaseErrorKey = currentKey
|
|
1329
|
-
lastPurchaseErrorTimestamp = now
|
|
1330
|
-
return false
|
|
1331
|
-
}
|
|
1332
|
-
if shouldSkip { return }
|
|
1333
|
-
|
|
1334
1875
|
// Ensure we never leak SKU via purchaseToken
|
|
1335
1876
|
let sanitized: NitroPurchaseResult
|
|
1336
1877
|
if let pid = productId, error.purchaseToken == pid {
|
|
@@ -1349,12 +1890,104 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1349
1890
|
} else {
|
|
1350
1891
|
sanitized = error
|
|
1351
1892
|
}
|
|
1352
|
-
|
|
1893
|
+
|
|
1894
|
+
// Protect error dedup state since sendPurchaseError is called from multiple threads
|
|
1895
|
+
let snapshot: [(NitroPurchaseResult) -> Void]? = listenerLock.withLock {
|
|
1896
|
+
if dedupe {
|
|
1897
|
+
let now = Date().timeIntervalSince1970
|
|
1898
|
+
let withinWindow = (now - lastPurchaseErrorTimestamp) < 0.15
|
|
1899
|
+
if currentKey == lastPurchaseErrorKey && withinWindow {
|
|
1900
|
+
return nil
|
|
1901
|
+
}
|
|
1902
|
+
lastPurchaseErrorKey = currentKey
|
|
1903
|
+
lastPurchaseErrorTimestamp = now
|
|
1904
|
+
}
|
|
1905
|
+
if pendingPurchaseErrors.enqueueIfNeeded(
|
|
1906
|
+
hasListeners: !purchaseErrorListeners.isEmpty,
|
|
1907
|
+
event: sanitized
|
|
1908
|
+
) {
|
|
1909
|
+
return []
|
|
1910
|
+
}
|
|
1911
|
+
return Array(purchaseErrorListeners)
|
|
1912
|
+
}
|
|
1913
|
+
guard let snapshot else { return }
|
|
1353
1914
|
for listener in snapshot {
|
|
1354
1915
|
listener(sanitized)
|
|
1355
1916
|
}
|
|
1356
1917
|
}
|
|
1357
1918
|
|
|
1919
|
+
private func schedulePendingPurchaseUpdateFlush(
|
|
1920
|
+
includeDuplicateListeners: Bool,
|
|
1921
|
+
expectedEpoch: UInt64
|
|
1922
|
+
) {
|
|
1923
|
+
let operation: Task<Void, Error> = enqueueLifecycleOperation {
|
|
1924
|
+
await self.flushPendingPurchaseUpdates(
|
|
1925
|
+
includeDuplicateListeners: includeDuplicateListeners,
|
|
1926
|
+
expectedEpoch: expectedEpoch
|
|
1927
|
+
)
|
|
1928
|
+
}
|
|
1929
|
+
Task { _ = try? await operation.value }
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
private func flushPendingPurchaseUpdates(
|
|
1933
|
+
includeDuplicateListeners: Bool,
|
|
1934
|
+
expectedEpoch: UInt64
|
|
1935
|
+
) async {
|
|
1936
|
+
while true {
|
|
1937
|
+
let delivery: (events: [NitroPurchase], listeners: [(NitroPurchase) -> Void])? = listenerLock.withLock {
|
|
1938
|
+
guard connectionEpoch == expectedEpoch else { return nil }
|
|
1939
|
+
let listeners = includeDuplicateListeners
|
|
1940
|
+
? purchaseUpdatedDuplicateListeners.map(\.listener)
|
|
1941
|
+
: purchaseUpdatedListeners.map(\.listener)
|
|
1942
|
+
let buffer = includeDuplicateListeners
|
|
1943
|
+
? pendingDuplicatePurchaseUpdates
|
|
1944
|
+
: pendingPurchaseUpdates
|
|
1945
|
+
guard let events = buffer.takeBatchOrFinish(
|
|
1946
|
+
hasListeners: !listeners.isEmpty
|
|
1947
|
+
) else { return nil }
|
|
1948
|
+
return (events, listeners)
|
|
1949
|
+
}
|
|
1950
|
+
guard let delivery else { return }
|
|
1951
|
+
for event in delivery.events {
|
|
1952
|
+
for listener in delivery.listeners {
|
|
1953
|
+
guard isCurrentEpoch(expectedEpoch) else { return }
|
|
1954
|
+
await MainActor.run { listener(event) }
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
private func schedulePendingPurchaseErrorFlush(expectedEpoch: UInt64) {
|
|
1961
|
+
let operation: Task<Void, Error> = enqueueLifecycleOperation {
|
|
1962
|
+
await self.flushPendingPurchaseErrors(expectedEpoch: expectedEpoch)
|
|
1963
|
+
}
|
|
1964
|
+
Task { _ = try? await operation.value }
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
private func flushPendingPurchaseErrors(expectedEpoch: UInt64) async {
|
|
1968
|
+
while true {
|
|
1969
|
+
let delivery: (events: [NitroPurchaseResult], listeners: [(NitroPurchaseResult) -> Void])? = listenerLock.withLock {
|
|
1970
|
+
guard connectionEpoch == expectedEpoch else { return nil }
|
|
1971
|
+
let listeners = Array(purchaseErrorListeners)
|
|
1972
|
+
guard let events = pendingPurchaseErrors.takeBatchOrFinish(
|
|
1973
|
+
hasListeners: !listeners.isEmpty
|
|
1974
|
+
) else { return nil }
|
|
1975
|
+
return (events, listeners)
|
|
1976
|
+
}
|
|
1977
|
+
guard let delivery else { return }
|
|
1978
|
+
for event in delivery.events {
|
|
1979
|
+
for listener in delivery.listeners {
|
|
1980
|
+
guard isCurrentEpoch(expectedEpoch) else { return }
|
|
1981
|
+
await MainActor.run { listener(event) }
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
func enqueueLifecycleBarrier() -> Task<Void, Error> {
|
|
1988
|
+
enqueueLifecycleOperation {}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1358
1991
|
private func sendPurchaseErrorDedup(_ error: NitroPurchaseResult, productId: String? = nil) {
|
|
1359
1992
|
sendPurchaseError(error, productId: productId)
|
|
1360
1993
|
}
|
|
@@ -1394,6 +2027,13 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1394
2027
|
subscriptionBillingIssueListeners.removeAll()
|
|
1395
2028
|
lastPurchaseErrorKey = nil
|
|
1396
2029
|
lastPurchaseErrorTimestamp = 0
|
|
2030
|
+
deliveredPurchaseUpdateIds.removeAll()
|
|
2031
|
+
pendingDuplicatePurchaseUpdateSuppressions.removeAll()
|
|
2032
|
+
pendingRequestPurchaseErrorSuppressions.removeAll()
|
|
2033
|
+
pendingOnDemandInitErrorSuppressions.removeAll()
|
|
2034
|
+
pendingPurchaseUpdates.clear()
|
|
2035
|
+
pendingDuplicatePurchaseUpdates.clear()
|
|
2036
|
+
pendingPurchaseErrors.clear()
|
|
1397
2037
|
return subscriptions
|
|
1398
2038
|
}
|
|
1399
2039
|
}
|
|
@@ -1484,14 +2124,18 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1484
2124
|
RnIapLog.payload("canPresentExternalPurchaseNoticeIOS", nil)
|
|
1485
2125
|
|
|
1486
2126
|
if #available(iOS 16.0, *) {
|
|
1487
|
-
try self.ensureConnection()
|
|
1488
2127
|
do {
|
|
1489
|
-
let canPresent = try await
|
|
2128
|
+
let canPresent = try await self.runConnectedOperation {
|
|
2129
|
+
try await OpenIapModule.shared.canPresentExternalPurchaseNoticeIOS()
|
|
2130
|
+
}
|
|
1490
2131
|
RnIapLog.result("canPresentExternalPurchaseNoticeIOS", canPresent)
|
|
1491
2132
|
return canPresent
|
|
1492
2133
|
} catch let purchaseError as PurchaseError {
|
|
1493
2134
|
RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: purchaseError)
|
|
1494
2135
|
throw OpenIapException.from(purchaseError)
|
|
2136
|
+
} catch let connectionError as OpenIapException {
|
|
2137
|
+
RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: connectionError)
|
|
2138
|
+
throw connectionError
|
|
1495
2139
|
} catch {
|
|
1496
2140
|
RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: error)
|
|
1497
2141
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -1509,9 +2153,10 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1509
2153
|
RnIapLog.payload("presentExternalPurchaseNoticeSheetIOS", nil)
|
|
1510
2154
|
|
|
1511
2155
|
if #available(iOS 16.0, *) {
|
|
1512
|
-
try self.ensureConnection()
|
|
1513
2156
|
do {
|
|
1514
|
-
let result = try await
|
|
2157
|
+
let result = try await self.runConnectedOperation {
|
|
2158
|
+
try await OpenIapModule.shared.presentExternalPurchaseNoticeSheetIOS()
|
|
2159
|
+
}
|
|
1515
2160
|
|
|
1516
2161
|
// Convert OpenIAP action to Nitro action via raw value
|
|
1517
2162
|
let actionString = result.result.rawValue
|
|
@@ -1533,6 +2178,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1533
2178
|
} catch let purchaseError as PurchaseError {
|
|
1534
2179
|
RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: purchaseError)
|
|
1535
2180
|
throw OpenIapException.from(purchaseError)
|
|
2181
|
+
} catch let connectionError as OpenIapException {
|
|
2182
|
+
RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: connectionError)
|
|
2183
|
+
throw connectionError
|
|
1536
2184
|
} catch {
|
|
1537
2185
|
RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: error)
|
|
1538
2186
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -1550,9 +2198,10 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1550
2198
|
RnIapLog.payload("presentExternalPurchaseLinkIOS", ["url": url])
|
|
1551
2199
|
|
|
1552
2200
|
if #available(iOS 16.0, *) {
|
|
1553
|
-
try self.ensureConnection()
|
|
1554
2201
|
do {
|
|
1555
|
-
let result = try await
|
|
2202
|
+
let result = try await self.runConnectedOperation {
|
|
2203
|
+
try await OpenIapModule.shared.presentExternalPurchaseLinkIOS(url)
|
|
2204
|
+
}
|
|
1556
2205
|
let nitroResult = ExternalPurchaseLinkResultIOS(
|
|
1557
2206
|
error: RnIapHelper.wrapString(result.error),
|
|
1558
2207
|
success: result.success
|
|
@@ -1562,6 +2211,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1562
2211
|
} catch let purchaseError as PurchaseError {
|
|
1563
2212
|
RnIapLog.failure("presentExternalPurchaseLinkIOS", error: purchaseError)
|
|
1564
2213
|
throw OpenIapException.from(purchaseError)
|
|
2214
|
+
} catch let connectionError as OpenIapException {
|
|
2215
|
+
RnIapLog.failure("presentExternalPurchaseLinkIOS", error: connectionError)
|
|
2216
|
+
throw connectionError
|
|
1565
2217
|
} catch {
|
|
1566
2218
|
RnIapLog.failure("presentExternalPurchaseLinkIOS", error: error)
|
|
1567
2219
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -1580,12 +2232,17 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1580
2232
|
return Promise.async {
|
|
1581
2233
|
RnIapLog.payload("isEligibleForExternalPurchaseCustomLinkIOS", nil)
|
|
1582
2234
|
do {
|
|
1583
|
-
let isEligible = try await
|
|
2235
|
+
let isEligible = try await self.runConnectedOperation {
|
|
2236
|
+
try await OpenIapModule.shared.isEligibleForExternalPurchaseCustomLinkIOS()
|
|
2237
|
+
}
|
|
1584
2238
|
RnIapLog.result("isEligibleForExternalPurchaseCustomLinkIOS", isEligible)
|
|
1585
2239
|
return isEligible
|
|
1586
2240
|
} catch let purchaseError as PurchaseError {
|
|
1587
2241
|
RnIapLog.failure("isEligibleForExternalPurchaseCustomLinkIOS", error: purchaseError)
|
|
1588
2242
|
throw OpenIapException.from(purchaseError)
|
|
2243
|
+
} catch let connectionError as OpenIapException {
|
|
2244
|
+
RnIapLog.failure("isEligibleForExternalPurchaseCustomLinkIOS", error: connectionError)
|
|
2245
|
+
throw connectionError
|
|
1589
2246
|
} catch {
|
|
1590
2247
|
RnIapLog.failure("isEligibleForExternalPurchaseCustomLinkIOS", error: error)
|
|
1591
2248
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -1601,7 +2258,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1601
2258
|
guard let openIapTokenType = OpenIAP.ExternalPurchaseCustomLinkTokenTypeIOS(rawValue: tokenType.stringValue) else {
|
|
1602
2259
|
throw OpenIapException.make(code: .developerError, message: "Invalid token type: \(tokenType.stringValue). Must be 'acquisition' or 'services'")
|
|
1603
2260
|
}
|
|
1604
|
-
let result = try await
|
|
2261
|
+
let result = try await self.runConnectedOperation {
|
|
2262
|
+
try await OpenIapModule.shared.getExternalPurchaseCustomLinkTokenIOS(openIapTokenType)
|
|
2263
|
+
}
|
|
1605
2264
|
let nitroResult = ExternalPurchaseCustomLinkTokenResultIOS(
|
|
1606
2265
|
error: RnIapHelper.wrapString(result.error),
|
|
1607
2266
|
token: RnIapHelper.wrapString(result.token)
|
|
@@ -1615,6 +2274,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1615
2274
|
} catch let purchaseError as PurchaseError {
|
|
1616
2275
|
RnIapLog.failure("getExternalPurchaseCustomLinkTokenIOS", error: purchaseError)
|
|
1617
2276
|
throw OpenIapException.from(purchaseError)
|
|
2277
|
+
} catch let connectionError as OpenIapException {
|
|
2278
|
+
RnIapLog.failure("getExternalPurchaseCustomLinkTokenIOS", error: connectionError)
|
|
2279
|
+
throw connectionError
|
|
1618
2280
|
} catch {
|
|
1619
2281
|
RnIapLog.failure("getExternalPurchaseCustomLinkTokenIOS", error: error)
|
|
1620
2282
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
@@ -1637,7 +2299,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1637
2299
|
} else {
|
|
1638
2300
|
throw OpenIapException.make(code: .developerError, message: "Invalid notice type: \(noticeType.stringValue). Must be 'browser'")
|
|
1639
2301
|
}
|
|
1640
|
-
let result = try await
|
|
2302
|
+
let result = try await self.runConnectedOperation {
|
|
2303
|
+
try await OpenIapModule.shared.showExternalPurchaseCustomLinkNoticeIOS(openIapNoticeType)
|
|
2304
|
+
}
|
|
1641
2305
|
let nitroResult = ExternalPurchaseCustomLinkNoticeResultIOS(
|
|
1642
2306
|
continued: result.continued,
|
|
1643
2307
|
error: RnIapHelper.wrapString(result.error)
|
|
@@ -1647,6 +2311,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1647
2311
|
} catch let purchaseError as PurchaseError {
|
|
1648
2312
|
RnIapLog.failure("showExternalPurchaseCustomLinkNoticeIOS", error: purchaseError)
|
|
1649
2313
|
throw OpenIapException.from(purchaseError)
|
|
2314
|
+
} catch let connectionError as OpenIapException {
|
|
2315
|
+
RnIapLog.failure("showExternalPurchaseCustomLinkNoticeIOS", error: connectionError)
|
|
2316
|
+
throw connectionError
|
|
1650
2317
|
} catch {
|
|
1651
2318
|
RnIapLog.failure("showExternalPurchaseCustomLinkNoticeIOS", error: error)
|
|
1652
2319
|
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|