react-native-revnix 1.4.2 → 1.6.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/README.md +138 -713
- package/android/build.gradle +34 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/com/revnix/reactnative/RevnixStoreModule.kt +247 -0
- package/android/src/main/kotlin/com/revnix/reactnative/RevnixStorePackage.kt +14 -0
- package/dist/client.d.ts +58 -11
- package/dist/client.js +403 -114
- package/dist/esm/client.js +402 -114
- package/dist/esm/index.js +1 -0
- package/dist/esm/store/index.js +22 -0
- package/dist/esm/store.js +9 -0
- package/dist/esm/ui/RevnixPaywall.js +42 -8
- package/dist/esm/ui/index.js +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.js +25 -0
- package/dist/store.d.ts +30 -0
- package/dist/store.js +13 -0
- package/dist/ui/RevnixPaywall.d.ts +17 -3
- package/dist/ui/RevnixPaywall.js +43 -8
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/RevnixStore.m +18 -0
- package/ios/RevnixStore.swift +106 -0
- package/package.json +20 -2
- package/react-native-revnix.podspec +22 -0
- package/src/client.ts +475 -146
- package/src/index.ts +5 -0
- package/src/store/index.ts +44 -0
- package/src/store.ts +41 -0
- package/src/ui/RevnixPaywall.tsx +68 -17
- package/src/ui/index.ts +1 -0
- package/src/version.ts +1 -1
- package/store.d.ts +1 -0
- package/store.js +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
apply plugin: "com.android.library"
|
|
2
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
3
|
+
|
|
4
|
+
def safeExtGet(prop, fallback) {
|
|
5
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
android {
|
|
9
|
+
namespace "com.revnix.reactnative"
|
|
10
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 35)
|
|
11
|
+
|
|
12
|
+
defaultConfig {
|
|
13
|
+
minSdkVersion safeExtGet("minSdkVersion", 24)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
compileOptions {
|
|
17
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
18
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
kotlinOptions {
|
|
22
|
+
jvmTarget = "17"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
repositories {
|
|
27
|
+
google()
|
|
28
|
+
mavenCentral()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
dependencies {
|
|
32
|
+
implementation "com.facebook.react:react-native:+"
|
|
33
|
+
implementation "com.android.billingclient:billing:8.3.0"
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest />
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
package com.revnix.reactnative
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.android.billingclient.api.AcknowledgePurchaseParams
|
|
5
|
+
import com.android.billingclient.api.BillingClient
|
|
6
|
+
import com.android.billingclient.api.BillingClient.BillingResponseCode
|
|
7
|
+
import com.android.billingclient.api.BillingClient.ProductType
|
|
8
|
+
import com.android.billingclient.api.BillingClientStateListener
|
|
9
|
+
import com.android.billingclient.api.BillingFlowParams
|
|
10
|
+
import com.android.billingclient.api.BillingResult
|
|
11
|
+
import com.android.billingclient.api.PendingPurchasesParams
|
|
12
|
+
import com.android.billingclient.api.ProductDetails
|
|
13
|
+
import com.android.billingclient.api.Purchase
|
|
14
|
+
import com.android.billingclient.api.PurchasesUpdatedListener
|
|
15
|
+
import com.android.billingclient.api.QueryProductDetailsParams
|
|
16
|
+
import com.android.billingclient.api.QueryPurchasesParams
|
|
17
|
+
import com.facebook.react.bridge.Arguments
|
|
18
|
+
import com.facebook.react.bridge.Promise
|
|
19
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
20
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
21
|
+
import com.facebook.react.bridge.ReactMethod
|
|
22
|
+
import com.facebook.react.bridge.WritableMap
|
|
23
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
24
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
25
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
26
|
+
|
|
27
|
+
class RevnixStoreModule(context: ReactApplicationContext) :
|
|
28
|
+
ReactContextBaseJavaModule(context), PurchasesUpdatedListener {
|
|
29
|
+
|
|
30
|
+
private class PendingPurchase(val productId: String, val promise: Promise)
|
|
31
|
+
|
|
32
|
+
private val pending = AtomicReference<PendingPurchase?>(null)
|
|
33
|
+
private val acknowledged = ConcurrentHashMap.newKeySet<String>()
|
|
34
|
+
private val waitingForSetup = mutableListOf<(BillingResult) -> Unit>()
|
|
35
|
+
|
|
36
|
+
private val billing: BillingClient = BillingClient.newBuilder(context)
|
|
37
|
+
.setListener(this)
|
|
38
|
+
.enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
|
|
39
|
+
.enableAutoServiceReconnection()
|
|
40
|
+
.build()
|
|
41
|
+
|
|
42
|
+
override fun getName() = "RevnixStore"
|
|
43
|
+
|
|
44
|
+
override fun invalidate() {
|
|
45
|
+
billing.endConnection()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@ReactMethod
|
|
49
|
+
fun addListener(eventName: String) {
|
|
50
|
+
withBilling {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@ReactMethod
|
|
54
|
+
fun removeListeners(count: Int) {}
|
|
55
|
+
|
|
56
|
+
@ReactMethod
|
|
57
|
+
fun purchase(productId: String, customerId: String, promise: Promise) {
|
|
58
|
+
withBilling { setup ->
|
|
59
|
+
if (setup.responseCode != BillingResponseCode.OK) {
|
|
60
|
+
promise.reject("store_error", setup.debugMessage)
|
|
61
|
+
return@withBilling
|
|
62
|
+
}
|
|
63
|
+
queryProduct(productId, ProductType.SUBS) { subs ->
|
|
64
|
+
if (subs != null) {
|
|
65
|
+
launch(subs, customerId, promise)
|
|
66
|
+
return@queryProduct
|
|
67
|
+
}
|
|
68
|
+
queryProduct(productId, ProductType.INAPP) { inapp ->
|
|
69
|
+
if (inapp != null) launch(inapp, customerId, promise)
|
|
70
|
+
else promise.reject("product_not_found", "No Play product with id $productId")
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@ReactMethod
|
|
77
|
+
fun restore(promise: Promise) {
|
|
78
|
+
withBilling { setup ->
|
|
79
|
+
if (setup.responseCode != BillingResponseCode.OK) {
|
|
80
|
+
promise.reject("store_error", setup.debugMessage)
|
|
81
|
+
return@withBilling
|
|
82
|
+
}
|
|
83
|
+
val restored = Arguments.createArray()
|
|
84
|
+
queryPurchases(ProductType.SUBS) { subs ->
|
|
85
|
+
subs.forEach { restored.pushMap(toMap(it)) }
|
|
86
|
+
queryPurchases(ProductType.INAPP) { inapp ->
|
|
87
|
+
inapp.forEach { restored.pushMap(toMap(it)) }
|
|
88
|
+
promise.resolve(restored)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ponytail: acknowledges every purchase; consumable INAPP products need consumeAsync to be bought again
|
|
95
|
+
@ReactMethod
|
|
96
|
+
fun finish(purchaseToken: String, promise: Promise) {
|
|
97
|
+
if (purchaseToken in acknowledged) {
|
|
98
|
+
promise.resolve(null)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
withBilling { setup ->
|
|
102
|
+
if (setup.responseCode != BillingResponseCode.OK) {
|
|
103
|
+
promise.reject("store_error", "${setup.responseCode} ${setup.debugMessage}")
|
|
104
|
+
return@withBilling
|
|
105
|
+
}
|
|
106
|
+
val params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build()
|
|
107
|
+
billing.acknowledgePurchase(params) { result ->
|
|
108
|
+
if (result.responseCode == BillingResponseCode.OK) {
|
|
109
|
+
acknowledged.add(purchaseToken)
|
|
110
|
+
promise.resolve(null)
|
|
111
|
+
} else {
|
|
112
|
+
promise.reject("store_error", "acknowledgePurchase failed: ${result.responseCode} ${result.debugMessage}")
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
|
|
119
|
+
if (result.responseCode != BillingResponseCode.OK) {
|
|
120
|
+
settle(pending.getAndSet(null) ?: return, result)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
for (purchase in purchases.orEmpty()) {
|
|
124
|
+
val waiting = pending.get()
|
|
125
|
+
if (waiting != null && purchase.products.contains(waiting.productId) &&
|
|
126
|
+
pending.compareAndSet(waiting, null)
|
|
127
|
+
) {
|
|
128
|
+
val purchased = purchase.purchaseState == Purchase.PurchaseState.PURCHASED
|
|
129
|
+
waiting.promise.resolve(if (purchased) toMap(purchase) else null)
|
|
130
|
+
} else if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
|
|
131
|
+
emit(toMap(purchase))
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun withBilling(onReady: (BillingResult) -> Unit) {
|
|
137
|
+
if (billing.isReady) {
|
|
138
|
+
onReady(BillingResult.newBuilder().setResponseCode(BillingResponseCode.OK).build())
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
synchronized(waitingForSetup) {
|
|
142
|
+
waitingForSetup.add(onReady)
|
|
143
|
+
if (waitingForSetup.size > 1) return
|
|
144
|
+
}
|
|
145
|
+
billing.startConnection(object : BillingClientStateListener {
|
|
146
|
+
override fun onBillingSetupFinished(result: BillingResult) {
|
|
147
|
+
val ready = synchronized(waitingForSetup) {
|
|
148
|
+
waitingForSetup.toList().also { waitingForSetup.clear() }
|
|
149
|
+
}
|
|
150
|
+
if (result.responseCode == BillingResponseCode.OK) {
|
|
151
|
+
queryPurchases(ProductType.SUBS) { subs ->
|
|
152
|
+
queryPurchases(ProductType.INAPP) { inapp ->
|
|
153
|
+
(subs + inapp).filterNot { it.isAcknowledged }.forEach { emit(toMap(it)) }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
ready.forEach { it(result) }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
override fun onBillingServiceDisconnected() {}
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private fun settle(waiting: PendingPurchase, result: BillingResult) {
|
|
165
|
+
when (result.responseCode) {
|
|
166
|
+
BillingResponseCode.USER_CANCELED -> waiting.promise.resolve(null)
|
|
167
|
+
BillingResponseCode.ITEM_ALREADY_OWNED -> queryPurchases(ProductType.SUBS) { subs ->
|
|
168
|
+
queryPurchases(ProductType.INAPP) { inapp ->
|
|
169
|
+
val owned = (subs + inapp).firstOrNull { waiting.productId in it.products }
|
|
170
|
+
if (owned != null) waiting.promise.resolve(toMap(owned))
|
|
171
|
+
else waiting.promise.reject("store_error", "${result.responseCode} ${result.debugMessage}")
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else -> waiting.promise.reject("store_error", "${result.responseCode} ${result.debugMessage}")
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private fun queryProduct(productId: String, type: String, then: (ProductDetails?) -> Unit) {
|
|
179
|
+
val product = QueryProductDetailsParams.Product.newBuilder()
|
|
180
|
+
.setProductId(productId)
|
|
181
|
+
.setProductType(type)
|
|
182
|
+
.build()
|
|
183
|
+
val params = QueryProductDetailsParams.newBuilder().setProductList(listOf(product)).build()
|
|
184
|
+
billing.queryProductDetailsAsync(params) { _, details ->
|
|
185
|
+
then(details.productDetailsList.firstOrNull())
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private fun queryPurchases(type: String, then: (List<Purchase>) -> Unit) {
|
|
190
|
+
val params = QueryPurchasesParams.newBuilder().setProductType(type).build()
|
|
191
|
+
billing.queryPurchasesAsync(params) { result, purchases ->
|
|
192
|
+
if (result.responseCode != BillingResponseCode.OK) {
|
|
193
|
+
Log.w(TAG, "queryPurchases($type) failed: ${result.responseCode} ${result.debugMessage}")
|
|
194
|
+
}
|
|
195
|
+
then(purchases.filter { it.purchaseState == Purchase.PurchaseState.PURCHASED })
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private fun launch(details: ProductDetails, customerId: String, promise: Promise) {
|
|
200
|
+
val activity = reactApplicationContext.currentActivity
|
|
201
|
+
if (activity == null) {
|
|
202
|
+
promise.reject("no_activity", "No foreground Activity to show Google Play billing")
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
val productParams = BillingFlowParams.ProductDetailsParams.newBuilder()
|
|
206
|
+
.setProductDetails(details)
|
|
207
|
+
.apply {
|
|
208
|
+
// ponytail: first base plan/offer wins; products with several base plans need a basePlanId/offerId selector passed through purchase()
|
|
209
|
+
details.subscriptionOfferDetails?.firstOrNull()?.let { setOfferToken(it.offerToken) }
|
|
210
|
+
}
|
|
211
|
+
.build()
|
|
212
|
+
val params = BillingFlowParams.newBuilder()
|
|
213
|
+
.setProductDetailsParamsList(listOf(productParams))
|
|
214
|
+
.setObfuscatedAccountId(customerId)
|
|
215
|
+
.build()
|
|
216
|
+
activity.runOnUiThread {
|
|
217
|
+
val waiting = PendingPurchase(details.productId, promise)
|
|
218
|
+
pending.getAndSet(waiting)?.promise?.resolve(null)
|
|
219
|
+
val result = billing.launchBillingFlow(activity, params)
|
|
220
|
+
if (result.responseCode != BillingResponseCode.OK && pending.compareAndSet(waiting, null)) {
|
|
221
|
+
settle(waiting, result)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private fun toMap(purchase: Purchase): WritableMap {
|
|
227
|
+
if (purchase.isAcknowledged) acknowledged.add(purchase.purchaseToken)
|
|
228
|
+
return Arguments.createMap().apply {
|
|
229
|
+
putString("store", "google")
|
|
230
|
+
putString("productId", purchase.products.firstOrNull())
|
|
231
|
+
putString("transactionId", purchase.purchaseToken)
|
|
232
|
+
putString("token", purchase.purchaseToken)
|
|
233
|
+
putDouble("occurredAt", purchase.purchaseTime.toDouble())
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private fun emit(transaction: WritableMap) {
|
|
238
|
+
if (!reactApplicationContext.hasActiveReactInstance()) return
|
|
239
|
+
reactApplicationContext
|
|
240
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
241
|
+
.emit("revnixStoreTransaction", transaction)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private companion object {
|
|
245
|
+
const val TAG = "RevnixStore"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.revnix.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class RevnixStorePackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
10
|
+
listOf(RevnixStoreModule(reactContext))
|
|
11
|
+
|
|
12
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
13
|
+
emptyList()
|
|
14
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AdRevenue, AliasResult, CustomerEntitlements, IdentifyResult, IdentifyStrategy, IdentityInfo, LastDeepLink, MmpAttribution, PaywallInteraction, PlacementResolution, RegisterPurchaseInput, RegisterPurchaseResult, RevnixAttribution, RevnixConfig } from "./types.js";
|
|
2
|
+
export declare function paywallImageUrls(value: unknown): string[];
|
|
2
3
|
export declare class RevnixClient {
|
|
3
4
|
private readonly apiKey;
|
|
4
5
|
private readonly baseUrl;
|
|
@@ -33,6 +34,7 @@ export declare class RevnixClient {
|
|
|
33
34
|
private implicitConfigPromise;
|
|
34
35
|
private implicitConfigRetryAt;
|
|
35
36
|
private lifecycleUnsubscribe;
|
|
37
|
+
private stopStoreObserver;
|
|
36
38
|
/** The cold-start batch while it runs, resolving to whether it presented a
|
|
37
39
|
* paywall. A deep link delivered on the first frame — SwiftUI's onOpenURL,
|
|
38
40
|
* RN's getInitialURL — must wait for it, or the customer gets the launch
|
|
@@ -55,6 +57,9 @@ export declare class RevnixClient {
|
|
|
55
57
|
* /v1/installs uses, and independent of the device header (which an app
|
|
56
58
|
* can turn off entirely). */
|
|
57
59
|
private freshInstall;
|
|
60
|
+
private eventFlush;
|
|
61
|
+
private eventTimer;
|
|
62
|
+
private eventRetryAt;
|
|
58
63
|
constructor(config: RevnixConfig);
|
|
59
64
|
/** The id every call is made as: the stored identified/canonical id, or a
|
|
60
65
|
* generated-and-persisted anonymous id (rvx_anon_…) on first launch. */
|
|
@@ -90,10 +95,22 @@ export declare class RevnixClient {
|
|
|
90
95
|
* PurchaseBlockedError when the token belongs to another customer and the
|
|
91
96
|
* app's transfer behavior forbids reassignment. */
|
|
92
97
|
registerPurchase(input: RegisterPurchaseInput): Promise<RegisterPurchaseResult>;
|
|
98
|
+
/** Buys `productId` through the native store (StoreKit 2 / Play Billing),
|
|
99
|
+
* registers it with Revnix, then finishes the store transaction. Resolves
|
|
100
|
+
* null when the customer cancelled or the purchase is pending approval.
|
|
101
|
+
* Needs `import "react-native-revnix/store"` before configure(). A refused
|
|
102
|
+
* claim (PurchaseBlockedError) is never finished, so Google refunds it. */
|
|
103
|
+
purchase(productId: string, opts?: Pick<RegisterPurchaseInput, "viewId" | "paywallId" | "placementKey">): Promise<RegisterPurchaseResult | null>;
|
|
104
|
+
/** Re-registers every purchase the store still holds for this device and
|
|
105
|
+
* resolves how many registered. Per-purchase failures go to onDiagnostic. */
|
|
106
|
+
restore(): Promise<number>;
|
|
107
|
+
private requireStore;
|
|
108
|
+
private registerStoreTransaction;
|
|
93
109
|
private postPurchase;
|
|
94
110
|
/** C7: retry purchases that failed to register and were persisted locally.
|
|
95
111
|
* Runs automatically at construction; call again when connectivity returns.
|
|
96
|
-
* Idempotent and safe to call repeatedly.
|
|
112
|
+
* Idempotent and safe to call repeatedly. Purchases queued more than 30
|
|
113
|
+
* days ago are dropped with a diagnostic. */
|
|
97
114
|
retryPendingPurchases(): Promise<void>;
|
|
98
115
|
private freshEntitlements;
|
|
99
116
|
private inflightEntitlements;
|
|
@@ -138,12 +155,22 @@ export declare class RevnixClient {
|
|
|
138
155
|
* RevnixAuthError locally, before any network call, like identify(). */
|
|
139
156
|
getIdentity(): Promise<IdentityInfo>;
|
|
140
157
|
/** Resolve a named in-app location to the currently published offering —
|
|
141
|
-
* the one-line paywall call.
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
158
|
+
* the one-line paywall call. Served instantly from a cache under 24 h old
|
|
159
|
+
* that was fetched for this customer since the last `setAttributes` or
|
|
160
|
+
* purchase, refreshed in the background. Otherwise reads the network and
|
|
161
|
+
* caches the result; if that read FAILS (offline / incident) it falls back
|
|
162
|
+
* to the last cached resolution with `stale: true`, so the paywall renders
|
|
163
|
+
* the last published version rather than not at all. Throws only when
|
|
164
|
+
* there is nothing cached. A deliberate rejection seen by the background
|
|
165
|
+
* refresh drops the cache, so the next resolve throws it. */
|
|
146
166
|
resolvePlacement(placementKey: string): Promise<PlacementResolution>;
|
|
167
|
+
/** Fetch these placements now so the next `resolvePlacement` answers from
|
|
168
|
+
* the cache. Never throws; failures report to `onDiagnostic`. Paywall
|
|
169
|
+
* images are warmed by `prefetchPaywallImages` from
|
|
170
|
+
* `react-native-revnix/ui`. */
|
|
171
|
+
preload(placementKeys: string[]): Promise<void>;
|
|
172
|
+
private refreshPlacement;
|
|
173
|
+
private fetchPlacement;
|
|
147
174
|
/** REV-157: the last resolution cached for `placementKey`, with NO network
|
|
148
175
|
* call — for an instant, offline-safe paywall at launch. Always
|
|
149
176
|
* `stale: true`; follow with resolvePlacement() to revalidate. Null if
|
|
@@ -195,7 +222,8 @@ export declare class RevnixClient {
|
|
|
195
222
|
* - `restore` — restore purchases was tapped
|
|
196
223
|
* - `error` — the paywall itself failed (config, products, render)
|
|
197
224
|
*
|
|
198
|
-
*
|
|
225
|
+
* Queued and sent in batches (see `flushEvents`): resolves once queued,
|
|
226
|
+
* never throws.
|
|
199
227
|
*
|
|
200
228
|
* `viewId` is the id `logPaywallShown` returned for THIS display. Passing
|
|
201
229
|
* it is what threads the whole life of one impression together and puts
|
|
@@ -221,15 +249,28 @@ export declare class RevnixClient {
|
|
|
221
249
|
message?: string;
|
|
222
250
|
/** Idempotency key for this occurrence; defaults to `viewId`. */
|
|
223
251
|
eventId?: string;
|
|
224
|
-
}): void
|
|
252
|
+
}): Promise<void>;
|
|
225
253
|
/** Reports one ad impression's payout (POST /v1/ad-revenue).
|
|
226
254
|
* Fire-and-forget like the other beacons: never blocks, never throws. */
|
|
227
255
|
logAdRevenue(input: AdRevenue): void;
|
|
228
256
|
/** Reports one custom in-app event (POST /v1/events), not for purchases.
|
|
229
|
-
*
|
|
257
|
+
* Queued and sent in batches (see `flushEvents`): resolves once queued,
|
|
258
|
+
* never throws. */
|
|
230
259
|
track(event: string, properties?: Record<string, string | number | boolean>, options?: {
|
|
231
260
|
eventId?: string;
|
|
232
|
-
}): void
|
|
261
|
+
}): Promise<void>;
|
|
262
|
+
/** Send every queued `track` and paywall interaction event now. Events are
|
|
263
|
+
* otherwise sent in batches: at 20 queued, 10 s after the first, when the
|
|
264
|
+
* app goes to the background, and on the next launch. Never throws; a
|
|
265
|
+
* transient failure keeps the events and holds further sends for 30 s. */
|
|
266
|
+
flushEvents(): Promise<void>;
|
|
267
|
+
private readEvents;
|
|
268
|
+
private mutateEvents;
|
|
269
|
+
private enqueueEvent;
|
|
270
|
+
private armEventTimer;
|
|
271
|
+
private startEventFlush;
|
|
272
|
+
private runEventFlush;
|
|
273
|
+
private removeEvents;
|
|
233
274
|
/** PT11: forward an MMP's attribution callback (Adjust, AppsFlyer, …) so
|
|
234
275
|
* Revnix credits revenue to the right network/campaign. */
|
|
235
276
|
setAttribution(attribution: MmpAttribution): void;
|
|
@@ -349,6 +390,7 @@ export declare class RevnixClient {
|
|
|
349
390
|
* cold start to ONE paywall. Never throws past its caller's catch. */
|
|
350
391
|
private triggerImplicit;
|
|
351
392
|
private consumePreviousSessionMs;
|
|
393
|
+
private subscribeLifecycle;
|
|
352
394
|
/** REV-272: the launch-time moments, plus the foreground listener that
|
|
353
395
|
* `session_start` is built on.
|
|
354
396
|
*
|
|
@@ -393,7 +435,12 @@ export declare class RevnixClient {
|
|
|
393
435
|
private readPlacementMap;
|
|
394
436
|
private readPlacementCache;
|
|
395
437
|
private writePlacementCache;
|
|
396
|
-
private
|
|
438
|
+
private deletePlacementCache;
|
|
439
|
+
private mutatePlacements;
|
|
440
|
+
private placementsInvalidatedAt;
|
|
441
|
+
private invalidatePlacements;
|
|
442
|
+
private storageLock;
|
|
443
|
+
private withStorageLock;
|
|
397
444
|
private mutateQueue;
|
|
398
445
|
private readPurchaseQueue;
|
|
399
446
|
private writePurchaseQueue;
|