react-native-iap 15.3.5 → 15.4.0-rc.1
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/README.md +1 -1
- package/android/build.gradle +13 -5
- package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +16 -3
- package/ios/HybridRnIap.swift +12 -0
- package/lib/module/index.js +93 -75
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.kepler.js +170 -0
- package/lib/module/index.kepler.js.map +1 -0
- package/lib/module/types/amazon-devices-kepler/index.d.js +2 -0
- package/lib/module/types/amazon-devices-kepler/index.d.js.map +1 -0
- package/lib/module/types.js +5 -2
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/error.js +98 -15
- package/lib/module/utils/error.js.map +1 -1
- package/lib/module/utils/type-bridge.js +4 -1
- package/lib/module/utils/type-bridge.js.map +1 -1
- package/lib/module/vega-adapter.js +981 -0
- package/lib/module/vega-adapter.js.map +1 -0
- package/lib/module/vega.js +10 -0
- package/lib/module/vega.js.map +1 -0
- package/lib/module/vega.kepler.js +18 -0
- package/lib/module/vega.kepler.js.map +1 -0
- package/lib/typescript/plugin/src/withIAP.d.ts +33 -0
- package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +9 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.kepler.d.ts +55 -0
- package/lib/typescript/src/index.kepler.d.ts.map +1 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +10 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +16 -3
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/error.d.ts +1 -1
- package/lib/typescript/src/utils/error.d.ts.map +1 -1
- package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
- package/lib/typescript/src/vega-adapter.d.ts +76 -0
- package/lib/typescript/src/vega-adapter.d.ts.map +1 -0
- package/lib/typescript/src/vega.d.ts +4 -0
- package/lib/typescript/src/vega.d.ts.map +1 -0
- package/lib/typescript/src/vega.kepler.d.ts +4 -0
- package/lib/typescript/src/vega.kepler.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +5 -0
- package/nitrogen/generated/android/c++/JIapStore.hpp +3 -0
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitAmazonProps.hpp +71 -0
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitProps.hpp +9 -1
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithProviderProps.hpp +4 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.hpp +75 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitProps.hpp +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/IapStore.kt +2 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitAmazonProps.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitProps.kt +5 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.kt +53 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +47 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +3 -0
- package/nitrogen/generated/ios/swift/IapStore.swift +4 -0
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitAmazonProps.swift +103 -0
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitProps.swift +38 -1
- package/nitrogen/generated/ios/swift/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.swift +18 -0
- package/nitrogen/generated/shared/c++/IapStore.hpp +4 -0
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitAmazonProps.hpp +94 -0
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitProps.hpp +8 -1
- package/openiap-versions.json +2 -2
- package/package.json +17 -4
- package/plugin/build/withIAP.d.ts +33 -0
- package/plugin/build/withIAP.js +109 -42
- package/plugin/src/withIAP.ts +185 -55
- package/src/index.kepler.ts +311 -0
- package/src/index.ts +187 -78
- package/src/specs/RnIap.nitro.ts +13 -2
- package/src/types/amazon-devices-kepler/index.d.ts +14 -0
- package/src/types.ts +17 -3
- package/src/utils/error.ts +127 -14
- package/src/utils/type-bridge.ts +4 -1
- package/src/vega-adapter.ts +1503 -0
- package/src/vega.kepler.ts +21 -0
- package/src/vega.ts +10 -0
- package/android/src/test/java/com/margelo/nitro/iap/ListenerThreadSafetyTest.kt +0 -154
- package/android/src/test/java/com/margelo/nitro/iap/OpenIapExceptionTest.kt +0 -93
- package/android/src/test/java/com/margelo/nitro/iap/ProductQueryHelpersTest.kt +0 -140
- package/plugin/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {Platform} from 'react-native';
|
|
2
|
+
// eslint-disable-next-line import/no-unresolved
|
|
3
|
+
import {PurchasingService} from '@amazon-devices/keplerscript-appstore-iap-lib';
|
|
4
|
+
import type {RnIap} from './specs/RnIap.nitro';
|
|
5
|
+
import {createVegaIapModule, type VegaPurchasingService} from './vega-adapter';
|
|
6
|
+
|
|
7
|
+
let cachedVegaModule: RnIap | null = null;
|
|
8
|
+
|
|
9
|
+
export const isVegaOS = (): boolean => {
|
|
10
|
+
return String(Platform.OS).toLowerCase() === 'kepler';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getVegaIapModule = (): RnIap | null => {
|
|
14
|
+
if (!isVegaOS()) return null;
|
|
15
|
+
if (!cachedVegaModule) {
|
|
16
|
+
cachedVegaModule = createVegaIapModule(
|
|
17
|
+
PurchasingService as unknown as VegaPurchasingService,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return cachedVegaModule;
|
|
21
|
+
};
|
package/src/vega.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {Platform} from 'react-native';
|
|
2
|
+
import type {RnIap} from './specs/RnIap.nitro';
|
|
3
|
+
|
|
4
|
+
export const isVegaOS = (): boolean => {
|
|
5
|
+
return String(Platform.OS).toLowerCase() === 'kepler';
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const getVegaIapModule = (): RnIap | null => {
|
|
9
|
+
return null;
|
|
10
|
+
};
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.iap
|
|
2
|
-
|
|
3
|
-
import org.junit.Test
|
|
4
|
-
import org.junit.Assert.*
|
|
5
|
-
import java.util.concurrent.CyclicBarrier
|
|
6
|
-
import java.util.concurrent.atomic.AtomicInteger
|
|
7
|
-
import java.util.concurrent.atomic.AtomicReference
|
|
8
|
-
import kotlin.concurrent.thread
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Thread safety tests for the synchronized + snapshot listener pattern
|
|
12
|
-
* used in HybridRnIap to prevent ConcurrentModificationException.
|
|
13
|
-
*
|
|
14
|
-
* Addresses Issue #3150 where purchase events were silently lost
|
|
15
|
-
* due to concurrent listener access.
|
|
16
|
-
*/
|
|
17
|
-
class ListenerThreadSafetyTest {
|
|
18
|
-
|
|
19
|
-
@Test
|
|
20
|
-
fun `concurrent add and snapshot iterate does not throw`() {
|
|
21
|
-
val listeners = mutableListOf<(String) -> Unit>()
|
|
22
|
-
val callCount = AtomicInteger(0)
|
|
23
|
-
val errorRef = AtomicReference<Throwable?>(null)
|
|
24
|
-
val iterations = 500
|
|
25
|
-
val barrier = CyclicBarrier(2)
|
|
26
|
-
|
|
27
|
-
val adder = thread {
|
|
28
|
-
barrier.await()
|
|
29
|
-
repeat(iterations) {
|
|
30
|
-
synchronized(listeners) { listeners.add { callCount.incrementAndGet() } }
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
val sender = thread {
|
|
35
|
-
barrier.await()
|
|
36
|
-
repeat(iterations) {
|
|
37
|
-
val snapshot = synchronized(listeners) { ArrayList(listeners) }
|
|
38
|
-
snapshot.forEach {
|
|
39
|
-
try {
|
|
40
|
-
it("event")
|
|
41
|
-
} catch (e: Throwable) {
|
|
42
|
-
errorRef.compareAndSet(null, e)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
adder.join(5000)
|
|
49
|
-
sender.join(5000)
|
|
50
|
-
assertFalse("Adder thread did not finish", adder.isAlive)
|
|
51
|
-
assertFalse("Sender thread did not finish", sender.isAlive)
|
|
52
|
-
assertNull("Should not throw ConcurrentModificationException", errorRef.get())
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@Test
|
|
56
|
-
fun `concurrent add, remove, and iterate is safe`() {
|
|
57
|
-
val listeners = mutableListOf<(String) -> Unit>()
|
|
58
|
-
val errorRef = AtomicReference<Throwable?>(null)
|
|
59
|
-
val barrier = CyclicBarrier(3)
|
|
60
|
-
|
|
61
|
-
val adder = thread {
|
|
62
|
-
barrier.await()
|
|
63
|
-
repeat(200) {
|
|
64
|
-
synchronized(listeners) { listeners.add { _ -> } }
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
val remover = thread {
|
|
69
|
-
barrier.await()
|
|
70
|
-
repeat(200) {
|
|
71
|
-
synchronized(listeners) {
|
|
72
|
-
if (listeners.isNotEmpty()) listeners.removeAt(0)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
val sender = thread {
|
|
78
|
-
barrier.await()
|
|
79
|
-
repeat(200) {
|
|
80
|
-
val snapshot = synchronized(listeners) { ArrayList(listeners) }
|
|
81
|
-
snapshot.forEach {
|
|
82
|
-
try {
|
|
83
|
-
it("event")
|
|
84
|
-
} catch (e: Throwable) {
|
|
85
|
-
errorRef.compareAndSet(null, e)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
adder.join(5000)
|
|
92
|
-
remover.join(5000)
|
|
93
|
-
sender.join(5000)
|
|
94
|
-
assertFalse("Adder thread did not finish", adder.isAlive)
|
|
95
|
-
assertFalse("Remover thread did not finish", remover.isAlive)
|
|
96
|
-
assertFalse("Sender thread did not finish", sender.isAlive)
|
|
97
|
-
assertNull("Concurrent access should be safe with snapshot pattern", errorRef.get())
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@Test
|
|
101
|
-
fun `snapshot delivers to all registered listeners`() {
|
|
102
|
-
val listeners = mutableListOf<(String) -> Unit>()
|
|
103
|
-
val results = mutableListOf<String>()
|
|
104
|
-
|
|
105
|
-
synchronized(listeners) {
|
|
106
|
-
listeners.add { results.add("listener1:$it") }
|
|
107
|
-
listeners.add { results.add("listener2:$it") }
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
val snapshot = synchronized(listeners) { ArrayList(listeners) }
|
|
111
|
-
snapshot.forEach { it("event") }
|
|
112
|
-
|
|
113
|
-
assertEquals(2, results.size)
|
|
114
|
-
assertTrue(results.contains("listener1:event"))
|
|
115
|
-
assertTrue(results.contains("listener2:event"))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
@Test
|
|
119
|
-
fun `synchronized clear removes all listeners`() {
|
|
120
|
-
val listeners = mutableListOf<(String) -> Unit>()
|
|
121
|
-
synchronized(listeners) {
|
|
122
|
-
listeners.add { _ -> }
|
|
123
|
-
listeners.add { _ -> }
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
synchronized(listeners) { listeners.clear() }
|
|
127
|
-
|
|
128
|
-
val snapshot = synchronized(listeners) { ArrayList(listeners) }
|
|
129
|
-
assertTrue("Listeners should be empty after clear", snapshot.isEmpty())
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
@Test
|
|
133
|
-
fun `snapshot is isolated from subsequent modifications`() {
|
|
134
|
-
val listeners = mutableListOf<(String) -> Unit>()
|
|
135
|
-
val results = mutableListOf<String>()
|
|
136
|
-
|
|
137
|
-
synchronized(listeners) {
|
|
138
|
-
listeners.add { results.add("original:$it") }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Take snapshot before adding more listeners
|
|
142
|
-
val snapshot = synchronized(listeners) { ArrayList(listeners) }
|
|
143
|
-
|
|
144
|
-
// Add another listener after snapshot
|
|
145
|
-
synchronized(listeners) {
|
|
146
|
-
listeners.add { results.add("added-after:$it") }
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Only the original listener should be in the snapshot
|
|
150
|
-
snapshot.forEach { it("event") }
|
|
151
|
-
assertEquals(1, results.size)
|
|
152
|
-
assertEquals("original:event", results[0])
|
|
153
|
-
}
|
|
154
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.iap
|
|
2
|
-
|
|
3
|
-
import org.junit.Test
|
|
4
|
-
import org.junit.Assert.*
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Unit tests for OpenIapException to verify that error messages
|
|
8
|
-
* are returned without Java/Kotlin stack traces.
|
|
9
|
-
*
|
|
10
|
-
* This addresses Issue #3075 where error.code was undefined because
|
|
11
|
-
* stack traces were breaking JSON parsing on the JavaScript side.
|
|
12
|
-
*/
|
|
13
|
-
class OpenIapExceptionTest {
|
|
14
|
-
|
|
15
|
-
@Test
|
|
16
|
-
fun `test OpenIapException message returns clean JSON`() {
|
|
17
|
-
val errorJson = """{"code":"init-connection","message":"Failed to initialize connection"}"""
|
|
18
|
-
val exception = OpenIapException(errorJson)
|
|
19
|
-
|
|
20
|
-
// Verify message is exactly the JSON string
|
|
21
|
-
assertEquals(errorJson, exception.message)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
@Test
|
|
25
|
-
fun `test OpenIapException toString returns clean JSON without stack trace`() {
|
|
26
|
-
val errorJson = """{"code":"user-cancelled","message":"User cancelled"}"""
|
|
27
|
-
val exception = OpenIapException(errorJson)
|
|
28
|
-
|
|
29
|
-
// toString() should return only the JSON, not "java.lang.Exception: ..."
|
|
30
|
-
val result = exception.toString()
|
|
31
|
-
assertEquals(errorJson, result)
|
|
32
|
-
assertFalse(result.startsWith("java.lang.Exception:"))
|
|
33
|
-
assertFalse(result.contains("\tat "))
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@Test
|
|
37
|
-
fun `test thrown OpenIapException message is clean`() {
|
|
38
|
-
val errorJson = """{"code":"network-error","message":"Network error occurred","responseCode":-1}"""
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
throw OpenIapException(errorJson)
|
|
42
|
-
} catch (e: Exception) {
|
|
43
|
-
// Verify the caught exception message is clean
|
|
44
|
-
assertEquals(errorJson, e.message)
|
|
45
|
-
assertEquals(errorJson, e.toString())
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@Test
|
|
50
|
-
fun `test OpenIapException with complex JSON structure`() {
|
|
51
|
-
val errorJson = """{"code":"purchase-error","message":"Purchase failed","responseCode":3,"debugMessage":"Item unavailable","productId":"com.test.product"}"""
|
|
52
|
-
val exception = OpenIapException(errorJson)
|
|
53
|
-
|
|
54
|
-
assertEquals(errorJson, exception.message)
|
|
55
|
-
assertEquals(errorJson, exception.toString())
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@Test
|
|
59
|
-
fun `test multiple OpenIapException instances are independent`() {
|
|
60
|
-
val error1 = """{"code":"error-1","message":"First error"}"""
|
|
61
|
-
val error2 = """{"code":"error-2","message":"Second error"}"""
|
|
62
|
-
|
|
63
|
-
val exception1 = OpenIapException(error1)
|
|
64
|
-
val exception2 = OpenIapException(error2)
|
|
65
|
-
|
|
66
|
-
assertEquals(error1, exception1.message)
|
|
67
|
-
assertEquals(error2, exception2.message)
|
|
68
|
-
assertNotEquals(exception1.message, exception2.message)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@Test
|
|
72
|
-
fun `test OpenIapException with empty JSON`() {
|
|
73
|
-
val errorJson = """{}"""
|
|
74
|
-
val exception = OpenIapException(errorJson)
|
|
75
|
-
|
|
76
|
-
assertEquals(errorJson, exception.message)
|
|
77
|
-
assertEquals(errorJson, exception.toString())
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
@Test
|
|
81
|
-
fun `test OpenIapException message does not contain stack trace keywords`() {
|
|
82
|
-
val errorJson = """{"code":"test-error","message":"Test message"}"""
|
|
83
|
-
val exception = OpenIapException(errorJson)
|
|
84
|
-
|
|
85
|
-
val result = exception.toString()
|
|
86
|
-
|
|
87
|
-
// Verify no stack trace keywords are present
|
|
88
|
-
assertFalse("Should not contain 'at ' (stack trace)", result.contains("\tat "))
|
|
89
|
-
assertFalse("Should not contain 'java.lang.Exception:'", result.contains("java.lang.Exception:"))
|
|
90
|
-
assertFalse("Should not contain '.kt:' (Kotlin file reference)", result.contains(".kt:"))
|
|
91
|
-
assertFalse("Should not contain '.java:' (Java file reference)", result.contains(".java:"))
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.iap
|
|
2
|
-
|
|
3
|
-
import dev.hyo.openiap.IapPlatform
|
|
4
|
-
import dev.hyo.openiap.OpenIapError
|
|
5
|
-
import dev.hyo.openiap.ProductCommon
|
|
6
|
-
import dev.hyo.openiap.ProductQueryType
|
|
7
|
-
import dev.hyo.openiap.ProductType
|
|
8
|
-
import java.util.Collections
|
|
9
|
-
import kotlinx.coroutines.CompletableDeferred
|
|
10
|
-
import kotlinx.coroutines.runBlocking
|
|
11
|
-
import kotlinx.coroutines.withTimeout
|
|
12
|
-
import org.junit.Assert.assertEquals
|
|
13
|
-
import org.junit.Assert.assertSame
|
|
14
|
-
import org.junit.Test
|
|
15
|
-
|
|
16
|
-
class ProductQueryHelpersTest {
|
|
17
|
-
@Test
|
|
18
|
-
fun `all query starts in-app and subs fetches concurrently`() = runBlocking {
|
|
19
|
-
val startedKinds = Collections.synchronizedSet(mutableSetOf<ProductQueryType>())
|
|
20
|
-
val bothStarted = CompletableDeferred<Unit>()
|
|
21
|
-
|
|
22
|
-
val products = withTimeout(1000) {
|
|
23
|
-
collectAllQueryProducts(
|
|
24
|
-
skusList = listOf("monthly", "lifetime"),
|
|
25
|
-
fetchKind = { kind ->
|
|
26
|
-
when (kind) {
|
|
27
|
-
ProductQueryType.InApp,
|
|
28
|
-
ProductQueryType.Subs -> {
|
|
29
|
-
startedKinds.add(kind)
|
|
30
|
-
if (startedKinds.size == 2) {
|
|
31
|
-
bothStarted.complete(Unit)
|
|
32
|
-
}
|
|
33
|
-
bothStarted.await()
|
|
34
|
-
|
|
35
|
-
when (kind) {
|
|
36
|
-
ProductQueryType.InApp -> listOf(
|
|
37
|
-
fakeProduct("lifetime", ProductType.InApp),
|
|
38
|
-
)
|
|
39
|
-
ProductQueryType.Subs -> listOf(
|
|
40
|
-
fakeProduct("monthly", ProductType.Subs),
|
|
41
|
-
)
|
|
42
|
-
ProductQueryType.All -> error("All should be expanded by the helper")
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
ProductQueryType.All -> error("All should be expanded by the helper")
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
assertEquals(setOf(ProductQueryType.InApp, ProductQueryType.Subs), startedKinds.toSet())
|
|
52
|
-
assertEquals(listOf("monthly", "lifetime"), products.map { it.id })
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@Test
|
|
56
|
-
fun `all query returns partial success when one product kind fails`() = runBlocking {
|
|
57
|
-
val queryError = OpenIapError.BillingError("Invalid subscriptions")
|
|
58
|
-
val failures = mutableListOf<Pair<ProductQueryType, Throwable>>()
|
|
59
|
-
|
|
60
|
-
val products = collectAllQueryProducts(
|
|
61
|
-
skusList = listOf("monthly", "lifetime"),
|
|
62
|
-
fetchKind = { kind ->
|
|
63
|
-
when (kind) {
|
|
64
|
-
ProductQueryType.InApp -> listOf(fakeProduct("lifetime", ProductType.InApp))
|
|
65
|
-
ProductQueryType.Subs -> throw queryError
|
|
66
|
-
ProductQueryType.All -> error("All should be expanded by the helper")
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
onFailure = { kind, error -> failures += kind to error },
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
assertEquals(listOf("lifetime"), products.map { it.id })
|
|
73
|
-
assertEquals(listOf(ProductQueryType.Subs), failures.map { it.first })
|
|
74
|
-
assertSame(queryError, failures.single().second)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
@Test
|
|
78
|
-
fun `all query rethrows first failure when both product kinds fail`() = runBlocking {
|
|
79
|
-
val firstError = OpenIapError.BillingError("Invalid in-app products")
|
|
80
|
-
val secondError = OpenIapError.BillingError("Service unavailable")
|
|
81
|
-
val failures = mutableListOf<ProductQueryType>()
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
collectAllQueryProducts(
|
|
85
|
-
skusList = listOf("monthly", "lifetime"),
|
|
86
|
-
fetchKind = { kind ->
|
|
87
|
-
when (kind) {
|
|
88
|
-
ProductQueryType.InApp -> throw firstError
|
|
89
|
-
ProductQueryType.Subs -> throw secondError
|
|
90
|
-
ProductQueryType.All -> error("All should be expanded by the helper")
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
onFailure = { kind, _ -> failures += kind },
|
|
94
|
-
)
|
|
95
|
-
} catch (error: Throwable) {
|
|
96
|
-
assertSame(firstError, error)
|
|
97
|
-
assertEquals(listOf(ProductQueryType.InApp, ProductQueryType.Subs), failures)
|
|
98
|
-
return@runBlocking
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
error("Expected the first product query failure to be rethrown")
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
@Test
|
|
105
|
-
fun `all query preserves input sku order and keeps first matching product`() = runBlocking {
|
|
106
|
-
val products = collectAllQueryProducts(
|
|
107
|
-
skusList = listOf("monthly", "lifetime", "annual"),
|
|
108
|
-
fetchKind = { kind ->
|
|
109
|
-
when (kind) {
|
|
110
|
-
ProductQueryType.InApp -> listOf(
|
|
111
|
-
fakeProduct("lifetime", ProductType.InApp),
|
|
112
|
-
fakeProduct("monthly", ProductType.InApp),
|
|
113
|
-
)
|
|
114
|
-
ProductQueryType.Subs -> listOf(
|
|
115
|
-
fakeProduct("monthly", ProductType.Subs),
|
|
116
|
-
fakeProduct("annual", ProductType.Subs),
|
|
117
|
-
)
|
|
118
|
-
ProductQueryType.All -> error("All should be expanded by the helper")
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
assertEquals(listOf("monthly", "lifetime", "annual"), products.map { it.id })
|
|
124
|
-
assertEquals(ProductType.InApp, products[0].type)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private fun fakeProduct(productId: String, type: ProductType): ProductCommon =
|
|
128
|
-
object : ProductCommon {
|
|
129
|
-
override val currency: String = "USD"
|
|
130
|
-
override val debugDescription: String? = null
|
|
131
|
-
override val description: String = productId
|
|
132
|
-
override val displayName: String? = productId
|
|
133
|
-
override val displayPrice: String = "$1.00"
|
|
134
|
-
override val id: String = productId
|
|
135
|
-
override val platform: IapPlatform = IapPlatform.Android
|
|
136
|
-
override val price: Double? = 1.0
|
|
137
|
-
override val title: String = productId
|
|
138
|
-
override val type: ProductType = type
|
|
139
|
-
}
|
|
140
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["./src/withIAP.ts"],"version":"5.9.2"}
|