@umituz/react-native-subscription 3.1.34 → 3.1.35

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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/CreditsInitializer.ts +34 -39
  3. package/src/domains/credits/application/DeductCreditsCommand.ts +13 -12
  4. package/src/domains/credits/presentation/useCredits.types.ts +1 -1
  5. package/src/domains/paywall/components/PaywallScreen.tsx +12 -11
  6. package/src/domains/paywall/hooks/usePaywallActions.ts +4 -3
  7. package/src/domains/paywall/hooks/usePaywallActions.utils.ts +14 -19
  8. package/src/domains/paywall/hooks/usePaywallPurchase.ts +10 -17
  9. package/src/domains/paywall/hooks/usePaywallRestore.ts +8 -15
  10. package/src/domains/revenuecat/infrastructure/services/RevenueCatInitializer.ts +6 -5
  11. package/src/domains/revenuecat/infrastructure/services/UserSwitchMutex.ts +8 -10
  12. package/src/domains/revenuecat/infrastructure/services/userSwitchCore.ts +16 -33
  13. package/src/domains/revenuecat/infrastructure/services/userSwitchHelpers.ts +3 -4
  14. package/src/domains/revenuecat/infrastructure/services/userSwitchInitializer.ts +18 -28
  15. package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +17 -29
  16. package/src/domains/subscription/application/sync/CreditDocumentOperations.ts +16 -17
  17. package/src/domains/subscription/application/sync/PurchaseSyncHandler.ts +20 -23
  18. package/src/domains/subscription/application/sync/RenewalSyncHandler.ts +8 -7
  19. package/src/domains/subscription/application/sync/StatusChangeSyncHandler.ts +4 -3
  20. package/src/domains/subscription/application/sync/SyncProcessorLogger.ts +39 -64
  21. package/src/domains/subscription/application/sync/UserIdResolver.ts +5 -1
  22. package/src/domains/subscription/infrastructure/handlers/package-operations/PackageFetcher.ts +7 -6
  23. package/src/domains/subscription/infrastructure/handlers/package-operations/PackagePurchaser.ts +8 -7
  24. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +4 -3
  25. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +20 -27
  26. package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +10 -9
  27. package/src/domains/subscription/infrastructure/services/OfferingsFetcher.ts +14 -21
  28. package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +8 -7
  29. package/src/domains/subscription/infrastructure/services/RevenueCatService.types.ts +4 -3
  30. package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +15 -29
  31. package/src/domains/subscription/infrastructure/services/purchase/PurchaseErrorHandler.ts +4 -2
  32. package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +27 -33
  33. package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +5 -1
  34. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +11 -17
  35. package/src/domains/subscription/presentation/providers/SubscriptionFlowProvider.tsx +11 -12
  36. package/src/domains/subscription/presentation/useSyncStatusListener.ts +10 -9
  37. package/src/init/createSubscriptionInitModule.ts +4 -1
  38. package/src/shared/infrastructure/SubscriptionEventBus.ts +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "3.1.34",
3
+ "version": "3.1.35",
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",
@@ -9,6 +9,9 @@ import { calculateNewCredits, buildCreditsData } from "./creditOperationUtils";
9
9
  import { CreditLimitService } from "../domain/services/CreditLimitService";
10
10
  import { generatePurchaseMetadata } from "./PurchaseMetadataGenerator";
11
11
  import { PURCHASE_ID_PREFIXES } from "../core/CreditsConstants";
12
+ import { createLogger } from "../../../shared/utils/logger";
13
+
14
+ const logger = createLogger("CreditsInitializer");
12
15
 
13
16
  export async function initializeCreditsTransaction(
14
17
  _db: Firestore,
@@ -19,19 +22,17 @@ export async function initializeCreditsTransaction(
19
22
  userId: string
20
23
  ): Promise<InitializationResult> {
21
24
 
22
- if (__DEV__) {
23
- console.log('[CreditsInitializer] 🔵 initializeCreditsTransaction: START', {
24
- userId,
25
- purchaseId,
26
- productId: metadata.productId,
27
- source: metadata.source,
28
- type: metadata.type,
29
- isPremium: metadata.isPremium,
30
- willRenew: metadata.willRenew,
31
- storeTransactionId: metadata.storeTransactionId,
32
- timestamp: new Date().toISOString(),
33
- });
34
- }
25
+ logger.debug("initializeCreditsTransaction: START", {
26
+ userId,
27
+ purchaseId,
28
+ productId: metadata.productId,
29
+ source: metadata.source,
30
+ type: metadata.type,
31
+ isPremium: metadata.isPremium,
32
+ willRenew: metadata.willRenew,
33
+ storeTransactionId: metadata.storeTransactionId,
34
+ timestamp: new Date().toISOString(),
35
+ });
35
36
 
36
37
  if (!purchaseId.startsWith(PURCHASE_ID_PREFIXES.PURCHASE) && !purchaseId.startsWith(PURCHASE_ID_PREFIXES.RENEWAL)) {
37
38
  throw new Error(`[CreditsInitializer] Only purchase and renewal operations can allocate credits. Received: ${purchaseId}`);
@@ -46,14 +47,12 @@ export async function initializeCreditsTransaction(
46
47
  const existingData = getCreditDocumentOrDefault(creditsDoc, platform);
47
48
 
48
49
  if (existingData.processedPurchases.includes(purchaseId)) {
49
- if (__DEV__) {
50
- console.log('[CreditsInitializer] 🟡 Transaction already processed, skipping', {
51
- userId,
52
- purchaseId,
53
- existingCredits: existingData.credits,
54
- processedPurchasesCount: existingData.processedPurchases.length,
55
- });
56
- }
50
+ logger.debug("Transaction already processed, skipping", {
51
+ userId,
52
+ purchaseId,
53
+ existingCredits: existingData.credits,
54
+ processedPurchasesCount: existingData.processedPurchases.length,
55
+ });
57
56
  return {
58
57
  credits: existingData.credits,
59
58
  alreadyProcessed: true,
@@ -61,14 +60,12 @@ export async function initializeCreditsTransaction(
61
60
  };
62
61
  }
63
62
 
64
- if (__DEV__) {
65
- console.log('[CreditsInitializer] 🔵 Processing credit allocation', {
66
- userId,
67
- purchaseId,
68
- existingCredits: existingData.credits,
69
- productId: metadata.productId,
70
- });
71
- }
63
+ logger.debug("Processing credit allocation", {
64
+ userId,
65
+ purchaseId,
66
+ existingCredits: existingData.credits,
67
+ productId: metadata.productId,
68
+ });
72
69
 
73
70
  const creditLimitService = new CreditLimitService(config);
74
71
  const creditLimit = creditLimitService.calculate(metadata.productId);
@@ -100,16 +97,14 @@ export async function initializeCreditsTransaction(
100
97
 
101
98
  transaction.set(creditsRef, creditsData, { merge: true });
102
99
 
103
- if (__DEV__) {
104
- console.log('[CreditsInitializer] 🟢 Credit allocation successful', {
105
- userId,
106
- purchaseId,
107
- previousCredits: existingData.credits,
108
- newCredits,
109
- creditLimit,
110
- productId: metadata.productId,
111
- });
112
- }
100
+ logger.debug("Credit allocation successful", {
101
+ userId,
102
+ purchaseId,
103
+ previousCredits: existingData.credits,
104
+ newCredits,
105
+ creditLimit,
106
+ productId: metadata.productId,
107
+ });
113
108
 
114
109
  return {
115
110
  credits: newCredits,
@@ -2,6 +2,9 @@ import { runTransaction, serverTimestamp, type DocumentReference } from "firebas
2
2
  import type { Firestore } from "@umituz/react-native-firebase";
3
3
  import type { DeductCreditsResult } from "../core/Credits";
4
4
  import { CREDIT_ERROR_CODES, MAX_SINGLE_DEDUCTION } from "../core/CreditsConstants";
5
+ import { createLogger } from "../../../shared/utils/logger";
6
+
7
+ const logger = createLogger("DeductCreditsCommand");
5
8
 
6
9
  export async function deductCreditsOperation(
7
10
  _db: Firestore,
@@ -32,12 +35,12 @@ export async function deductCreditsOperation(
32
35
  }
33
36
 
34
37
  try {
35
- if (__DEV__) console.log('[DeductCreditsCommand] >>> starting transaction', { userId, cost, creditsRefPath: creditsRef.path });
38
+ logger.debug(">>> starting transaction", { userId, cost, creditsRefPath: creditsRef.path });
36
39
 
37
40
  const remaining = await runTransaction(_db, async (tx) => {
38
41
  const docSnap = await tx.get(creditsRef);
39
42
 
40
- if (__DEV__) console.log('[DeductCreditsCommand] doc exists:', docSnap.exists());
43
+ logger.debug("doc exists", { exists: docSnap.exists() });
41
44
 
42
45
  if (!docSnap.exists()) {
43
46
  throw new Error(CREDIT_ERROR_CODES.NO_CREDITS);
@@ -46,7 +49,7 @@ export async function deductCreditsOperation(
46
49
  const rawCredits = docSnap.data().credits;
47
50
  const current = typeof rawCredits === "number" && Number.isFinite(rawCredits) ? rawCredits : 0;
48
51
 
49
- if (__DEV__) console.log('[DeductCreditsCommand] current credits:', current, 'cost:', cost);
52
+ logger.debug("current credits", { current, cost });
50
53
 
51
54
  if (current < cost) {
52
55
  throw new Error(CREDIT_ERROR_CODES.CREDITS_EXHAUSTED);
@@ -58,12 +61,12 @@ export async function deductCreditsOperation(
58
61
  lastUpdatedAt: serverTimestamp()
59
62
  });
60
63
 
61
- if (__DEV__) console.log('[DeductCreditsCommand] updated credits to:', updated);
64
+ logger.debug("updated credits to", { updated });
62
65
 
63
66
  return updated;
64
67
  });
65
68
 
66
- if (__DEV__) console.log('[DeductCreditsCommand] transaction SUCCESS, remaining:', remaining);
69
+ logger.debug("transaction SUCCESS", { remaining });
67
70
 
68
71
  return {
69
72
  success: true,
@@ -76,13 +79,11 @@ export async function deductCreditsOperation(
76
79
  ? message
77
80
  : CREDIT_ERROR_CODES.DEDUCT_ERR;
78
81
 
79
- // Use console.warn instead of console.error for "no credits" - this is expected behavior, not a system error
80
- if (__DEV__) {
81
- if (code === CREDIT_ERROR_CODES.NO_CREDITS || code === CREDIT_ERROR_CODES.CREDITS_EXHAUSTED) {
82
- console.warn('[DeductCreditsCommand] ⚠️ User has no credits - paywall should open', { code, message });
83
- } else {
84
- console.error('[DeductCreditsCommand] ❌ Unexpected transaction error:', { code, message });
85
- }
82
+ // Use logger.warn instead of logger.error for "no credits" - this is expected behavior, not a system error
83
+ if (code === CREDIT_ERROR_CODES.NO_CREDITS || code === CREDIT_ERROR_CODES.CREDITS_EXHAUSTED) {
84
+ logger.warn("User has no credits - paywall should open", { code, message });
85
+ } else {
86
+ logger.error("Unexpected transaction error", e, { code, message });
86
87
  }
87
88
 
88
89
  return {
@@ -10,6 +10,6 @@ export interface UseCreditsResult {
10
10
  error: Error | null;
11
11
  hasCredits: boolean;
12
12
  creditsPercent: number;
13
- refetch: () => Promise<any>;
13
+ refetch: () => void;
14
14
  canAfford: (cost: number) => boolean;
15
15
  }
@@ -29,6 +29,9 @@ import {
29
29
  } from "../utils/paywallLayoutUtils";
30
30
  import { hasItems } from "../../../shared/utils/arrayUtils";
31
31
  import { PaywallRenderItem } from "./PaywallScreen.renderItem";
32
+ import { createLogger } from "../../../../shared/utils/logger";
33
+
34
+ const logger = createLogger("PaywallScreen");
32
35
 
33
36
  // Paywall layout constants
34
37
  const PAYWALL_HEADER_HEIGHT = 60; // Close button + spacing
@@ -37,13 +40,11 @@ const PAYWALL_FOOTER_HEIGHT_BASE = 280; // Base footer height
37
40
  export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) => {
38
41
  const navigation = useNavigation();
39
42
 
40
- if (__DEV__) {
41
- console.log('[PaywallScreen] 📱 Rendering PaywallScreen', {
42
- hasPackages: !!props.packages?.length,
43
- packagesCount: props.packages?.length || 0,
44
- isPremium: props.isPremium,
45
- });
46
- }
43
+ logger.debug("Rendering PaywallScreen", {
44
+ hasPackages: !!props.packages?.length,
45
+ packagesCount: props.packages?.length || 0,
46
+ isPremium: props.isPremium,
47
+ });
47
48
 
48
49
  const {
49
50
  translations,
@@ -69,7 +70,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
69
70
  const responsive = useResponsive();
70
71
 
71
72
  const handleClose = useCallback(() => {
72
- if (__DEV__) console.log('[PaywallScreen] 🔙 Closing paywall');
73
+ logger.debug("Closing paywall");
73
74
  if (onClose) {
74
75
  onClose();
75
76
  } else if (navigation.canGoBack()) {
@@ -98,7 +99,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
98
99
  // Cleanup on unmount only - resetState is stable from useCallback
99
100
  useEffect(() => {
100
101
  return () => {
101
- if (__DEV__) console.log('[PaywallScreen] 🧹 Cleanup: resetting state');
102
+ logger.debug("Cleanup: resetting state");
102
103
  resetState();
103
104
  };
104
105
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -118,7 +119,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
118
119
  try {
119
120
  if (await Linking.canOpenURL(url)) await Linking.openURL(url);
120
121
  } catch (error) {
121
- console.error('[PaywallScreen] Failed to open URL:', error);
122
+ logger.error("Failed to open URL", error);
122
123
  }
123
124
  }, []);
124
125
 
@@ -176,7 +177,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
176
177
 
177
178
  // Defensive check for translations
178
179
  if (!translations) {
179
- if (__DEV__) console.warn("[PaywallScreen] Translations prop is missing");
180
+ logger.warn("Translations prop is missing");
180
181
  return null;
181
182
  }
182
183
 
@@ -9,6 +9,9 @@ import { useState, useRef, useMemo, useCallback } from "react";
9
9
  import type { UsePaywallActionsParams } from "./usePaywallActions.types";
10
10
  import { usePurchaseHandler } from "./usePaywallPurchase";
11
11
  import { useRestoreHandler } from "./usePaywallRestore";
12
+ import { createLogger } from "../../../../shared/utils/logger";
13
+
14
+ const logger = createLogger("usePaywallActions");
12
15
 
13
16
  export function usePaywallActions({
14
17
  packages = [],
@@ -61,9 +64,7 @@ export function usePaywallActions({
61
64
 
62
65
  // Reset state - memoized with useCallback to prevent infinite loops
63
66
  const resetState = useCallback(() => {
64
- if (__DEV__) {
65
- console.log('[usePaywallActions] 🧹 Resetting state');
66
- }
67
+ logger.debug("Resetting state");
67
68
  setSelectedPlanId(null);
68
69
  setIsProcessing(false);
69
70
  }, []);
@@ -5,32 +5,27 @@
5
5
 
6
6
  import { useSubscriptionStatus } from "../../subscription/presentation/useSubscriptionStatus";
7
7
  import { useCredits } from "../../credits/presentation/useCredits";
8
+ import { createLogger } from "../../../../shared/utils/logger";
9
+
10
+ const logger = createLogger("PremiumVerification");
8
11
 
9
12
  export function usePremiumVerification() {
10
- const { refetch: refetchStatus } = useSubscriptionStatus();
11
- const { refetch: refetchCredits } = useCredits();
13
+ const { isPremium: isSubscriptionPremium } = useSubscriptionStatus();
14
+ const { credits } = useCredits();
12
15
 
13
16
  const verifyPremiumStatus = async (): Promise<boolean> => {
14
- if (__DEV__) {
15
- console.log('[PremiumVerification] 🔍 Checking premium status as fallback...');
16
- }
17
-
18
- const [statusResult, creditsResult] = await Promise.all([
19
- refetchStatus(),
20
- refetchCredits()
21
- ]);
17
+ logger.debug("Checking premium status as fallback...");
22
18
 
23
- const isSubscriptionPremium = statusResult.data?.isPremium ?? false;
24
- const isCreditsPremium = creditsResult.data?.isPremium ?? false;
19
+ // With real-time sync, data is already up-to-date via onSnapshot
20
+ // No need to manually refetch - just check current state
21
+ const isCreditsPremium = credits?.isPremium ?? false;
25
22
  const isActuallySuccessful = isSubscriptionPremium || isCreditsPremium;
26
23
 
27
- if (__DEV__) {
28
- console.log('[PremiumVerification] 📊 Fallback check result:', {
29
- isSubscriptionPremium,
30
- isCreditsPremium,
31
- isActuallySuccessful
32
- });
33
- }
24
+ logger.debug("Fallback check result", {
25
+ isSubscriptionPremium,
26
+ isCreditsPremium,
27
+ isActuallySuccessful
28
+ });
34
29
 
35
30
  return isActuallySuccessful;
36
31
  };
@@ -7,6 +7,9 @@
7
7
  import { useCallback } from "react";
8
8
  import type { UsePaywallActionsParams } from "./usePaywallActions.types";
9
9
  import { usePremiumVerification } from "./usePaywallActions.utils";
10
+ import { createLogger } from "../../../../shared/utils/logger";
11
+
12
+ const logger = createLogger("usePurchaseHandler");
10
13
 
11
14
  interface UsePurchaseHandlerParams {
12
15
  selectedPlanId: string | null;
@@ -36,18 +39,14 @@ export function usePurchaseHandler({
36
39
  return;
37
40
  }
38
41
 
39
- if (__DEV__) {
40
- console.log('[usePurchaseHandler] 🛒 Starting purchase', {
41
- productId: pkg.product.identifier,
42
- });
43
- }
42
+ logger.debug("Starting purchase", {
43
+ productId: pkg.product.identifier,
44
+ });
44
45
 
45
46
  try {
46
47
  const success = await callbacksRef.current.purchasePackage(pkg);
47
48
 
48
- if (__DEV__) {
49
- console.log('[usePurchaseHandler] ✅ Purchase completed', { success });
50
- }
49
+ logger.debug("Purchase completed", { success });
51
50
 
52
51
  let isActuallySuccessful = success === true;
53
52
 
@@ -56,20 +55,14 @@ export function usePurchaseHandler({
56
55
  }
57
56
 
58
57
  if (isActuallySuccessful) {
59
- if (__DEV__) {
60
- console.log('[usePurchaseHandler] 🎉 Purchase successful, closing paywall');
61
- }
58
+ logger.debug("Purchase successful, closing paywall");
62
59
  callbacksRef.current.onPurchaseSuccess?.();
63
60
  callbacksRef.current.onClose?.();
64
61
  } else {
65
- if (__DEV__) {
66
- console.warn('[usePurchaseHandler] ⚠️ Purchase did not indicate success');
67
- }
62
+ logger.warn("Purchase did not indicate success");
68
63
  }
69
64
  } catch (error) {
70
- if (__DEV__) {
71
- console.error('[usePurchaseHandler] ❌ Purchase error:', error);
72
- }
65
+ logger.error("Purchase error", error);
73
66
  callbacksRef.current.onPurchaseError?.(error instanceof Error ? error : new Error(String(error)));
74
67
  }
75
68
  }, [selectedPlanId, verifyPremiumStatus, callbacksRef, isProcessingRef]);
@@ -7,6 +7,9 @@
7
7
  import { useCallback } from "react";
8
8
  import type { UsePaywallActionsParams } from "./usePaywallActions.types";
9
9
  import { usePremiumVerification } from "./usePaywallActions.utils";
10
+ import { createLogger } from "../../../../shared/utils/logger";
11
+
12
+ const logger = createLogger("useRestoreHandler");
10
13
 
11
14
  interface UseRestoreHandlerParams {
12
15
  isProcessingRef: React.MutableRefObject<boolean>;
@@ -26,16 +29,12 @@ export function useRestoreHandler({
26
29
  const handleRestore = useCallback(async () => {
27
30
  if (isProcessingRef.current) return;
28
31
 
29
- if (__DEV__) {
30
- console.log('[useRestoreHandler] 🔄 Starting restore');
31
- }
32
+ logger.debug("Starting restore");
32
33
 
33
34
  try {
34
35
  const success = await callbacksRef.current.restorePurchase();
35
36
 
36
- if (__DEV__) {
37
- console.log('[useRestoreHandler] ✅ Restore completed', { success });
38
- }
37
+ logger.debug("Restore completed", { success });
39
38
 
40
39
  let isActuallySuccessful = success === true;
41
40
 
@@ -44,20 +43,14 @@ export function useRestoreHandler({
44
43
  }
45
44
 
46
45
  if (isActuallySuccessful) {
47
- if (__DEV__) {
48
- console.log('[useRestoreHandler] 🎉 Restore successful, closing paywall');
49
- }
46
+ logger.debug("Restore successful, closing paywall");
50
47
  callbacksRef.current.onPurchaseSuccess?.();
51
48
  callbacksRef.current.onClose?.();
52
49
  } else {
53
- if (__DEV__) {
54
- console.warn('[useRestoreHandler] ⚠️ Restore did not indicate success');
55
- }
50
+ logger.warn("Restore did not indicate success");
56
51
  }
57
52
  } catch (error) {
58
- if (__DEV__) {
59
- console.error('[useRestoreHandler] ❌ Restore error:', error);
60
- }
53
+ logger.error("Restore error", error);
61
54
  callbacksRef.current.onPurchaseError?.(error instanceof Error ? error : new Error(String(error)));
62
55
  }
63
56
  }, [verifyPremiumStatus, callbacksRef, isProcessingRef]);
@@ -3,6 +3,9 @@ import type { InitializerDeps } from "./RevenueCatInitializer.types";
3
3
  import { FAILED_INITIALIZATION_RESULT, CONFIGURATION_RETRY_DELAY_MS, MAX_INIT_RETRIES } from "./initializerConstants";
4
4
  import { configState } from "./ConfigurationStateManager";
5
5
  import { handleUserSwitch, handleInitialConfiguration, fetchCurrentUserData } from "./userSwitchHandler";
6
+ import { createLogger } from "../../../../../shared/utils/logger";
7
+
8
+ const logger = createLogger("RevenueCatInitializer");
6
9
 
7
10
  const MAX_CONFIG_START_RETRIES = 3;
8
11
 
@@ -32,7 +35,7 @@ export async function initializeSDK(
32
35
  }
33
36
 
34
37
  if (configState.isConfiguring) {
35
- console.error('[RevenueCatInitializer] Max retry attempts reached', { userId });
38
+ logger.error("Max retry attempts reached", undefined, { userId });
36
39
  return FAILED_INITIALIZATION_RESULT;
37
40
  }
38
41
 
@@ -49,18 +52,16 @@ export async function initializeSDK(
49
52
  resolveConfig = configState.startConfiguration();
50
53
  } catch (error) {
51
54
  if (configStartRetryCount >= MAX_CONFIG_START_RETRIES) {
52
- console.error('[RevenueCatInitializer] Max configuration start retries reached', {
55
+ logger.error("Max configuration start retries reached", error, {
53
56
  userId,
54
57
  retryCount: configStartRetryCount,
55
- error
56
58
  });
57
59
  return FAILED_INITIALIZATION_RESULT;
58
60
  }
59
61
 
60
- console.error('[RevenueCatInitializer] Failed to start configuration, retrying', {
62
+ logger.error("Failed to start configuration, retrying", error, {
61
63
  userId,
62
64
  retryCount: configStartRetryCount,
63
- error
64
65
  });
65
66
  await new Promise<void>(resolve => setTimeout(() => resolve(), CONFIGURATION_RETRY_DELAY_MS));
66
67
  return initializeSDK(deps, userId, apiKey, configStartRetryCount + 1);
@@ -1,3 +1,7 @@
1
+ import { createLogger } from "../../../../../shared/utils/logger";
2
+
3
+ const logger = createLogger("UserSwitchMutex");
4
+
1
5
  class UserSwitchMutexImpl {
2
6
  private activeSwitchPromise: Promise<unknown> | null = null;
3
7
  private activeUserId: string | null = null;
@@ -6,30 +10,24 @@ class UserSwitchMutexImpl {
6
10
 
7
11
  async acquire(userId: string): Promise<{ shouldProceed: boolean; existingPromise?: Promise<unknown> }> {
8
12
  if (this.activeSwitchPromise && this.activeUserId === userId) {
9
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
10
- console.log('[UserSwitchMutex] Switch already in progress for this user, returning existing promise');
11
- }
13
+ logger.debug("Switch already in progress for this user, returning existing promise");
12
14
  return { shouldProceed: false, existingPromise: this.activeSwitchPromise };
13
15
  }
14
16
 
15
17
  if (this.activeSwitchPromise) {
16
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
17
- console.log('[UserSwitchMutex] Waiting for active switch to complete...');
18
- }
18
+ logger.debug("Waiting for active switch to complete...");
19
19
  try {
20
20
  await this.activeSwitchPromise;
21
21
  } catch (error) {
22
22
  // Previous switch failed — this is non-fatal for the current switch,
23
23
  // but worth logging so the failure is visible in diagnostics.
24
- console.warn('[UserSwitchMutex] Previous user switch failed:', error instanceof Error ? error.message : String(error));
24
+ logger.warn("Previous user switch failed", error);
25
25
  }
26
26
 
27
27
  const timeSinceLastSwitch = Date.now() - this.lastSwitchTime;
28
28
  if (timeSinceLastSwitch < this.MIN_SWITCH_INTERVAL_MS) {
29
29
  const delayNeeded = this.MIN_SWITCH_INTERVAL_MS - timeSinceLastSwitch;
30
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
31
- console.log(`[UserSwitchMutex] Rate limiting: waiting ${delayNeeded}ms`);
32
- }
30
+ logger.debug(`Rate limiting: waiting ${delayNeeded}ms`);
33
31
  await new Promise<void>(resolve => setTimeout(() => resolve(), delayNeeded));
34
32
  }
35
33
 
@@ -16,8 +16,9 @@ import {
16
16
  buildSuccessResult,
17
17
  fetchOfferingsSafe,
18
18
  } from "./userSwitchHelpers";
19
+ import { createLogger } from "../../../../../shared/utils/logger";
19
20
 
20
- declare const __DEV__: boolean;
21
+ const logger = createLogger("UserSwitchCore");
21
22
 
22
23
  /**
23
24
  * Handle user switch operation with mutex protection.
@@ -31,9 +32,7 @@ export async function handleUserSwitch(
31
32
  const { shouldProceed, existingPromise } = await UserSwitchMutex.acquire(mutexKey);
32
33
 
33
34
  if (!shouldProceed && existingPromise) {
34
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
35
- console.log('[UserSwitchCore] Using result from active switch operation');
36
- }
35
+ logger.debug("Using result from active switch operation");
37
36
  return existingPromise as Promise<InitializeResult>;
38
37
  }
39
38
 
@@ -54,38 +53,28 @@ async function performUserSwitch(
54
53
  const normalizedUserId = normalizeUserId(userId);
55
54
  const normalizedCurrentUserId = isAnonymousId(currentAppUserId) ? null : currentAppUserId;
56
55
 
57
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
58
- console.log('[UserSwitchCore] performUserSwitch:', {
59
- providedUserId: userId,
60
- normalizedUserId: normalizedUserId || '(null - anonymous)',
61
- currentAppUserId,
62
- normalizedCurrentUserId: normalizedCurrentUserId || '(null - anonymous)',
63
- needsSwitch: normalizedCurrentUserId !== normalizedUserId,
64
- });
65
- }
56
+ logger.debug("performUserSwitch", {
57
+ providedUserId: userId,
58
+ normalizedUserId: normalizedUserId || '(null - anonymous)',
59
+ currentAppUserId,
60
+ normalizedCurrentUserId: normalizedCurrentUserId || '(null - anonymous)',
61
+ needsSwitch: normalizedCurrentUserId !== normalizedUserId,
62
+ });
66
63
 
67
64
  let customerInfo: CustomerInfo;
68
65
 
69
66
  if (normalizedCurrentUserId !== normalizedUserId) {
70
67
  if (normalizedUserId) {
71
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
72
- console.log('[UserSwitchCore] Calling Purchases.logIn() to switch from anonymous to:', normalizedUserId);
73
- }
68
+ logger.debug("Calling Purchases.logIn() to switch from anonymous to", normalizedUserId);
74
69
  const result = await Purchases.logIn(normalizedUserId!);
75
70
  customerInfo = result.customerInfo;
76
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
77
- console.log('[UserSwitchCore] Purchases.logIn() successful, created:', result.created);
78
- }
71
+ logger.debug("Purchases.logIn() successful, created", result.created);
79
72
  } else {
80
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
81
- console.log('[UserSwitchCore] User is anonymous, fetching customer info');
82
- }
73
+ logger.debug("User is anonymous, fetching customer info");
83
74
  customerInfo = await Purchases.getCustomerInfo();
84
75
  }
85
76
  } else {
86
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
87
- console.log('[UserSwitchCore] No user switch needed, fetching current customer info');
88
- }
77
+ logger.debug("No user switch needed, fetching current customer info");
89
78
  customerInfo = await Purchases.getCustomerInfo();
90
79
  }
91
80
 
@@ -93,9 +82,7 @@ async function performUserSwitch(
93
82
  deps.setCurrentUserId(normalizedUserId || undefined);
94
83
  const offerings = await fetchOfferingsSafe();
95
84
 
96
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
97
- console.log('[UserSwitchCore] User switch completed successfully');
98
- }
85
+ logger.debug("User switch completed successfully");
99
86
 
100
87
  return buildSuccessResult(deps, customerInfo, offerings);
101
88
  } catch (error) {
@@ -106,11 +93,7 @@ async function performUserSwitch(
106
93
  // Ignore error in error handler
107
94
  }
108
95
 
109
- console.error('[UserSwitchCore] Failed during user switch or fetch', {
110
- userId,
111
- currentAppUserId,
112
- error
113
- });
96
+ logger.error("Failed during user switch or fetch", error, { userId, currentAppUserId });
114
97
  return FAILED_INITIALIZATION_RESULT;
115
98
  }
116
99
  }
@@ -8,8 +8,9 @@ import Purchases, { type CustomerInfo, type PurchasesOfferings } from "react-nat
8
8
  import type { InitializeResult } from "../../../../shared/application/ports/IRevenueCatService";
9
9
  import type { InitializerDeps } from "./RevenueCatInitializer.types";
10
10
  import { ANONYMOUS_CACHE_KEY } from "../../../subscription/core/SubscriptionConstants";
11
+ import { createLogger } from "../../../../../shared/utils/logger";
11
12
 
12
- declare const __DEV__: boolean;
13
+ const logger = createLogger("UserSwitchHelpers");
13
14
 
14
15
  /**
15
16
  * Normalize user ID to null if empty or anonymous cache key.
@@ -47,9 +48,7 @@ export async function fetchOfferingsSafe(): Promise<PurchasesOfferings | null> {
47
48
  try {
48
49
  return await Purchases.getOfferings();
49
50
  } catch (error) {
50
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
51
- console.warn('[UserSwitchHelpers] Offerings fetch failed (non-fatal):', error);
52
- }
51
+ logger.warn("Offerings fetch failed (non-fatal)", error);
53
52
  return null;
54
53
  }
55
54
  }