@umituz/react-native-subscription 2.22.1 → 2.22.3

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.1",
3
+ "version": "2.22.3",
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",
@@ -31,7 +31,7 @@ export const SubscriptionDetailScreen: React.FC<
31
31
  SubscriptionDetailScreenProps
32
32
  > = ({ config }) => {
33
33
  const tokens = useAppDesignTokens();
34
- const showCredits = config.isPremium && config.credits && config.credits.length > 0;
34
+ const showCredits = config.credits && config.credits.length > 0;
35
35
  const showUpgradePrompt = !config.isPremium && config.upgradePrompt;
36
36
 
37
37
  const styles = useMemo(
@@ -68,8 +68,8 @@ export const SubscriptionDetailScreen: React.FC<
68
68
  ) : undefined
69
69
  }
70
70
  >
71
- {config.isPremium ? (
72
- <View style={styles.cardsContainer}>
71
+ <View style={styles.cardsContainer}>
72
+ {config.isPremium && (
73
73
  <SubscriptionHeader
74
74
  statusType={config.statusType}
75
75
  isPremium={config.isPremium}
@@ -79,20 +79,20 @@ export const SubscriptionDetailScreen: React.FC<
79
79
  daysRemaining={config.daysRemaining}
80
80
  translations={config.translations}
81
81
  />
82
+ )}
82
83
 
83
- {showCredits && (
84
- <CreditsList
85
- credits={config.credits!}
86
- title={
87
- config.translations.usageTitle || config.translations.creditsTitle
88
- }
89
- description={config.translations.creditsResetInfo}
90
- remainingLabel={config.translations.remainingLabel}
91
- />
92
- )}
93
- </View>
94
- ) : (
95
- showUpgradePrompt && (
84
+ {showCredits && (
85
+ <CreditsList
86
+ credits={config.credits!}
87
+ title={
88
+ config.translations.usageTitle || config.translations.creditsTitle
89
+ }
90
+ description={config.translations.creditsResetInfo}
91
+ remainingLabel={config.translations.remainingLabel}
92
+ />
93
+ )}
94
+
95
+ {showUpgradePrompt && (
96
96
  <UpgradePrompt
97
97
  title={config.upgradePrompt!.title}
98
98
  subtitle={config.upgradePrompt!.subtitle}
@@ -100,8 +100,8 @@ export const SubscriptionDetailScreen: React.FC<
100
100
  upgradeButtonLabel={config.translations.upgradeButton}
101
101
  onUpgrade={config.onUpgrade}
102
102
  />
103
- )
104
- )}
103
+ )}
104
+ </View>
105
105
 
106
106
  <View style={styles.spacer} />
107
107
  </ScreenLayout>
@@ -1,13 +1,9 @@
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
8
4
  */
9
5
 
10
- import Purchases, { type PurchasesPackage, type CustomerInfo } from "react-native-purchases";
6
+ import Purchases, { type PurchasesPackage } from "react-native-purchases";
11
7
  import type { PurchaseResult } from "../../application/ports/IRevenueCatService";
12
8
  import {
13
9
  RevenueCatPurchaseError,
@@ -24,50 +20,6 @@ import {
24
20
  } from "../utils/PremiumStatusSyncer";
25
21
  import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/hooks/useAuthAwarePurchase";
26
22
 
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
-
71
23
  export interface PurchaseHandlerDeps {
72
24
  config: RevenueCatConfig;
73
25
  isInitialized: () => boolean;
@@ -110,48 +62,18 @@ export async function handlePurchase(
110
62
 
111
63
  const consumableIds = deps.config.consumableProductIdentifiers || [];
112
64
  const isConsumable = isConsumableProduct(pkg, consumableIds);
113
-
114
- // Set up listener-based detection as fallback for purchasePackage bug
115
65
  const entitlementIdentifier = deps.config.entitlementIdentifier;
116
- const { promise: listenerPromise, cleanup: cleanupListener } = createPremiumListenerPromise(entitlementIdentifier);
117
66
 
118
67
  try {
119
68
  if (__DEV__) {
120
- console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage with listener fallback...', {
69
+ console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage...', {
121
70
  productId: pkg.product.identifier,
122
71
  packageIdentifier: pkg.identifier,
123
72
  offeringIdentifier: pkg.offeringIdentifier,
124
- timestamp: new Date().toISOString()
125
73
  });
126
74
  }
127
75
 
128
- const startTime = Date.now();
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
-
145
- const duration = Date.now() - startTime;
146
- const customerInfo = result.customerInfo;
147
-
148
- if (__DEV__) {
149
- console.log('[DEBUG PurchaseHandler] Purchase resolved via:', {
150
- source: result.source,
151
- duration: `${duration}ms`,
152
- productId: pkg.product.identifier
153
- });
154
- }
76
+ const { customerInfo } = await Purchases.purchasePackage(pkg);
155
77
 
156
78
  if (__DEV__) {
157
79
  console.log('[DEBUG PurchaseHandler] Purchase completed', {
@@ -240,9 +162,6 @@ export async function handlePurchase(
240
162
  pkg.product.identifier
241
163
  );
242
164
  } catch (error) {
243
- // Ensure listener cleanup on error
244
- cleanupListener();
245
-
246
165
  if (__DEV__) {
247
166
  console.error('[DEBUG PurchaseHandler] Purchase error caught', {
248
167
  error,