@umituz/react-native-subscription 2.33.8 → 2.34.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/package.json +1 -1
- package/src/domains/revenuecat/infrastructure/services/userSwitchHandler.ts +55 -3
- package/src/domains/subscription/application/SubscriptionAuthListener.ts +45 -8
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +17 -0
- package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +1 -2
- package/src/domains/subscription/application/statusChangeHandlers.ts +32 -0
- package/src/domains/subscription/infrastructure/managers/SubscriptionInternalState.ts +1 -4
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +24 -10
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.types.ts +0 -1
- package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +39 -1
- package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +29 -0
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +21 -2
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +33 -0
- package/src/domains/subscription/infrastructure/utils/UserIdProvider.ts +0 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.34.0",
|
|
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",
|
|
@@ -3,6 +3,8 @@ import type { InitializeResult } from "../../../../shared/application/ports/IRev
|
|
|
3
3
|
import type { InitializerDeps } from "./RevenueCatInitializer.types";
|
|
4
4
|
import { FAILED_INITIALIZATION_RESULT } from "./initializerConstants";
|
|
5
5
|
|
|
6
|
+
declare const __DEV__: boolean;
|
|
7
|
+
|
|
6
8
|
function buildSuccessResult(deps: InitializerDeps, customerInfo: CustomerInfo, offerings: any): InitializeResult {
|
|
7
9
|
const isPremium = !!customerInfo.entitlements.active[deps.config.entitlementIdentifier];
|
|
8
10
|
return { success: true, offering: offerings.current, isPremium };
|
|
@@ -13,7 +15,7 @@ function normalizeUserId(userId: string): string | null {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
function isAnonymousId(userId: string): boolean {
|
|
16
|
-
return userId.startsWith('$RCAnonymous');
|
|
18
|
+
return userId.startsWith('$RCAnonymous') || userId.startsWith('device_');
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export async function handleUserSwitch(
|
|
@@ -22,25 +24,52 @@ export async function handleUserSwitch(
|
|
|
22
24
|
): Promise<InitializeResult> {
|
|
23
25
|
try {
|
|
24
26
|
const currentAppUserId = await Purchases.getAppUserID();
|
|
25
|
-
let customerInfo;
|
|
26
|
-
|
|
27
27
|
const normalizedUserId = normalizeUserId(userId);
|
|
28
28
|
const normalizedCurrentUserId = isAnonymousId(currentAppUserId) ? null : currentAppUserId;
|
|
29
29
|
|
|
30
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
31
|
+
console.log('[UserSwitchHandler] handleUserSwitch:', {
|
|
32
|
+
providedUserId: userId,
|
|
33
|
+
normalizedUserId: normalizedUserId || '(null - anonymous)',
|
|
34
|
+
currentAppUserId,
|
|
35
|
+
normalizedCurrentUserId: normalizedCurrentUserId || '(null - anonymous)',
|
|
36
|
+
needsSwitch: normalizedCurrentUserId !== normalizedUserId,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let customerInfo;
|
|
41
|
+
|
|
30
42
|
if (normalizedCurrentUserId !== normalizedUserId) {
|
|
31
43
|
if (normalizedUserId) {
|
|
44
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
45
|
+
console.log('[UserSwitchHandler] Calling Purchases.logIn() to switch from anonymous to:', normalizedUserId);
|
|
46
|
+
}
|
|
32
47
|
const result = await Purchases.logIn(normalizedUserId);
|
|
33
48
|
customerInfo = result.customerInfo;
|
|
49
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
50
|
+
console.log('[UserSwitchHandler] ✅ Purchases.logIn() successful, created:', result.created);
|
|
51
|
+
}
|
|
34
52
|
} else {
|
|
53
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
54
|
+
console.log('[UserSwitchHandler] User is anonymous, fetching customer info');
|
|
55
|
+
}
|
|
35
56
|
customerInfo = await Purchases.getCustomerInfo();
|
|
36
57
|
}
|
|
37
58
|
} else {
|
|
59
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
60
|
+
console.log('[UserSwitchHandler] No user switch needed, fetching current customer info');
|
|
61
|
+
}
|
|
38
62
|
customerInfo = await Purchases.getCustomerInfo();
|
|
39
63
|
}
|
|
40
64
|
|
|
41
65
|
deps.setInitialized(true);
|
|
42
66
|
deps.setCurrentUserId(normalizedUserId);
|
|
43
67
|
const offerings = await Purchases.getOfferings();
|
|
68
|
+
|
|
69
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
70
|
+
console.log('[UserSwitchHandler] ✅ User switch completed successfully');
|
|
71
|
+
}
|
|
72
|
+
|
|
44
73
|
return buildSuccessResult(deps, customerInfo, offerings);
|
|
45
74
|
} catch (error) {
|
|
46
75
|
console.error('[UserSwitchHandler] Failed during user switch or fetch', {
|
|
@@ -59,15 +88,38 @@ export async function handleInitialConfiguration(
|
|
|
59
88
|
): Promise<InitializeResult> {
|
|
60
89
|
try {
|
|
61
90
|
const normalizedUserId = normalizeUserId(userId);
|
|
91
|
+
|
|
92
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
93
|
+
console.log('[UserSwitchHandler] handleInitialConfiguration:', {
|
|
94
|
+
providedUserId: userId,
|
|
95
|
+
normalizedUserId: normalizedUserId || '(null - anonymous)',
|
|
96
|
+
apiKeyPrefix: apiKey.substring(0, 5) + '...',
|
|
97
|
+
isTestKey: apiKey.startsWith('test_'),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
62
101
|
await Purchases.configure({ apiKey, appUserID: normalizedUserId });
|
|
63
102
|
deps.setInitialized(true);
|
|
64
103
|
deps.setCurrentUserId(normalizedUserId);
|
|
65
104
|
|
|
105
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
106
|
+
console.log('[UserSwitchHandler] ✅ Purchases.configure() successful');
|
|
107
|
+
}
|
|
108
|
+
|
|
66
109
|
const [customerInfo, offerings] = await Promise.all([
|
|
67
110
|
Purchases.getCustomerInfo(),
|
|
68
111
|
Purchases.getOfferings(),
|
|
69
112
|
]);
|
|
70
113
|
|
|
114
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
115
|
+
const currentUserId = await Purchases.getAppUserID();
|
|
116
|
+
console.log('[UserSwitchHandler] ✅ Initial configuration completed:', {
|
|
117
|
+
revenueCatUserId: currentUserId,
|
|
118
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
119
|
+
offeringsCount: offerings.all ? Object.keys(offerings.all).length : 0,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
71
123
|
return buildSuccessResult(deps, customerInfo, offerings);
|
|
72
124
|
} catch (error) {
|
|
73
125
|
console.error('[UserSwitchHandler] SDK configuration failed', {
|
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
import type { FirebaseAuthLike } from "./SubscriptionInitializerTypes";
|
|
2
2
|
|
|
3
|
+
declare const __DEV__: boolean;
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Gets the current user ID from Firebase auth.
|
|
5
|
-
* Returns undefined for anonymous users to
|
|
7
|
+
* Returns undefined for anonymous users to let RevenueCat generate its own anonymous ID.
|
|
6
8
|
*/
|
|
7
9
|
export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string | undefined => {
|
|
8
10
|
const auth = getAuth();
|
|
9
|
-
if (!auth)
|
|
11
|
+
if (!auth) {
|
|
12
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
13
|
+
console.log('[SubscriptionAuthListener] No auth available');
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
10
17
|
|
|
11
18
|
const user = auth.currentUser;
|
|
12
|
-
if (!user)
|
|
19
|
+
if (!user) {
|
|
20
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
21
|
+
console.log('[SubscriptionAuthListener] No current user');
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (user.isAnonymous) {
|
|
27
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
28
|
+
console.log('[SubscriptionAuthListener] Anonymous user - returning undefined (RevenueCat will use its own ID)');
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
34
|
+
console.log('[SubscriptionAuthListener] Authenticated user:', user.uid);
|
|
35
|
+
}
|
|
16
36
|
|
|
17
37
|
return user.uid;
|
|
18
38
|
};
|
|
@@ -20,18 +40,35 @@ export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string
|
|
|
20
40
|
/**
|
|
21
41
|
* Sets up auth state listener that will re-initialize subscription
|
|
22
42
|
* when user auth state changes (login/logout).
|
|
23
|
-
* Returns undefined for anonymous users to
|
|
43
|
+
* Returns undefined for anonymous users to let RevenueCat generate its own anonymous ID.
|
|
24
44
|
*/
|
|
25
45
|
export const setupAuthStateListener = (
|
|
26
46
|
getAuth: () => FirebaseAuthLike | null,
|
|
27
47
|
onUserChange: (userId: string | undefined) => void
|
|
28
48
|
): (() => void) | null => {
|
|
29
49
|
const auth = getAuth();
|
|
30
|
-
if (!auth)
|
|
50
|
+
if (!auth) {
|
|
51
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
52
|
+
console.log('[SubscriptionAuthListener] Cannot setup listener - no auth available');
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
58
|
+
console.log('[SubscriptionAuthListener] Setting up auth state listener');
|
|
59
|
+
}
|
|
31
60
|
|
|
32
61
|
return auth.onAuthStateChanged((user) => {
|
|
33
|
-
// Don't pass userId for anonymous users - let RevenueCat use its own anonymous ID
|
|
34
62
|
const userId = (user && !user.isAnonymous) ? user.uid : undefined;
|
|
63
|
+
|
|
64
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
65
|
+
console.log('[SubscriptionAuthListener] 🔔 Auth state changed:', {
|
|
66
|
+
hasUser: !!user,
|
|
67
|
+
isAnonymous: user?.isAnonymous,
|
|
68
|
+
userId: userId || '(undefined - anonymous)',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
35
72
|
onUserChange(userId);
|
|
36
73
|
});
|
|
37
74
|
};
|
|
@@ -2,8 +2,13 @@ import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionM
|
|
|
2
2
|
import { getCurrentUserId, setupAuthStateListener } from "../SubscriptionAuthListener";
|
|
3
3
|
import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
|
|
4
4
|
|
|
5
|
+
declare const __DEV__: boolean;
|
|
6
|
+
|
|
5
7
|
export async function startBackgroundInitialization(config: SubscriptionInitConfig): Promise<void> {
|
|
6
8
|
const initializeInBackground = async (userId?: string): Promise<void> => {
|
|
9
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
10
|
+
console.log('[BackgroundInitializer] initializeInBackground called with userId:', userId || '(undefined - anonymous)');
|
|
11
|
+
}
|
|
7
12
|
await SubscriptionManager.initialize(userId);
|
|
8
13
|
};
|
|
9
14
|
|
|
@@ -12,10 +17,22 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
12
17
|
throw new Error("Firebase auth is not available");
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
21
|
+
console.log('[BackgroundInitializer] Starting background initialization');
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
const initialUserId = getCurrentUserId(() => auth);
|
|
25
|
+
|
|
26
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
27
|
+
console.log('[BackgroundInitializer] Initial userId:', initialUserId || '(undefined - anonymous)');
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
await initializeInBackground(initialUserId);
|
|
17
31
|
|
|
18
32
|
setupAuthStateListener(() => auth, (newUserId) => {
|
|
33
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
34
|
+
console.log('[BackgroundInitializer] Auth state listener triggered, reinitializing with userId:', newUserId || '(undefined - anonymous)');
|
|
35
|
+
}
|
|
19
36
|
initializeInBackground(newUserId);
|
|
20
37
|
});
|
|
21
38
|
}
|
|
@@ -5,7 +5,7 @@ import { SubscriptionSyncService } from "../SubscriptionSyncService";
|
|
|
5
5
|
import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
|
|
6
6
|
|
|
7
7
|
export function configureServices(config: SubscriptionInitConfig, apiKey: string): SubscriptionSyncService {
|
|
8
|
-
const { entitlementId, credits, creditPackages,
|
|
8
|
+
const { entitlementId, credits, creditPackages, getFirebaseAuth, showAuthModal, onCreditsUpdated } = config;
|
|
9
9
|
|
|
10
10
|
configureCreditsRepository({
|
|
11
11
|
...credits,
|
|
@@ -25,7 +25,6 @@ export function configureServices(config: SubscriptionInitConfig, apiKey: string
|
|
|
25
25
|
onCreditsUpdated,
|
|
26
26
|
},
|
|
27
27
|
apiKey,
|
|
28
|
-
getAnonymousUserId: getAnonymousUserId!,
|
|
29
28
|
});
|
|
30
29
|
|
|
31
30
|
configureAuthProvider({
|
|
@@ -6,6 +6,8 @@ import { emitCreditsUpdated } from "./syncEventEmitter";
|
|
|
6
6
|
import { generateInitSyncId, generateStatusSyncId } from "./syncIdGenerators";
|
|
7
7
|
import { NO_SUBSCRIPTION_PRODUCT_ID, DEFAULT_FREE_USER_DATA } from "./syncConstants";
|
|
8
8
|
|
|
9
|
+
declare const __DEV__: boolean;
|
|
10
|
+
|
|
9
11
|
export const handleExpiredSubscription = async (userId: string): Promise<void> => {
|
|
10
12
|
await getCreditsRepository().syncExpiredStatus(userId);
|
|
11
13
|
emitCreditsUpdated(userId);
|
|
@@ -32,6 +34,17 @@ export const handlePremiumStatusSync = async (
|
|
|
32
34
|
willRenew: boolean,
|
|
33
35
|
periodType: PeriodType | null
|
|
34
36
|
): Promise<void> => {
|
|
37
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
38
|
+
console.log("[StatusChangeHandlers] handlePremiumStatusSync called:", {
|
|
39
|
+
userId,
|
|
40
|
+
isPremium,
|
|
41
|
+
productId,
|
|
42
|
+
expiresAt,
|
|
43
|
+
willRenew,
|
|
44
|
+
periodType,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
const revenueCatData: RevenueCatData = {
|
|
36
49
|
expirationDate: expiresAt,
|
|
37
50
|
willRenew,
|
|
@@ -45,6 +58,17 @@ export const handlePremiumStatusSync = async (
|
|
|
45
58
|
};
|
|
46
59
|
|
|
47
60
|
const statusSyncId = generateStatusSyncId(userId, isPremium);
|
|
61
|
+
|
|
62
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
63
|
+
console.log("[StatusChangeHandlers] Calling initializeCredits with:", {
|
|
64
|
+
userId,
|
|
65
|
+
statusSyncId,
|
|
66
|
+
productId,
|
|
67
|
+
source: PURCHASE_SOURCE.SETTINGS,
|
|
68
|
+
type: PURCHASE_TYPE.INITIAL,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
await getCreditsRepository().initializeCredits(
|
|
49
73
|
userId,
|
|
50
74
|
statusSyncId,
|
|
@@ -54,5 +78,13 @@ export const handlePremiumStatusSync = async (
|
|
|
54
78
|
PURCHASE_TYPE.INITIAL
|
|
55
79
|
);
|
|
56
80
|
|
|
81
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
82
|
+
console.log("[StatusChangeHandlers] initializeCredits completed, emitting credits updated event");
|
|
83
|
+
}
|
|
84
|
+
|
|
57
85
|
emitCreditsUpdated(userId);
|
|
86
|
+
|
|
87
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
88
|
+
console.log("[StatusChangeHandlers] ✅ handlePremiumStatusSync completed successfully");
|
|
89
|
+
}
|
|
58
90
|
};
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { UserIdProvider } from "../utils/UserIdProvider";
|
|
2
1
|
import { InitializationCache } from "../utils/InitializationCache";
|
|
3
2
|
|
|
4
3
|
export class SubscriptionInternalState {
|
|
5
|
-
public userIdProvider = new UserIdProvider();
|
|
6
4
|
public initCache = new InitializationCache();
|
|
7
|
-
|
|
5
|
+
|
|
8
6
|
reset() {
|
|
9
|
-
this.userIdProvider.reset();
|
|
10
7
|
this.initCache.reset();
|
|
11
8
|
}
|
|
12
9
|
}
|
|
@@ -17,7 +17,6 @@ class SubscriptionManagerImpl {
|
|
|
17
17
|
|
|
18
18
|
configure(config: SubscriptionManagerConfig): void {
|
|
19
19
|
this.managerConfig = config;
|
|
20
|
-
this.state.userIdProvider.configure(config.getAnonymousUserId);
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
private ensureConfigured(): void {
|
|
@@ -34,19 +33,22 @@ class SubscriptionManagerImpl {
|
|
|
34
33
|
async initialize(userId?: string): Promise<boolean> {
|
|
35
34
|
this.ensureConfigured();
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
const actualUserId: string = (userId && userId.length > 0) ? userId : '';
|
|
38
37
|
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
39
|
+
console.log('[SubscriptionManager] initialize called:', {
|
|
40
|
+
providedUserId: userId,
|
|
41
|
+
actualUserId: actualUserId || '(empty - RevenueCat will generate anonymous ID)',
|
|
42
|
+
});
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
const cacheKey = actualUserId
|
|
45
|
+
const cacheKey = actualUserId || '__anonymous__';
|
|
47
46
|
const { shouldInit, existingPromise } = this.state.initCache.tryAcquireInitialization(cacheKey);
|
|
48
47
|
|
|
49
48
|
if (!shouldInit && existingPromise) {
|
|
49
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
50
|
+
console.log('[SubscriptionManager] Using cached initialization for:', cacheKey);
|
|
51
|
+
}
|
|
50
52
|
return existingPromise;
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -55,11 +57,23 @@ class SubscriptionManagerImpl {
|
|
|
55
57
|
return promise;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
private async performInitialization(userId: string
|
|
60
|
+
private async performInitialization(userId: string): Promise<boolean> {
|
|
59
61
|
this.ensureConfigured();
|
|
60
|
-
|
|
62
|
+
|
|
63
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
64
|
+
console.log('[SubscriptionManager] performInitialization:', {
|
|
65
|
+
userId: userId || '(empty - anonymous)',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { service, success } = await performServiceInitialization(this.managerConfig.config, userId);
|
|
61
70
|
this.serviceInstance = service ?? null;
|
|
62
71
|
this.ensurePackageHandlerInitialized();
|
|
72
|
+
|
|
73
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
74
|
+
console.log('[SubscriptionManager] Initialization completed:', { success });
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
return success;
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -3,12 +3,22 @@ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
|
3
3
|
import { ListenerState } from "./listeners/ListenerState";
|
|
4
4
|
import { processCustomerInfo } from "./listeners/CustomerInfoHandler";
|
|
5
5
|
|
|
6
|
+
declare const __DEV__: boolean;
|
|
7
|
+
|
|
6
8
|
export class CustomerInfoListenerManager {
|
|
7
9
|
private state = new ListenerState();
|
|
8
10
|
|
|
9
11
|
setUserId(userId: string, config: RevenueCatConfig): void {
|
|
10
12
|
const wasUserChange = this.state.hasUserChanged(userId);
|
|
11
13
|
|
|
14
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
15
|
+
console.log("[CustomerInfoListener] setUserId called:", {
|
|
16
|
+
userId,
|
|
17
|
+
wasUserChange,
|
|
18
|
+
hasListener: !!this.state.listener,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
if (wasUserChange) {
|
|
13
23
|
this.removeListener();
|
|
14
24
|
this.state.resetRenewalState();
|
|
@@ -22,6 +32,9 @@ export class CustomerInfoListenerManager {
|
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
clearUserId(): void {
|
|
35
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
36
|
+
console.log("[CustomerInfoListener] clearUserId called");
|
|
37
|
+
}
|
|
25
38
|
this.state.currentUserId = null;
|
|
26
39
|
this.state.resetRenewalState();
|
|
27
40
|
}
|
|
@@ -29,8 +42,25 @@ export class CustomerInfoListenerManager {
|
|
|
29
42
|
setupListener(config: RevenueCatConfig): void {
|
|
30
43
|
this.removeListener();
|
|
31
44
|
|
|
45
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
46
|
+
console.log("[CustomerInfoListener] setupListener: Registering listener");
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
this.state.listener = async (customerInfo: CustomerInfo) => {
|
|
33
|
-
if (
|
|
50
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
|
+
console.log("[CustomerInfoListener] 🔔 LISTENER TRIGGERED!", {
|
|
52
|
+
userId: this.state.currentUserId,
|
|
53
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
54
|
+
entitlementsCount: Object.keys(customerInfo.entitlements.all).length,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!this.state.currentUserId) {
|
|
59
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
60
|
+
console.log("[CustomerInfoListener] No userId - skipping");
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
34
64
|
|
|
35
65
|
this.state.renewalState = await processCustomerInfo(
|
|
36
66
|
customerInfo,
|
|
@@ -38,9 +68,17 @@ export class CustomerInfoListenerManager {
|
|
|
38
68
|
this.state.renewalState,
|
|
39
69
|
config
|
|
40
70
|
);
|
|
71
|
+
|
|
72
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
73
|
+
console.log("[CustomerInfoListener] processCustomerInfo completed");
|
|
74
|
+
}
|
|
41
75
|
};
|
|
42
76
|
|
|
43
77
|
Purchases.addCustomerInfoUpdateListener(this.state.listener);
|
|
78
|
+
|
|
79
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
80
|
+
console.log("[CustomerInfoListener] Listener registered successfully");
|
|
81
|
+
}
|
|
44
82
|
}
|
|
45
83
|
|
|
46
84
|
removeListener(): void {
|
|
@@ -3,6 +3,8 @@ import type { RevenueCatConfig } from "../../../../revenuecat/core/types";
|
|
|
3
3
|
import { syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
|
|
4
4
|
import { detectRenewal, updateRenewalState, type RenewalState } from "../../utils/renewal";
|
|
5
5
|
|
|
6
|
+
declare const __DEV__: boolean;
|
|
7
|
+
|
|
6
8
|
async function handleRenewal(
|
|
7
9
|
userId: string,
|
|
8
10
|
productId: string,
|
|
@@ -67,13 +69,34 @@ export async function processCustomerInfo(
|
|
|
67
69
|
renewalState: RenewalState,
|
|
68
70
|
config: RevenueCatConfig
|
|
69
71
|
): Promise<RenewalState> {
|
|
72
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
73
|
+
console.log("[CustomerInfoHandler] processCustomerInfo called:", {
|
|
74
|
+
userId,
|
|
75
|
+
renewalState,
|
|
76
|
+
entitlementId: config.entitlementIdentifier,
|
|
77
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
const renewalResult = detectRenewal(
|
|
71
82
|
renewalState,
|
|
72
83
|
customerInfo,
|
|
73
84
|
config.entitlementIdentifier
|
|
74
85
|
);
|
|
75
86
|
|
|
87
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
88
|
+
console.log("[CustomerInfoHandler] Renewal detection result:", {
|
|
89
|
+
isRenewal: renewalResult.isRenewal,
|
|
90
|
+
isPlanChange: renewalResult.isPlanChange,
|
|
91
|
+
productId: renewalResult.productId,
|
|
92
|
+
previousProductId: renewalResult.previousProductId,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
76
96
|
if (renewalResult.isRenewal) {
|
|
97
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
98
|
+
console.log("[CustomerInfoHandler] Handling renewal");
|
|
99
|
+
}
|
|
77
100
|
await handleRenewal(
|
|
78
101
|
userId,
|
|
79
102
|
renewalResult.productId!,
|
|
@@ -84,6 +107,9 @@ export async function processCustomerInfo(
|
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
if (renewalResult.isPlanChange) {
|
|
110
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
111
|
+
console.log("[CustomerInfoHandler] Handling plan change");
|
|
112
|
+
}
|
|
87
113
|
await handlePlanChange(
|
|
88
114
|
userId,
|
|
89
115
|
renewalResult.productId!,
|
|
@@ -95,6 +121,9 @@ export async function processCustomerInfo(
|
|
|
95
121
|
}
|
|
96
122
|
|
|
97
123
|
if (!renewalResult.isRenewal && !renewalResult.isPlanChange) {
|
|
124
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
125
|
+
console.log("[CustomerInfoHandler] Handling premium status sync (new purchase or status update)");
|
|
126
|
+
}
|
|
98
127
|
await handlePremiumStatusSync(config, userId, customerInfo);
|
|
99
128
|
}
|
|
100
129
|
|
|
@@ -22,6 +22,8 @@ async function executeConsumablePurchase(
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
declare const __DEV__: boolean;
|
|
26
|
+
|
|
25
27
|
async function executeSubscriptionPurchase(
|
|
26
28
|
config: RevenueCatConfig,
|
|
27
29
|
userId: string,
|
|
@@ -32,13 +34,30 @@ async function executeSubscriptionPurchase(
|
|
|
32
34
|
const isPremium = !!customerInfo.entitlements.active[entitlementIdentifier];
|
|
33
35
|
const source = getSavedPurchase()?.source;
|
|
34
36
|
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
38
|
+
console.log("[PurchaseExecutor] executeSubscriptionPurchase:", {
|
|
39
|
+
userId,
|
|
40
|
+
productId,
|
|
41
|
+
isPremium,
|
|
42
|
+
entitlementIdentifier,
|
|
43
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
44
|
+
source,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await syncPremiumStatus(config, userId, customerInfo);
|
|
49
|
+
|
|
50
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
|
+
console.log("[PurchaseExecutor] syncPremiumStatus completed");
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
await notifyPurchaseCompleted(config, userId, productId, customerInfo, source);
|
|
40
55
|
clearSavedPurchase();
|
|
41
56
|
|
|
57
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
58
|
+
console.log("[PurchaseExecutor] Purchase flow completed successfully");
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
return {
|
|
43
62
|
success: true,
|
|
44
63
|
isPremium,
|
|
@@ -8,12 +8,26 @@ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
|
8
8
|
import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
|
|
9
9
|
import { getPremiumEntitlement } from "../../../revenuecat/core/types";
|
|
10
10
|
|
|
11
|
+
declare const __DEV__: boolean;
|
|
12
|
+
|
|
11
13
|
export async function syncPremiumStatus(
|
|
12
14
|
config: RevenueCatConfig,
|
|
13
15
|
userId: string,
|
|
14
16
|
customerInfo: CustomerInfo
|
|
15
17
|
): Promise<void> {
|
|
18
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
19
|
+
console.log("[PremiumStatusSyncer] syncPremiumStatus called:", {
|
|
20
|
+
userId,
|
|
21
|
+
hasCallback: !!config.onPremiumStatusChanged,
|
|
22
|
+
entitlementId: config.entitlementIdentifier,
|
|
23
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
if (!config.onPremiumStatusChanged) {
|
|
28
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
29
|
+
console.log("[PremiumStatusSyncer] No onPremiumStatusChanged callback - skipping");
|
|
30
|
+
}
|
|
17
31
|
return;
|
|
18
32
|
}
|
|
19
33
|
|
|
@@ -22,8 +36,21 @@ export async function syncPremiumStatus(
|
|
|
22
36
|
config.entitlementIdentifier
|
|
23
37
|
);
|
|
24
38
|
|
|
39
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
40
|
+
console.log("[PremiumStatusSyncer] Premium entitlement:", {
|
|
41
|
+
found: !!premiumEntitlement,
|
|
42
|
+
productId: premiumEntitlement?.productIdentifier,
|
|
43
|
+
expirationDate: premiumEntitlement?.expirationDate,
|
|
44
|
+
willRenew: premiumEntitlement?.willRenew,
|
|
45
|
+
periodType: premiumEntitlement?.periodType,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
25
49
|
try {
|
|
26
50
|
if (premiumEntitlement) {
|
|
51
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
52
|
+
console.log("[PremiumStatusSyncer] Calling onPremiumStatusChanged with premium=true");
|
|
53
|
+
}
|
|
27
54
|
await config.onPremiumStatusChanged(
|
|
28
55
|
userId,
|
|
29
56
|
true,
|
|
@@ -33,8 +60,14 @@ export async function syncPremiumStatus(
|
|
|
33
60
|
premiumEntitlement.periodType as "NORMAL" | "INTRO" | "TRIAL" | undefined
|
|
34
61
|
);
|
|
35
62
|
} else {
|
|
63
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
64
|
+
console.log("[PremiumStatusSyncer] Calling onPremiumStatusChanged with premium=false");
|
|
65
|
+
}
|
|
36
66
|
await config.onPremiumStatusChanged(userId, false, undefined, undefined, undefined, undefined);
|
|
37
67
|
}
|
|
68
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
69
|
+
console.log("[PremiumStatusSyncer] onPremiumStatusChanged completed successfully");
|
|
70
|
+
}
|
|
38
71
|
} catch (error) {
|
|
39
72
|
console.error('[PremiumStatusSyncer] Premium status change callback failed', {
|
|
40
73
|
userId,
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User ID Provider
|
|
3
|
-
* Manages user ID retrieval (anonymous or authenticated)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export class UserIdProvider {
|
|
7
|
-
private cachedAnonUserId: string | null = null;
|
|
8
|
-
private getAnonymousUserIdFn: (() => Promise<string>) | null = null;
|
|
9
|
-
|
|
10
|
-
configure(getAnonymousUserId: () => Promise<string>): void {
|
|
11
|
-
this.getAnonymousUserIdFn = getAnonymousUserId;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async getOrCreateAnonymousUserId(): Promise<string> {
|
|
15
|
-
if (this.cachedAnonUserId) {
|
|
16
|
-
return this.cachedAnonUserId;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (!this.getAnonymousUserIdFn) {
|
|
20
|
-
throw new Error("Anonymous user ID provider not configured");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
this.cachedAnonUserId = await this.getAnonymousUserIdFn();
|
|
24
|
-
return this.cachedAnonUserId;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
reset(): void {
|
|
28
|
-
this.cachedAnonUserId = null;
|
|
29
|
-
}
|
|
30
|
-
}
|