@umituz/react-native-subscription 2.17.1 → 2.17.4
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 +1 -1
- package/src/presentation/hooks/useCompletePendingPurchase.ts +5 -5
- package/src/utils/__tests__/authUtils.test.ts +3 -17
- package/src/utils/__tests__/edgeCases.test.ts +8 -11
- package/src/utils/__tests__/premiumUtils.test.ts +6 -93
- package/src/utils/__tests__/tierUtils.test.ts +3 -75
- package/src/utils/__tests__/validation.test.ts +11 -12
- package/src/utils/index.ts +0 -2
- package/src/utils/premiumAsyncUtils.ts +0 -60
- package/src/utils/userTierUtils.ts +0 -81
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.4",
|
|
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",
|
|
@@ -67,8 +67,9 @@ export function useCompletePendingPurchase({
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Clear pending BEFORE purchase to prevent double processing
|
|
70
|
+
// Clear pending and close paywall BEFORE purchase to prevent double processing
|
|
71
71
|
clearPendingPurchase();
|
|
72
|
+
closePaywall();
|
|
72
73
|
|
|
73
74
|
try {
|
|
74
75
|
const result = await purchasePackage(pkg);
|
|
@@ -76,7 +77,6 @@ export function useCompletePendingPurchase({
|
|
|
76
77
|
if (result.success) {
|
|
77
78
|
if (__DEV__) console.log("[CompletePendingPurchase] Purchase SUCCESS");
|
|
78
79
|
onPurchaseSuccess?.();
|
|
79
|
-
closePaywall();
|
|
80
80
|
return true;
|
|
81
81
|
} else {
|
|
82
82
|
if (__DEV__) console.log("[CompletePendingPurchase] Purchase FAILED");
|
|
@@ -113,10 +113,10 @@ export function useCompletePendingPurchase({
|
|
|
113
113
|
console.log("[CompletePendingPurchase] Auth completed, auto-completing purchase");
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
//
|
|
117
|
-
|
|
116
|
+
// Execute in next microtask to ensure React state updates are flushed
|
|
117
|
+
queueMicrotask(() => {
|
|
118
118
|
completePendingPurchase();
|
|
119
|
-
}
|
|
119
|
+
});
|
|
120
120
|
}
|
|
121
121
|
}, [userId, isAnonymous, completePendingPurchase]);
|
|
122
122
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Authentication Utilities Tests
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Tests for authentication check functions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
isGuest,
|
|
9
|
-
} from '../authUtils';
|
|
7
|
+
import { isAuthenticated, isGuest } from '../authUtils';
|
|
10
8
|
|
|
11
9
|
describe('isAuthenticated', () => {
|
|
12
10
|
it('should return false for guest users', () => {
|
|
@@ -21,12 +19,6 @@ describe('isAuthenticated', () => {
|
|
|
21
19
|
it('should return true for authenticated users', () => {
|
|
22
20
|
expect(isAuthenticated(false, 'user123')).toBe(true);
|
|
23
21
|
});
|
|
24
|
-
|
|
25
|
-
it('should throw error for invalid inputs', () => {
|
|
26
|
-
expect(() => isAuthenticated('invalid' as any, null)).toThrow(TypeError);
|
|
27
|
-
expect(() => isAuthenticated(true, 123 as any)).toThrow(TypeError);
|
|
28
|
-
expect(() => isAuthenticated(true, '' as any)).toThrow(TypeError);
|
|
29
|
-
});
|
|
30
22
|
});
|
|
31
23
|
|
|
32
24
|
describe('isGuest', () => {
|
|
@@ -42,10 +34,4 @@ describe('isGuest', () => {
|
|
|
42
34
|
it('should return false for authenticated users', () => {
|
|
43
35
|
expect(isGuest(false, 'user123')).toBe(false);
|
|
44
36
|
});
|
|
45
|
-
|
|
46
|
-
it('should throw error for invalid inputs', () => {
|
|
47
|
-
expect(() => isGuest('invalid' as any, null)).toThrow(TypeError);
|
|
48
|
-
expect(() => isGuest(true, 123 as any)).toThrow(TypeError);
|
|
49
|
-
expect(() => isGuest(true, '' as any)).toThrow(TypeError);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
37
|
+
});
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Edge Cases Tests
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Tests for edge cases and special scenarios
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
} from '../
|
|
9
|
-
isAuthenticated,
|
|
10
|
-
isGuest,
|
|
11
|
-
} from '../authUtils';
|
|
7
|
+
import { getUserTierInfo } from '../tierUtils';
|
|
8
|
+
import { isAuthenticated, isGuest } from '../authUtils';
|
|
12
9
|
import { validateUserId } from '../validation';
|
|
13
10
|
|
|
14
11
|
describe('Edge Cases', () => {
|
|
@@ -41,7 +38,7 @@ describe('Edge Cases', () => {
|
|
|
41
38
|
// isGuest=true but userId provided - should prioritize guest logic
|
|
42
39
|
expect(isAuthenticated(true, 'user123')).toBe(false);
|
|
43
40
|
expect(isGuest(true, 'user123')).toBe(true);
|
|
44
|
-
|
|
41
|
+
|
|
45
42
|
const result = getUserTierInfo(true, 'user123', true);
|
|
46
43
|
expect(result.tier).toBe('guest');
|
|
47
44
|
expect(result.isPremium).toBe(false);
|
|
@@ -51,7 +48,7 @@ describe('Edge Cases', () => {
|
|
|
51
48
|
// isGuest=false but userId=null - should treat as guest
|
|
52
49
|
expect(isAuthenticated(false, null)).toBe(false);
|
|
53
50
|
expect(isGuest(false, null)).toBe(true);
|
|
54
|
-
|
|
51
|
+
|
|
55
52
|
const result = getUserTierInfo(false, null, true);
|
|
56
53
|
expect(result.tier).toBe('guest');
|
|
57
54
|
expect(result.isPremium).toBe(false);
|
|
@@ -62,7 +59,7 @@ describe('Edge Cases', () => {
|
|
|
62
59
|
it('should ignore isPremium for guest users regardless of value', () => {
|
|
63
60
|
const guestTrue = getUserTierInfo(true, null, true);
|
|
64
61
|
const guestFalse = getUserTierInfo(true, null, false);
|
|
65
|
-
|
|
62
|
+
|
|
66
63
|
expect(guestTrue.isPremium).toBe(false);
|
|
67
64
|
expect(guestFalse.isPremium).toBe(false);
|
|
68
65
|
expect(guestTrue.tier).toBe('guest');
|
|
@@ -72,11 +69,11 @@ describe('Edge Cases', () => {
|
|
|
72
69
|
it('should handle authenticated users with various premium states', () => {
|
|
73
70
|
const premium = getUserTierInfo(false, 'user123', true);
|
|
74
71
|
const freemium = getUserTierInfo(false, 'user123', false);
|
|
75
|
-
|
|
72
|
+
|
|
76
73
|
expect(premium.tier).toBe('premium');
|
|
77
74
|
expect(premium.isPremium).toBe(true);
|
|
78
75
|
expect(freemium.tier).toBe('freemium');
|
|
79
76
|
expect(freemium.isPremium).toBe(false);
|
|
80
77
|
});
|
|
81
78
|
});
|
|
82
|
-
});
|
|
79
|
+
});
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Premium Utilities Tests
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Tests for premium status fetching and async functions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
} from '../premiumStatusUtils';
|
|
9
|
-
getUserTierInfoAsync,
|
|
10
|
-
checkPremiumAccessAsync,
|
|
11
|
-
} from '../premiumAsyncUtils';
|
|
7
|
+
import { getIsPremium } from '../premiumStatusUtils';
|
|
12
8
|
import type { PremiumStatusFetcher } from '../types';
|
|
13
9
|
|
|
14
10
|
describe('getIsPremium', () => {
|
|
@@ -32,11 +28,6 @@ describe('getIsPremium', () => {
|
|
|
32
28
|
const result = getIsPremium(false, 'user123', false);
|
|
33
29
|
expect(result).toBe(false);
|
|
34
30
|
});
|
|
35
|
-
|
|
36
|
-
it('should throw error for invalid inputs', () => {
|
|
37
|
-
expect(() => getIsPremium('invalid' as any, null, true)).toThrow(TypeError);
|
|
38
|
-
expect(() => getIsPremium(true, 123 as any, true)).toThrow(TypeError);
|
|
39
|
-
});
|
|
40
31
|
});
|
|
41
32
|
|
|
42
33
|
describe('Async mode (fetcher)', () => {
|
|
@@ -62,7 +53,7 @@ describe('getIsPremium', () => {
|
|
|
62
53
|
|
|
63
54
|
it('should call fetcher for authenticated users', async () => {
|
|
64
55
|
(mockFetcher.isPremium as jest.Mock).mockResolvedValue(true);
|
|
65
|
-
|
|
56
|
+
|
|
66
57
|
const result = await getIsPremium(false, 'user123', mockFetcher);
|
|
67
58
|
expect(result).toBe(true);
|
|
68
59
|
expect(mockFetcher.isPremium).toHaveBeenCalledWith('user123');
|
|
@@ -71,7 +62,7 @@ describe('getIsPremium', () => {
|
|
|
71
62
|
|
|
72
63
|
it('should return false when fetcher returns false', async () => {
|
|
73
64
|
(mockFetcher.isPremium as jest.Mock).mockResolvedValue(false);
|
|
74
|
-
|
|
65
|
+
|
|
75
66
|
const result = await getIsPremium(false, 'user123', mockFetcher);
|
|
76
67
|
expect(result).toBe(false);
|
|
77
68
|
expect(mockFetcher.isPremium).toHaveBeenCalledWith('user123');
|
|
@@ -80,7 +71,7 @@ describe('getIsPremium', () => {
|
|
|
80
71
|
it('should throw error when fetcher throws Error', async () => {
|
|
81
72
|
const error = new Error('Database error');
|
|
82
73
|
(mockFetcher.isPremium as jest.Mock).mockRejectedValue(error);
|
|
83
|
-
|
|
74
|
+
|
|
84
75
|
await expect(getIsPremium(false, 'user123', mockFetcher)).rejects.toThrow(
|
|
85
76
|
'Failed to fetch premium status: Database error'
|
|
86
77
|
);
|
|
@@ -89,88 +80,10 @@ describe('getIsPremium', () => {
|
|
|
89
80
|
it('should throw error when fetcher throws non-Error', async () => {
|
|
90
81
|
const error = 'String error';
|
|
91
82
|
(mockFetcher.isPremium as jest.Mock).mockRejectedValue(error);
|
|
92
|
-
|
|
83
|
+
|
|
93
84
|
await expect(getIsPremium(false, 'user123', mockFetcher)).rejects.toThrow(
|
|
94
85
|
'Failed to fetch premium status: String error'
|
|
95
86
|
);
|
|
96
87
|
});
|
|
97
|
-
|
|
98
|
-
it('should throw error for invalid inputs', () => {
|
|
99
|
-
// Invalid isGuest/userId - validation happens before async check (sync)
|
|
100
|
-
expect(() => getIsPremium('invalid' as any, null, mockFetcher)).toThrow(TypeError);
|
|
101
|
-
expect(() => getIsPremium(true, 123 as any, mockFetcher)).toThrow(TypeError);
|
|
102
|
-
|
|
103
|
-
// Invalid fetcher - validation happens in async mode but throws sync
|
|
104
|
-
// Use authenticated user (not guest) to reach fetcher validation
|
|
105
|
-
expect(() => getIsPremium(false, 'user123', null as any)).toThrow(TypeError);
|
|
106
|
-
expect(() => getIsPremium(false, 'user123', {} as any)).toThrow(TypeError);
|
|
107
|
-
});
|
|
108
88
|
});
|
|
109
89
|
});
|
|
110
|
-
|
|
111
|
-
describe('getUserTierInfoAsync', () => {
|
|
112
|
-
const mockFetcher: PremiumStatusFetcher = {
|
|
113
|
-
isPremium: jest.fn(),
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
beforeEach(() => {
|
|
117
|
-
jest.clearAllMocks();
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should return guest tier for guest users', async () => {
|
|
121
|
-
const result = await getUserTierInfoAsync(true, null, mockFetcher);
|
|
122
|
-
expect(result.tier).toBe('guest');
|
|
123
|
-
expect(result.isPremium).toBe(false);
|
|
124
|
-
expect(mockFetcher.isPremium).not.toHaveBeenCalled();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should return premium tier when fetcher returns true', async () => {
|
|
128
|
-
(mockFetcher.isPremium as jest.Mock).mockResolvedValue(true);
|
|
129
|
-
|
|
130
|
-
const result = await getUserTierInfoAsync(false, 'user123', mockFetcher);
|
|
131
|
-
expect(result.tier).toBe('premium');
|
|
132
|
-
expect(result.isPremium).toBe(true);
|
|
133
|
-
expect(result.isGuest).toBe(false);
|
|
134
|
-
expect(result.isAuthenticated).toBe(true);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('should return freemium tier when fetcher returns false', async () => {
|
|
138
|
-
(mockFetcher.isPremium as jest.Mock).mockResolvedValue(false);
|
|
139
|
-
|
|
140
|
-
const result = await getUserTierInfoAsync(false, 'user123', mockFetcher);
|
|
141
|
-
expect(result.tier).toBe('freemium');
|
|
142
|
-
expect(result.isPremium).toBe(false);
|
|
143
|
-
expect(result.isGuest).toBe(false);
|
|
144
|
-
expect(result.isAuthenticated).toBe(true);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe('checkPremiumAccessAsync', () => {
|
|
149
|
-
const mockFetcher: PremiumStatusFetcher = {
|
|
150
|
-
isPremium: jest.fn(),
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
beforeEach(() => {
|
|
154
|
-
jest.clearAllMocks();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should return false for guest users', async () => {
|
|
158
|
-
const result = await checkPremiumAccessAsync(true, null, mockFetcher);
|
|
159
|
-
expect(result).toBe(false);
|
|
160
|
-
expect(mockFetcher.isPremium).not.toHaveBeenCalled();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('should return true when fetcher returns true', async () => {
|
|
164
|
-
(mockFetcher.isPremium as jest.Mock).mockResolvedValue(true);
|
|
165
|
-
|
|
166
|
-
const result = await checkPremiumAccessAsync(false, 'user123', mockFetcher);
|
|
167
|
-
expect(result).toBe(true);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should return false when fetcher returns false', async () => {
|
|
171
|
-
(mockFetcher.isPremium as jest.Mock).mockResolvedValue(false);
|
|
172
|
-
|
|
173
|
-
const result = await checkPremiumAccessAsync(false, 'user123', mockFetcher);
|
|
174
|
-
expect(result).toBe(false);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tier Utilities Tests
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Tests for tier determination and comparison functions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
checkPremiumAccess,
|
|
9
|
-
} from '../tierUtils';
|
|
10
|
-
hasTierAccess,
|
|
11
|
-
isTierPremium,
|
|
12
|
-
isTierFreemium,
|
|
13
|
-
isTierGuest,
|
|
14
|
-
} from '../userTierUtils';
|
|
7
|
+
import { getUserTierInfo, checkPremiumAccess } from '../tierUtils';
|
|
15
8
|
|
|
16
9
|
describe('getUserTierInfo', () => {
|
|
17
10
|
describe('Guest users', () => {
|
|
@@ -36,7 +29,7 @@ describe('getUserTierInfo', () => {
|
|
|
36
29
|
it('should ignore isPremium for guest users', () => {
|
|
37
30
|
const result = getUserTierInfo(true, null, true);
|
|
38
31
|
expect(result.tier).toBe('guest');
|
|
39
|
-
expect(result.isPremium).toBe(false);
|
|
32
|
+
expect(result.isPremium).toBe(false);
|
|
40
33
|
});
|
|
41
34
|
});
|
|
42
35
|
|
|
@@ -59,12 +52,6 @@ describe('getUserTierInfo', () => {
|
|
|
59
52
|
expect(result.userId).toBe('user123');
|
|
60
53
|
});
|
|
61
54
|
});
|
|
62
|
-
|
|
63
|
-
it('should throw error for invalid inputs', () => {
|
|
64
|
-
expect(() => getUserTierInfo('invalid' as any, null, false)).toThrow(TypeError);
|
|
65
|
-
expect(() => getUserTierInfo(true, 123 as any, false)).toThrow(TypeError);
|
|
66
|
-
expect(() => getUserTierInfo(true, null, 'invalid' as any)).toThrow(TypeError);
|
|
67
|
-
});
|
|
68
55
|
});
|
|
69
56
|
|
|
70
57
|
describe('checkPremiumAccess', () => {
|
|
@@ -84,63 +71,4 @@ describe('checkPremiumAccess', () => {
|
|
|
84
71
|
it('should return false for authenticated freemium users', () => {
|
|
85
72
|
expect(checkPremiumAccess(false, 'user123', false)).toBe(false);
|
|
86
73
|
});
|
|
87
|
-
|
|
88
|
-
it('should throw error for invalid inputs', () => {
|
|
89
|
-
expect(() => checkPremiumAccess('invalid' as any, null, true)).toThrow(TypeError);
|
|
90
|
-
expect(() => checkPremiumAccess(true, 123 as any, true)).toThrow(TypeError);
|
|
91
|
-
expect(() => checkPremiumAccess(true, null, 'invalid' as any)).toThrow(TypeError);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('hasTierAccess', () => {
|
|
96
|
-
it('should return true when tier1 has higher access', () => {
|
|
97
|
-
expect(hasTierAccess('premium', 'freemium')).toBe(true);
|
|
98
|
-
expect(hasTierAccess('premium', 'guest')).toBe(true);
|
|
99
|
-
expect(hasTierAccess('freemium', 'guest')).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should return true when tiers are equal', () => {
|
|
103
|
-
expect(hasTierAccess('premium', 'premium')).toBe(true);
|
|
104
|
-
expect(hasTierAccess('freemium', 'freemium')).toBe(true);
|
|
105
|
-
expect(hasTierAccess('guest', 'guest')).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should return false when tier1 has lower access', () => {
|
|
109
|
-
expect(hasTierAccess('freemium', 'premium')).toBe(false);
|
|
110
|
-
expect(hasTierAccess('guest', 'premium')).toBe(false);
|
|
111
|
-
expect(hasTierAccess('guest', 'freemium')).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('isTierPremium', () => {
|
|
116
|
-
it('should return true for premium tier', () => {
|
|
117
|
-
expect(isTierPremium('premium')).toBe(true);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should return false for non-premium tiers', () => {
|
|
121
|
-
expect(isTierPremium('freemium')).toBe(false);
|
|
122
|
-
expect(isTierPremium('guest')).toBe(false);
|
|
123
|
-
});
|
|
124
74
|
});
|
|
125
|
-
|
|
126
|
-
describe('isTierFreemium', () => {
|
|
127
|
-
it('should return true for freemium tier', () => {
|
|
128
|
-
expect(isTierFreemium('freemium')).toBe(true);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should return false for non-freemium tiers', () => {
|
|
132
|
-
expect(isTierFreemium('premium')).toBe(false);
|
|
133
|
-
expect(isTierFreemium('guest')).toBe(false);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
describe('isTierGuest', () => {
|
|
138
|
-
it('should return true for guest tier', () => {
|
|
139
|
-
expect(isTierGuest('guest')).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should return false for non-guest tiers', () => {
|
|
143
|
-
expect(isTierGuest('premium')).toBe(false);
|
|
144
|
-
expect(isTierGuest('freemium')).toBe(false);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Tier Validation Tests
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Tests for validation functions and type guards
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import {
|
|
7
8
|
isValidUserTier,
|
|
8
9
|
isUserTierInfo,
|
|
9
10
|
validateUserId,
|
|
10
11
|
validateIsGuest,
|
|
11
12
|
validateIsPremium,
|
|
12
13
|
validateFetcher,
|
|
13
|
-
type UserTierInfo,
|
|
14
|
-
type PremiumStatusFetcher,
|
|
15
14
|
} from '../validation';
|
|
15
|
+
import type { UserTierInfo, PremiumStatusFetcher } from '../types';
|
|
16
16
|
|
|
17
17
|
describe('isValidUserTier', () => {
|
|
18
18
|
it('should return true for valid tiers', () => {
|
|
@@ -62,7 +62,6 @@ describe('validateUserId', () => {
|
|
|
62
62
|
it('should throw for invalid userId', () => {
|
|
63
63
|
expect(() => validateUserId('')).toThrow(TypeError);
|
|
64
64
|
expect(() => validateUserId(' ')).toThrow(TypeError);
|
|
65
|
-
expect(() => validateUserId(123 as any)).toThrow(TypeError);
|
|
66
65
|
});
|
|
67
66
|
});
|
|
68
67
|
|
|
@@ -73,8 +72,8 @@ describe('validateIsGuest', () => {
|
|
|
73
72
|
});
|
|
74
73
|
|
|
75
74
|
it('should throw for invalid isGuest', () => {
|
|
76
|
-
expect(() => validateIsGuest('true' as
|
|
77
|
-
expect(() => validateIsGuest(1 as
|
|
75
|
+
expect(() => validateIsGuest('true' as unknown as boolean)).toThrow(TypeError);
|
|
76
|
+
expect(() => validateIsGuest(1 as unknown as boolean)).toThrow(TypeError);
|
|
78
77
|
});
|
|
79
78
|
});
|
|
80
79
|
|
|
@@ -85,8 +84,8 @@ describe('validateIsPremium', () => {
|
|
|
85
84
|
});
|
|
86
85
|
|
|
87
86
|
it('should throw for invalid isPremium', () => {
|
|
88
|
-
expect(() => validateIsPremium('true' as
|
|
89
|
-
expect(() => validateIsPremium(1 as
|
|
87
|
+
expect(() => validateIsPremium('true' as unknown as boolean)).toThrow(TypeError);
|
|
88
|
+
expect(() => validateIsPremium(1 as unknown as boolean)).toThrow(TypeError);
|
|
90
89
|
});
|
|
91
90
|
});
|
|
92
91
|
|
|
@@ -99,8 +98,8 @@ describe('validateFetcher', () => {
|
|
|
99
98
|
});
|
|
100
99
|
|
|
101
100
|
it('should throw for invalid fetcher', () => {
|
|
102
|
-
expect(() => validateFetcher(null as
|
|
103
|
-
expect(() => validateFetcher({} as
|
|
104
|
-
expect(() => validateFetcher({ isPremium: 'not a function' } as
|
|
101
|
+
expect(() => validateFetcher(null as unknown as PremiumStatusFetcher)).toThrow(TypeError);
|
|
102
|
+
expect(() => validateFetcher({} as unknown as PremiumStatusFetcher)).toThrow(TypeError);
|
|
103
|
+
expect(() => validateFetcher({ isPremium: 'not a function' } as unknown as PremiumStatusFetcher)).toThrow(TypeError);
|
|
105
104
|
});
|
|
106
|
-
});
|
|
105
|
+
});
|
package/src/utils/index.ts
CHANGED
|
@@ -5,10 +5,8 @@ export * from "./creditMapper";
|
|
|
5
5
|
export * from "./packageFilter";
|
|
6
6
|
export * from "./packagePeriodUtils";
|
|
7
7
|
export * from "./packageTypeDetector";
|
|
8
|
-
export * from "./premiumAsyncUtils";
|
|
9
8
|
export * from "./premiumStatusUtils";
|
|
10
9
|
export * from "./priceUtils";
|
|
11
10
|
export * from "./tierUtils";
|
|
12
11
|
export * from "./types";
|
|
13
|
-
export * from "./userTierUtils";
|
|
14
12
|
export * from "./validation";
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Async Premium Utilities
|
|
3
|
-
*
|
|
4
|
-
* Async premium status fetching and tier determination
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { getIsPremium } from './premiumStatusUtils';
|
|
8
|
-
import type { PremiumStatusFetcher } from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get user tier info asynchronously with fetcher
|
|
12
|
-
*
|
|
13
|
-
* This function combines getUserTierInfo and getIsPremium logic.
|
|
14
|
-
* All tier determination logic is centralized here.
|
|
15
|
-
*
|
|
16
|
-
* @param isGuest - Whether user is a guest
|
|
17
|
-
* @param userId - User ID (null for guests)
|
|
18
|
-
* @param fetcher - Premium status fetcher (app-specific implementation)
|
|
19
|
-
* @returns Promise<UserTierInfo> - User tier information
|
|
20
|
-
*/
|
|
21
|
-
export async function getUserTierInfoAsync(
|
|
22
|
-
isGuestFlag: boolean,
|
|
23
|
-
userId: string | null,
|
|
24
|
-
fetcher: PremiumStatusFetcher,
|
|
25
|
-
): Promise<import('./types').UserTierInfo> {
|
|
26
|
-
// Import here to avoid circular dependency
|
|
27
|
-
const { getUserTierInfo } = await import('./tierUtils');
|
|
28
|
-
|
|
29
|
-
// Get isPremium using centralized logic (async mode)
|
|
30
|
-
const isPremium = await getIsPremium(isGuestFlag, userId, fetcher);
|
|
31
|
-
|
|
32
|
-
// Get tier info using centralized logic
|
|
33
|
-
return getUserTierInfo(isGuestFlag, userId, isPremium);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Check if user has premium access (async version with fetcher)
|
|
38
|
-
*
|
|
39
|
-
* This function combines getIsPremium and checkPremiumAccess logic.
|
|
40
|
-
* Guest users NEVER have premium access.
|
|
41
|
-
*
|
|
42
|
-
* @param isGuest - Whether user is a guest
|
|
43
|
-
* @param userId - User ID (null for guests)
|
|
44
|
-
* @param fetcher - Premium status fetcher (app-specific implementation)
|
|
45
|
-
* @returns Promise<boolean> - Whether user has premium access
|
|
46
|
-
*/
|
|
47
|
-
export async function checkPremiumAccessAsync(
|
|
48
|
-
isGuestFlag: boolean,
|
|
49
|
-
userId: string | null,
|
|
50
|
-
fetcher: PremiumStatusFetcher,
|
|
51
|
-
): Promise<boolean> {
|
|
52
|
-
// Import here to avoid circular dependency
|
|
53
|
-
const { checkPremiumAccess } = await import('./tierUtils');
|
|
54
|
-
|
|
55
|
-
// Get isPremium using centralized logic (async mode)
|
|
56
|
-
const isPremium = await getIsPremium(isGuestFlag, userId, fetcher);
|
|
57
|
-
|
|
58
|
-
// Apply premium access check logic
|
|
59
|
-
return checkPremiumAccess(isGuestFlag, userId, isPremium);
|
|
60
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tier Comparison Utilities
|
|
3
|
-
*
|
|
4
|
-
* Utilities for comparing and checking user tiers
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { UserTier } from './types';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Compare two tiers to determine if first tier has higher or equal access than second
|
|
11
|
-
*
|
|
12
|
-
* Tier hierarchy: guest < freemium < premium
|
|
13
|
-
*
|
|
14
|
-
* @param tier1 - First tier to compare
|
|
15
|
-
* @param tier2 - Second tier to compare
|
|
16
|
-
* @returns Whether tier1 has higher or equal access than tier2
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```typescript
|
|
20
|
-
* hasTierAccess('premium', 'freemium'); // true
|
|
21
|
-
* hasTierAccess('freemium', 'premium'); // false
|
|
22
|
-
* hasTierAccess('premium', 'premium'); // true
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export function hasTierAccess(tier1: UserTier, tier2: UserTier): boolean {
|
|
26
|
-
const tierLevels: Record<UserTier, number> = {
|
|
27
|
-
guest: 0,
|
|
28
|
-
freemium: 1,
|
|
29
|
-
premium: 2,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
return tierLevels[tier1] >= tierLevels[tier2];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Check if tier is premium
|
|
37
|
-
*
|
|
38
|
-
* @param tier - Tier to check
|
|
39
|
-
* @returns Whether tier is premium
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```typescript
|
|
43
|
-
* isTierPremium('premium'); // true
|
|
44
|
-
* isTierPremium('freemium'); // false
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export function isTierPremium(tier: UserTier): boolean {
|
|
48
|
-
return tier === 'premium';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Check if tier is freemium
|
|
53
|
-
*
|
|
54
|
-
* @param tier - Tier to check
|
|
55
|
-
* @returns Whether tier is freemium
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* ```typescript
|
|
59
|
-
* isTierFreemium('freemium'); // true
|
|
60
|
-
* isTierFreemium('premium'); // false
|
|
61
|
-
* ```
|
|
62
|
-
*/
|
|
63
|
-
export function isTierFreemium(tier: UserTier): boolean {
|
|
64
|
-
return tier === 'freemium';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Check if tier is guest
|
|
69
|
-
*
|
|
70
|
-
* @param tier - Tier to check
|
|
71
|
-
* @returns Whether tier is guest
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```typescript
|
|
75
|
-
* isTierGuest('guest'); // true
|
|
76
|
-
* isTierGuest('premium'); // false
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
export function isTierGuest(tier: UserTier): boolean {
|
|
80
|
-
return tier === 'guest';
|
|
81
|
-
}
|