@umituz/react-native-subscription 2.22.0 → 2.22.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.1",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Purchase Handler
|
|
3
3
|
* Handles RevenueCat purchase operations for both subscriptions and consumables
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: Uses race condition with CustomerInfo listener to handle
|
|
6
|
+
* known RevenueCat bug where purchasePackage can hang indefinitely.
|
|
7
|
+
* @see https://github.com/RevenueCat/react-native-purchases/issues/1082
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
|
-
import Purchases, { type PurchasesPackage } from "react-native-purchases";
|
|
10
|
+
import Purchases, { type PurchasesPackage, type CustomerInfo } from "react-native-purchases";
|
|
7
11
|
import type { PurchaseResult } from "../../application/ports/IRevenueCatService";
|
|
8
12
|
import {
|
|
9
13
|
RevenueCatPurchaseError,
|
|
@@ -20,6 +24,50 @@ import {
|
|
|
20
24
|
} from "../utils/PremiumStatusSyncer";
|
|
21
25
|
import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/hooks/useAuthAwarePurchase";
|
|
22
26
|
|
|
27
|
+
const PURCHASE_LISTENER_TIMEOUT_MS = 30000; // 30 seconds fallback timeout
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a promise that resolves when CustomerInfo listener detects premium
|
|
31
|
+
* This is a workaround for RevenueCat bug where purchasePackage can hang
|
|
32
|
+
*/
|
|
33
|
+
function createPremiumListenerPromise(
|
|
34
|
+
entitlementId: string
|
|
35
|
+
): { promise: Promise<CustomerInfo>; cleanup: () => void } {
|
|
36
|
+
let cleanup: () => void = () => {};
|
|
37
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
38
|
+
|
|
39
|
+
const promise = new Promise<CustomerInfo>((resolve, reject) => {
|
|
40
|
+
const listener = (info: CustomerInfo) => {
|
|
41
|
+
const isPremium = !!info.entitlements.active[entitlementId];
|
|
42
|
+
if (isPremium) {
|
|
43
|
+
if (__DEV__) {
|
|
44
|
+
console.log('[DEBUG PurchaseHandler] Listener detected premium!');
|
|
45
|
+
}
|
|
46
|
+
cleanup();
|
|
47
|
+
resolve(info);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Purchases.addCustomerInfoUpdateListener(listener);
|
|
52
|
+
|
|
53
|
+
// Fallback timeout - if neither purchasePackage nor listener resolves
|
|
54
|
+
timeoutId = setTimeout(() => {
|
|
55
|
+
cleanup();
|
|
56
|
+
reject(new Error('Purchase timed out. Please try again.'));
|
|
57
|
+
}, PURCHASE_LISTENER_TIMEOUT_MS);
|
|
58
|
+
|
|
59
|
+
cleanup = () => {
|
|
60
|
+
Purchases.removeCustomerInfoUpdateListener(listener);
|
|
61
|
+
if (timeoutId) {
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
timeoutId = null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { promise, cleanup };
|
|
69
|
+
}
|
|
70
|
+
|
|
23
71
|
export interface PurchaseHandlerDeps {
|
|
24
72
|
config: RevenueCatConfig;
|
|
25
73
|
isInitialized: () => boolean;
|
|
@@ -63,9 +111,13 @@ export async function handlePurchase(
|
|
|
63
111
|
const consumableIds = deps.config.consumableProductIdentifiers || [];
|
|
64
112
|
const isConsumable = isConsumableProduct(pkg, consumableIds);
|
|
65
113
|
|
|
114
|
+
// Set up listener-based detection as fallback for purchasePackage bug
|
|
115
|
+
const entitlementIdentifier = deps.config.entitlementIdentifier;
|
|
116
|
+
const { promise: listenerPromise, cleanup: cleanupListener } = createPremiumListenerPromise(entitlementIdentifier);
|
|
117
|
+
|
|
66
118
|
try {
|
|
67
119
|
if (__DEV__) {
|
|
68
|
-
console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage...', {
|
|
120
|
+
console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage with listener fallback...', {
|
|
69
121
|
productId: pkg.product.identifier,
|
|
70
122
|
packageIdentifier: pkg.identifier,
|
|
71
123
|
offeringIdentifier: pkg.offeringIdentifier,
|
|
@@ -74,12 +126,28 @@ export async function handlePurchase(
|
|
|
74
126
|
}
|
|
75
127
|
|
|
76
128
|
const startTime = Date.now();
|
|
77
|
-
|
|
129
|
+
|
|
130
|
+
// Race between purchasePackage and listener detection
|
|
131
|
+
// This handles the known RevenueCat bug where purchasePackage can hang
|
|
132
|
+
const purchasePromise = Purchases.purchasePackage(pkg).then(result => ({
|
|
133
|
+
source: 'purchasePackage' as const,
|
|
134
|
+
customerInfo: result.customerInfo
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
const listenerWithSource = listenerPromise.then(info => ({
|
|
138
|
+
source: 'listener' as const,
|
|
139
|
+
customerInfo: info
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
const result = await Promise.race([purchasePromise, listenerWithSource]);
|
|
143
|
+
cleanupListener();
|
|
144
|
+
|
|
78
145
|
const duration = Date.now() - startTime;
|
|
79
|
-
const customerInfo =
|
|
146
|
+
const customerInfo = result.customerInfo;
|
|
80
147
|
|
|
81
148
|
if (__DEV__) {
|
|
82
|
-
console.log('[DEBUG PurchaseHandler]
|
|
149
|
+
console.log('[DEBUG PurchaseHandler] Purchase resolved via:', {
|
|
150
|
+
source: result.source,
|
|
83
151
|
duration: `${duration}ms`,
|
|
84
152
|
productId: pkg.product.identifier
|
|
85
153
|
});
|
|
@@ -118,7 +186,6 @@ export async function handlePurchase(
|
|
|
118
186
|
};
|
|
119
187
|
}
|
|
120
188
|
|
|
121
|
-
const entitlementIdentifier = deps.config.entitlementIdentifier;
|
|
122
189
|
const isPremium = !!customerInfo.entitlements.active[entitlementIdentifier];
|
|
123
190
|
|
|
124
191
|
if (__DEV__) {
|
|
@@ -173,6 +240,9 @@ export async function handlePurchase(
|
|
|
173
240
|
pkg.product.identifier
|
|
174
241
|
);
|
|
175
242
|
} catch (error) {
|
|
243
|
+
// Ensure listener cleanup on error
|
|
244
|
+
cleanupListener();
|
|
245
|
+
|
|
176
246
|
if (__DEV__) {
|
|
177
247
|
console.error('[DEBUG PurchaseHandler] Purchase error caught', {
|
|
178
248
|
error,
|