@umituz/react-native-subscription 2.26.13 → 2.26.14
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.26.
|
|
3
|
+
"version": "2.26.14",
|
|
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",
|
|
@@ -2,76 +2,62 @@
|
|
|
2
2
|
* useCreditsGate Hook
|
|
3
3
|
*
|
|
4
4
|
* Single responsibility: Credits gating
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* const { requireCredits, hasCredits } = useCreditsGate({
|
|
10
|
-
* hasCredits: canAfford(cost),
|
|
11
|
-
* creditBalance: credits,
|
|
12
|
-
* requiredCredits: cost,
|
|
13
|
-
* onCreditsRequired: (required) => showPaywall(required),
|
|
14
|
-
* });
|
|
15
|
-
*
|
|
16
|
-
* const handleGenerate = () => {
|
|
17
|
-
* requireCredits(() => generate());
|
|
18
|
-
* };
|
|
19
|
-
* ```
|
|
5
|
+
* Uses ref pattern to avoid stale closure issues.
|
|
20
6
|
*/
|
|
21
7
|
|
|
22
|
-
import { useCallback } from "react";
|
|
8
|
+
import { useCallback, useRef, useEffect } from "react";
|
|
9
|
+
|
|
10
|
+
declare const __DEV__: boolean;
|
|
23
11
|
|
|
24
12
|
export interface UseCreditsGateParams {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
creditBalance: number;
|
|
29
|
-
/** Credits required for this action (optional, for display) */
|
|
30
|
-
requiredCredits?: number;
|
|
31
|
-
/** Callback when credits are required - receives required amount */
|
|
32
|
-
onCreditsRequired: (requiredCredits?: number) => void;
|
|
13
|
+
readonly creditBalance: number;
|
|
14
|
+
readonly requiredCredits?: number;
|
|
15
|
+
readonly onCreditsRequired: (requiredCredits?: number) => void;
|
|
33
16
|
}
|
|
34
17
|
|
|
35
18
|
export interface UseCreditsGateResult {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
creditBalance: number;
|
|
40
|
-
/** Gate action behind credits - executes if has credits, else shows paywall */
|
|
41
|
-
requireCredits: (action: () => void | Promise<void>) => boolean;
|
|
19
|
+
readonly hasCredits: boolean;
|
|
20
|
+
readonly creditBalance: number;
|
|
21
|
+
readonly requireCredits: () => boolean;
|
|
42
22
|
}
|
|
43
23
|
|
|
44
|
-
declare const __DEV__: boolean;
|
|
45
|
-
|
|
46
24
|
export function useCreditsGate(
|
|
47
25
|
params: UseCreditsGateParams
|
|
48
26
|
): UseCreditsGateResult {
|
|
49
|
-
const {
|
|
50
|
-
|
|
27
|
+
const { creditBalance, requiredCredits = 1, onCreditsRequired } = params;
|
|
28
|
+
|
|
29
|
+
const creditBalanceRef = useRef(creditBalance);
|
|
30
|
+
const onCreditsRequiredRef = useRef(onCreditsRequired);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
creditBalanceRef.current = creditBalance;
|
|
34
|
+
}, [creditBalance]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
onCreditsRequiredRef.current = onCreditsRequired;
|
|
38
|
+
}, [onCreditsRequired]);
|
|
39
|
+
|
|
40
|
+
const hasCredits = creditBalance >= requiredCredits;
|
|
41
|
+
|
|
42
|
+
const requireCredits = useCallback((): boolean => {
|
|
43
|
+
const currentBalance = creditBalanceRef.current;
|
|
44
|
+
const canAfford = currentBalance >= requiredCredits;
|
|
45
|
+
|
|
46
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
47
|
+
console.log("[useCreditsGate] requireCredits", {
|
|
48
|
+
currentBalance,
|
|
49
|
+
requiredCredits,
|
|
50
|
+
canAfford,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!canAfford) {
|
|
55
|
+
onCreditsRequiredRef.current(requiredCredits);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
55
|
-
console.log("[useCreditsGate] requireCredits called", {
|
|
56
|
-
hasCredits,
|
|
57
|
-
creditBalance,
|
|
58
|
-
requiredCredits,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
if (!hasCredits) {
|
|
62
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
63
|
-
console.log("[useCreditsGate] No credits, showing paywall");
|
|
64
|
-
}
|
|
65
|
-
onCreditsRequired(requiredCredits);
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
69
|
-
console.log("[useCreditsGate] Has credits, allowing action");
|
|
70
|
-
}
|
|
71
|
-
return true;
|
|
72
|
-
},
|
|
73
|
-
[hasCredits, creditBalance, requiredCredits, onCreditsRequired]
|
|
74
|
-
);
|
|
59
|
+
return true;
|
|
60
|
+
}, [requiredCredits]);
|
|
75
61
|
|
|
76
62
|
return {
|
|
77
63
|
hasCredits,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useFeatureGate Hook
|
|
3
3
|
* Combines auth, subscription, and credits gates into a unified feature gate.
|
|
4
|
+
* Uses ref pattern to avoid stale closure issues.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { useCallback, useRef, useEffect } from "react";
|
|
@@ -10,38 +11,22 @@ import { useCreditsGate } from "./useCreditsGate";
|
|
|
10
11
|
|
|
11
12
|
declare const __DEV__: boolean;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
14
|
export interface UseFeatureGateParams {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/** Whether user has enough credits for the action */
|
|
23
|
-
hasCredits: boolean;
|
|
24
|
-
/** Current credit balance */
|
|
25
|
-
creditBalance: number;
|
|
26
|
-
/** Credits required for this action (optional, for paywall display) */
|
|
27
|
-
requiredCredits?: number;
|
|
28
|
-
/** Callback to show paywall - receives required credits */
|
|
29
|
-
onShowPaywall: (requiredCredits?: number) => void;
|
|
15
|
+
readonly isAuthenticated: boolean;
|
|
16
|
+
readonly onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
|
|
17
|
+
readonly hasSubscription?: boolean;
|
|
18
|
+
readonly creditBalance: number;
|
|
19
|
+
readonly requiredCredits?: number;
|
|
20
|
+
readonly onShowPaywall: (requiredCredits?: number) => void;
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
export interface UseFeatureGateResult {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/** Whether user has enough credits */
|
|
40
|
-
hasCredits: boolean;
|
|
41
|
-
/** Current credit balance */
|
|
42
|
-
creditBalance: number;
|
|
43
|
-
/** Whether feature access is allowed */
|
|
44
|
-
canAccess: boolean;
|
|
24
|
+
readonly requireFeature: (action: () => void | Promise<void>) => void;
|
|
25
|
+
readonly isAuthenticated: boolean;
|
|
26
|
+
readonly hasSubscription: boolean;
|
|
27
|
+
readonly hasCredits: boolean;
|
|
28
|
+
readonly creditBalance: number;
|
|
29
|
+
readonly canAccess: boolean;
|
|
45
30
|
}
|
|
46
31
|
|
|
47
32
|
export function useFeatureGate(
|
|
@@ -51,25 +36,22 @@ export function useFeatureGate(
|
|
|
51
36
|
isAuthenticated,
|
|
52
37
|
onShowAuthModal,
|
|
53
38
|
hasSubscription = false,
|
|
54
|
-
hasCredits,
|
|
55
39
|
creditBalance,
|
|
56
|
-
requiredCredits,
|
|
40
|
+
requiredCredits = 1,
|
|
57
41
|
onShowPaywall,
|
|
58
42
|
} = params;
|
|
59
43
|
|
|
60
|
-
// Store pending action for execution after purchase
|
|
61
44
|
const pendingActionRef = useRef<(() => void | Promise<void>) | null>(null);
|
|
62
45
|
const prevCreditBalanceRef = useRef(creditBalance);
|
|
63
46
|
const isWaitingForPurchaseRef = useRef(false);
|
|
64
47
|
|
|
65
|
-
|
|
66
|
-
const hasCreditsRef = useRef(hasCredits);
|
|
48
|
+
const creditBalanceRef = useRef(creditBalance);
|
|
67
49
|
const hasSubscriptionRef = useRef(hasSubscription);
|
|
68
50
|
const onShowPaywallRef = useRef(onShowPaywall);
|
|
69
51
|
|
|
70
52
|
useEffect(() => {
|
|
71
|
-
|
|
72
|
-
}, [
|
|
53
|
+
creditBalanceRef.current = creditBalance;
|
|
54
|
+
}, [creditBalance]);
|
|
73
55
|
|
|
74
56
|
useEffect(() => {
|
|
75
57
|
hasSubscriptionRef.current = hasSubscription;
|
|
@@ -79,11 +61,9 @@ export function useFeatureGate(
|
|
|
79
61
|
onShowPaywallRef.current = onShowPaywall;
|
|
80
62
|
}, [onShowPaywall]);
|
|
81
63
|
|
|
82
|
-
// Execute pending action when credits increase after purchase
|
|
83
64
|
useEffect(() => {
|
|
84
|
-
const prevBalance = prevCreditBalanceRef.current ?? 0;
|
|
85
|
-
const
|
|
86
|
-
const creditsIncreased = currentBalance > prevBalance;
|
|
65
|
+
const prevBalance = prevCreditBalanceRef.current ?? 0;
|
|
66
|
+
const creditsIncreased = creditBalance > prevBalance;
|
|
87
67
|
|
|
88
68
|
if (isWaitingForPurchaseRef.current && creditsIncreased && pendingActionRef.current) {
|
|
89
69
|
const action = pendingActionRef.current;
|
|
@@ -91,11 +71,7 @@ export function useFeatureGate(
|
|
|
91
71
|
isWaitingForPurchaseRef.current = false;
|
|
92
72
|
|
|
93
73
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
94
|
-
console.log("[useFeatureGate] Credits increased, executing pending action"
|
|
95
|
-
prevBalance,
|
|
96
|
-
currentBalance,
|
|
97
|
-
creditsIncreased,
|
|
98
|
-
});
|
|
74
|
+
console.log("[useFeatureGate] Credits increased, executing pending action");
|
|
99
75
|
}
|
|
100
76
|
action();
|
|
101
77
|
}
|
|
@@ -103,7 +79,6 @@ export function useFeatureGate(
|
|
|
103
79
|
prevCreditBalanceRef.current = creditBalance;
|
|
104
80
|
}, [creditBalance]);
|
|
105
81
|
|
|
106
|
-
// Compose individual gates
|
|
107
82
|
const authGate = useAuthGate({
|
|
108
83
|
isAuthenticated,
|
|
109
84
|
onAuthRequired: onShowAuthModal,
|
|
@@ -115,7 +90,6 @@ export function useFeatureGate(
|
|
|
115
90
|
});
|
|
116
91
|
|
|
117
92
|
const creditsGate = useCreditsGate({
|
|
118
|
-
hasCredits,
|
|
119
93
|
creditBalance,
|
|
120
94
|
requiredCredits,
|
|
121
95
|
onCreditsRequired: onShowPaywall,
|
|
@@ -124,92 +98,64 @@ export function useFeatureGate(
|
|
|
124
98
|
const requireFeature = useCallback(
|
|
125
99
|
(action: () => void | Promise<void>) => {
|
|
126
100
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
127
|
-
console.log("[useFeatureGate] requireFeature
|
|
101
|
+
console.log("[useFeatureGate] requireFeature", {
|
|
128
102
|
isAuthenticated,
|
|
129
103
|
hasSubscription,
|
|
130
|
-
|
|
131
|
-
|
|
104
|
+
creditBalance: creditBalanceRef.current,
|
|
105
|
+
requiredCredits,
|
|
132
106
|
});
|
|
133
107
|
}
|
|
134
108
|
|
|
135
|
-
// Step 1: Auth check
|
|
136
109
|
if (!authGate.requireAuth(() => {})) {
|
|
137
|
-
// Wrap action to re-check credits after auth succeeds
|
|
138
|
-
// Using refs to get current values when callback executes
|
|
139
110
|
const postAuthAction = () => {
|
|
140
|
-
// Subscription check (bypasses credits if subscribed)
|
|
141
111
|
if (hasSubscriptionRef.current) {
|
|
142
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
143
|
-
console.log("[useFeatureGate] User has subscription, executing action");
|
|
144
|
-
}
|
|
145
112
|
action();
|
|
146
113
|
return;
|
|
147
114
|
}
|
|
148
115
|
|
|
149
|
-
|
|
150
|
-
if (
|
|
116
|
+
const currentBalance = creditBalanceRef.current;
|
|
117
|
+
if (currentBalance < requiredCredits) {
|
|
151
118
|
pendingActionRef.current = action;
|
|
152
119
|
isWaitingForPurchaseRef.current = true;
|
|
153
120
|
onShowPaywallRef.current(requiredCredits);
|
|
154
121
|
return;
|
|
155
122
|
}
|
|
156
123
|
|
|
157
|
-
// All checks passed
|
|
158
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
159
|
-
console.log("[useFeatureGate] User has credits, executing action");
|
|
160
|
-
}
|
|
161
124
|
action();
|
|
162
125
|
};
|
|
163
126
|
onShowAuthModal(postAuthAction);
|
|
164
127
|
return;
|
|
165
128
|
}
|
|
166
129
|
|
|
167
|
-
// Step 2: Subscription check (bypasses credits if subscribed)
|
|
168
130
|
if (hasSubscription) {
|
|
169
131
|
action();
|
|
170
132
|
return;
|
|
171
133
|
}
|
|
172
134
|
|
|
173
|
-
|
|
174
|
-
if (!creditsGate.requireCredits(() => {})) {
|
|
175
|
-
// Store pending action for execution after purchase
|
|
135
|
+
if (!creditsGate.requireCredits()) {
|
|
176
136
|
pendingActionRef.current = action;
|
|
177
137
|
isWaitingForPurchaseRef.current = true;
|
|
178
138
|
return;
|
|
179
139
|
}
|
|
180
140
|
|
|
181
|
-
// Step 4: All checks passed, execute action
|
|
182
141
|
action();
|
|
183
142
|
},
|
|
184
|
-
[
|
|
185
|
-
authGate,
|
|
186
|
-
creditsGate,
|
|
187
|
-
hasSubscription,
|
|
188
|
-
requiredCredits,
|
|
189
|
-
onShowAuthModal,
|
|
190
|
-
]
|
|
143
|
+
[authGate, creditsGate, hasSubscription, requiredCredits, isAuthenticated, onShowAuthModal]
|
|
191
144
|
);
|
|
192
145
|
|
|
146
|
+
const hasCredits = creditBalance >= requiredCredits;
|
|
147
|
+
|
|
193
148
|
return {
|
|
194
149
|
requireFeature,
|
|
195
150
|
isAuthenticated: authGate.isAuthenticated,
|
|
196
151
|
hasSubscription: subscriptionGate.hasSubscription,
|
|
197
|
-
hasCredits
|
|
198
|
-
creditBalance
|
|
152
|
+
hasCredits,
|
|
153
|
+
creditBalance,
|
|
199
154
|
canAccess: isAuthenticated && (hasSubscription || hasCredits),
|
|
200
155
|
};
|
|
201
156
|
}
|
|
202
157
|
|
|
203
158
|
export { useAuthGate, useSubscriptionGate, useCreditsGate };
|
|
204
|
-
export type {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
} from "./useAuthGate";
|
|
208
|
-
export type {
|
|
209
|
-
UseSubscriptionGateParams,
|
|
210
|
-
UseSubscriptionGateResult,
|
|
211
|
-
} from "./useSubscriptionGate";
|
|
212
|
-
export type {
|
|
213
|
-
UseCreditsGateParams,
|
|
214
|
-
UseCreditsGateResult,
|
|
215
|
-
} from "./useCreditsGate";
|
|
159
|
+
export type { UseAuthGateParams, UseAuthGateResult } from "./useAuthGate";
|
|
160
|
+
export type { UseSubscriptionGateParams, UseSubscriptionGateResult } from "./useSubscriptionGate";
|
|
161
|
+
export type { UseCreditsGateParams, UseCreditsGateResult } from "./useCreditsGate";
|