@umituz/react-native-subscription 2.27.96 → 2.27.97

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 (33) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/core/Credits.ts +3 -11
  3. package/src/domains/paywall/components/PaywallContainer.tsx +17 -1
  4. package/src/domains/paywall/components/PaywallContainer.types.ts +2 -1
  5. package/src/domains/paywall/hooks/usePaywallActions.ts +1 -1
  6. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +32 -12
  7. package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +4 -4
  8. package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +1 -7
  9. package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -2
  10. package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +20 -7
  11. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +1 -1
  12. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +55 -16
  13. package/src/domains/subscription/presentation/screens/components/CreditsList.tsx +14 -1
  14. package/src/domains/subscription/presentation/screens/components/DevTestSection.tsx +10 -2
  15. package/src/domains/subscription/presentation/screens/components/SubscriptionActions.tsx +6 -1
  16. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +20 -1
  17. package/src/domains/subscription/presentation/screens/components/UpgradePrompt.tsx +13 -1
  18. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +1 -1
  19. package/src/domains/subscription/presentation/useFeatureGate.ts +11 -7
  20. package/src/domains/subscription/presentation/usePaywallVisibility.ts +1 -1
  21. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -5
  22. package/src/init/index.ts +0 -3
  23. package/src/presentation/hooks/index.ts +0 -4
  24. package/src/shared/infrastructure/SubscriptionEventBus.ts +27 -0
  25. package/src/utils/packageTypeDetector.ts +0 -4
  26. package/src/domains/subscription/presentation/types/README.md +0 -22
  27. package/src/domains/subscription/presentation/types/SubscriptionDetailTypes.ts +0 -153
  28. package/src/domains/subscription/presentation/types/SubscriptionSettingsTypes.ts +0 -74
  29. package/src/domains/subscription/presentation/useAuthSubscriptionSync.ts +0 -63
  30. package/src/domains/subscription/presentation/usePremiumGate.ts +0 -84
  31. package/src/domains/subscription/presentation/useSavedPurchaseAutoExecution.ts +0 -148
  32. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.ts +0 -115
  33. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.utils.ts +0 -57
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.27.96",
3
+ "version": "2.27.97",
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",
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { SubscriptionPackageType } from "../../../utils/packageTypeDetector";
9
+ // Types imported from SubscriptionConstants are used directly in UserCredits interface
9
10
  import type {
10
11
  SubscriptionStatusType,
11
12
  PeriodType,
@@ -15,15 +16,6 @@ import type {
15
16
  PurchaseType
16
17
  } from "../../subscription/core/SubscriptionConstants";
17
18
 
18
- export type {
19
- SubscriptionStatusType,
20
- PeriodType,
21
- PackageType,
22
- Platform,
23
- PurchaseSource,
24
- PurchaseType
25
- };
26
-
27
19
  export type CreditType = "text" | "image";
28
20
 
29
21
  /** Single Source of Truth for user subscription + credits data */
@@ -67,10 +59,10 @@ export interface CreditAllocation {
67
59
  credits: number;
68
60
  }
69
61
 
70
- export type PackageAllocationMap = Record<
62
+ export type PackageAllocationMap = Partial<Record<
71
63
  Exclude<SubscriptionPackageType, "unknown">,
72
64
  CreditAllocation
73
- >;
65
+ >>;
74
66
 
75
67
  export interface CreditsConfig {
76
68
  collectionName: string;
@@ -49,9 +49,25 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
49
49
  });
50
50
 
51
51
  // Check trial eligibility only if trialConfig is enabled
52
+ // Use ref to track if we've already checked for these packages to avoid redundant calls
53
+ const checkedPackagesRef = React.useRef<string[]>([]);
54
+
52
55
  useEffect(() => {
53
56
  if (!trialConfig?.enabled) return;
54
57
  if (packages.length === 0) return;
58
+ if (isLoading) return; // Wait for packages to fully load
59
+
60
+ // Get current package identifiers
61
+ const currentPackageIds = packages.map((pkg) => pkg.product.identifier);
62
+ const sortedIds = [...currentPackageIds].sort().join(",");
63
+
64
+ // Skip if we've already checked these exact packages
65
+ if (checkedPackagesRef.current.join(",") === sortedIds) {
66
+ return;
67
+ }
68
+
69
+ // Update ref
70
+ checkedPackagesRef.current = currentPackageIds;
55
71
 
56
72
  // Get all actual product IDs from packages
57
73
  const allProductIds = packages.map((pkg) => pkg.product.identifier);
@@ -72,7 +88,7 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
72
88
  if (productIdsToCheck.length > 0) {
73
89
  checkEligibility(productIdsToCheck);
74
90
  }
75
- }, [packages, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
91
+ }, [packages, isLoading, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
76
92
 
77
93
  // Convert eligibility map to format expected by PaywallModal
78
94
  // Only process if trial is enabled
@@ -5,7 +5,8 @@
5
5
 
6
6
  import type { ImageSourcePropType } from "react-native";
7
7
  import type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
8
- import type { PurchaseSource, PackageAllocationMap } from "../../credits/core/Credits";
8
+ import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
9
+ import type { PackageAllocationMap } from "../../credits/core/Credits";
9
10
 
10
11
  /**
11
12
  * Trial display configuration
@@ -2,7 +2,7 @@ import { useCallback } from "react";
2
2
  import type { PurchasesPackage } from "react-native-purchases";
3
3
  import { useRestorePurchase } from "../../subscription/infrastructure/hooks/useRestorePurchase";
4
4
  import { useAuthAwarePurchase } from "../../subscription/presentation/useAuthAwarePurchase";
5
- import type { PurchaseSource } from "../../credits/core/Credits";
5
+ import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
6
6
 
7
7
  interface UsePaywallActionsProps {
8
8
  source?: PurchaseSource;
@@ -25,21 +25,41 @@ export class PackageHandler {
25
25
 
26
26
  async fetchPackages(): Promise<PurchasesPackage[]> {
27
27
  if (!this.service.isInitialized()) {
28
- throw new Error("Service not initialized");
28
+ throw new Error("Service not initialized. Please initialize before fetching packages.");
29
29
  }
30
30
 
31
- const offering = await this.service.fetchOfferings();
32
-
33
- if (!offering) {
34
- throw new Error("No offerings available");
31
+ try {
32
+ const offering = await this.service.fetchOfferings();
33
+
34
+ if (!offering) {
35
+ if (__DEV__) {
36
+ console.warn("[PackageHandler] No offerings available from RevenueCat");
37
+ }
38
+ // Return empty array instead of throwing - allows graceful degradation
39
+ return [];
40
+ }
41
+
42
+ const packages = offering.availablePackages;
43
+ if (!packages || packages.length === 0) {
44
+ if (__DEV__) {
45
+ console.warn("[PackageHandler] No packages available in offering");
46
+ }
47
+ // Return empty array instead of throwing - allows graceful degradation
48
+ return [];
49
+ }
50
+
51
+ return packages;
52
+ } catch (error) {
53
+ if (__DEV__) {
54
+ console.error("[PackageHandler] Failed to fetch packages:", error);
55
+ }
56
+ // Re-throw with more context
57
+ throw new Error(
58
+ `Failed to fetch subscription packages. ${
59
+ error instanceof Error ? error.message : "Unknown error"
60
+ }`
61
+ );
35
62
  }
36
-
37
- const packages = offering.availablePackages;
38
- if (!packages) {
39
- throw new Error("No packages available in offering");
40
- }
41
-
42
- return packages;
43
63
  }
44
64
 
45
65
  async purchase(pkg: PurchasesPackage, userId: string): Promise<boolean> {
@@ -3,6 +3,9 @@
3
3
  * TanStack Query keys and constants for subscription state
4
4
  */
5
5
 
6
+ /** Query cache time constants */
7
+
8
+
6
9
  /**
7
10
  * Query keys for TanStack Query
8
11
  */
@@ -12,7 +15,4 @@ export const SUBSCRIPTION_QUERY_KEYS = {
12
15
  ["subscription", "initialized", userId] as const,
13
16
  } as const;
14
17
 
15
- // No cache - always fetch fresh data for subscription packages
16
- // This ensures users always see real-time subscription status
17
- export const STALE_TIME = 0; // Always stale - refetch immediately
18
- export const GC_TIME = 0; // Don't cache - garbage collect immediately
18
+
@@ -12,8 +12,6 @@ import {
12
12
  import { SubscriptionManager } from '../../infrastructure/managers/SubscriptionManager';
13
13
  import {
14
14
  SUBSCRIPTION_QUERY_KEYS,
15
- STALE_TIME,
16
- GC_TIME,
17
15
  } from "./subscriptionQueryKeys";
18
16
 
19
17
  /**
@@ -41,11 +39,7 @@ export const useSubscriptionPackages = () => {
41
39
 
42
40
  return SubscriptionManager.getPackages();
43
41
  },
44
- staleTime: STALE_TIME,
45
- gcTime: GC_TIME,
46
42
  enabled: isConfigured,
47
- refetchOnMount: true,
48
- refetchOnWindowFocus: true, // Refetch when app becomes active
49
- refetchOnReconnect: true, // Refetch when network reconnects
43
+
50
44
  });
51
45
  };
@@ -6,8 +6,6 @@
6
6
 
7
7
  export {
8
8
  SUBSCRIPTION_QUERY_KEYS,
9
- STALE_TIME,
10
- GC_TIME,
11
9
  } from "./subscriptionQueryKeys";
12
10
  export { useInitializeSubscription } from "./useInitializeSubscription";
13
11
  export { useSubscriptionPackages } from "./useSubscriptionPackages";
@@ -11,6 +11,8 @@ export class InitializationCache {
11
11
  private initializationInProgress = false;
12
12
  // Track which userId the promise is for (separate from currentUserId which is set after completion)
13
13
  private promiseUserId: string | null = null;
14
+ // Track promise completion state to avoid returning failed promises
15
+ private promiseCompleted = true;
14
16
 
15
17
  /**
16
18
  * Atomically check if reinitialization is needed AND reserve the slot
@@ -22,17 +24,24 @@ export class InitializationCache {
22
24
  return { shouldInit: false, existingPromise: this.initPromise };
23
25
  }
24
26
 
25
- // If already initialized for this user and promise resolved successfully, return it
26
- if (this.initPromise && this.currentUserId === userId && !this.initializationInProgress) {
27
+ // If already initialized for this user and promise completed successfully, return it
28
+ // Only return cached promise if it completed AND it's for the same user
29
+ if (this.initPromise && this.currentUserId === userId && !this.initializationInProgress && this.promiseCompleted) {
27
30
  return { shouldInit: false, existingPromise: this.initPromise };
28
31
  }
29
32
 
30
- // Different user or no initialization - need to reinitialize
31
- // Atomically set the flag
32
- this.initializationInProgress = true;
33
- this.promiseUserId = userId;
33
+ // Different user, no initialization, or failed promise - need to reinitialize
34
+ // Atomically set the flag and clear previous state if needed
35
+ if (!this.initializationInProgress) {
36
+ this.initializationInProgress = true;
37
+ this.promiseUserId = userId;
38
+ this.promiseCompleted = false;
39
+ return { shouldInit: true, existingPromise: null };
40
+ }
34
41
 
35
- return { shouldInit: true, existingPromise: null };
42
+ // If we reach here, initialization is in progress for a different user
43
+ // Wait for current initialization to complete
44
+ return { shouldInit: false, existingPromise: this.initPromise };
36
45
  }
37
46
 
38
47
  setPromise(promise: Promise<boolean>, userId: string): void {
@@ -45,6 +54,7 @@ export class InitializationCache {
45
54
  if (result && this.promiseUserId === userId) {
46
55
  this.currentUserId = userId;
47
56
  }
57
+ this.promiseCompleted = true;
48
58
  return result;
49
59
  })
50
60
  .catch(() => {
@@ -52,7 +62,9 @@ export class InitializationCache {
52
62
  if (this.promiseUserId === userId) {
53
63
  this.initPromise = null;
54
64
  this.promiseUserId = null;
65
+ this.currentUserId = null; // Clear user on failure
55
66
  }
67
+ this.promiseCompleted = true;
56
68
  })
57
69
  .finally(() => {
58
70
  // Always release the mutex
@@ -71,5 +83,6 @@ export class InitializationCache {
71
83
  this.currentUserId = null;
72
84
  this.initializationInProgress = false;
73
85
  this.promiseUserId = null;
86
+ this.promiseCompleted = true;
74
87
  }
75
88
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { CustomerInfo } from "react-native-purchases";
7
7
  import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
8
- import type { PurchaseSource } from "../../../credits/core/Credits";
8
+ import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
9
9
  import { getPremiumEntitlement } from "../../core/RevenueCatTypes";
10
10
 
11
11
  export async function syncPremiumStatus(
@@ -11,22 +11,61 @@ import {
11
11
  ScreenLayout,
12
12
  } from "@umituz/react-native-design-system";
13
13
  import { SubscriptionHeader } from "./components/SubscriptionHeader";
14
- import { CreditsList } from "./components/CreditsList";
15
- import { UpgradePrompt } from "./components/UpgradePrompt";
16
- import { DevTestSection } from "./components/DevTestSection";
17
- import type { SubscriptionDetailScreenProps } from "../types/SubscriptionDetailTypes";
14
+ import { CreditsList, type CreditItem } from "./components/CreditsList";
15
+ import { UpgradePrompt, type Benefit } from "./components/UpgradePrompt";
16
+ import { DevTestSection, type DevTestActions } from "./components/DevTestSection";
18
17
 
19
- export type {
20
- SubscriptionDisplayFlags,
21
- SubscriptionDetailTranslations,
22
- SubscriptionDetailConfig,
23
- SubscriptionDetailScreenProps,
24
- DevTestActions,
25
- DevToolsConfig,
26
- UpgradeBenefit,
27
- UpgradePromptConfig,
28
- UpgradePromptProps,
29
- } from "../types/SubscriptionDetailTypes";
18
+ export interface SubscriptionDisplayFlags {
19
+ showHeader: boolean;
20
+ showCredits: boolean;
21
+ showUpgradePrompt: boolean;
22
+ showExpirationDate: boolean;
23
+ }
24
+
25
+ export interface SubscriptionDetailTranslations {
26
+ title: string;
27
+ statusActive: string;
28
+ statusExpired: string;
29
+ statusFree: string;
30
+ statusCanceled: string;
31
+ statusLabel: string;
32
+ lifetimeLabel: string;
33
+ expiresLabel: string;
34
+ purchasedLabel: string;
35
+ usageTitle?: string;
36
+ creditsTitle: string;
37
+ creditsResetInfo?: string;
38
+ remainingLabel?: string;
39
+ upgradeButton: string;
40
+ }
41
+
42
+ export interface DevToolsConfig {
43
+ actions: DevTestActions;
44
+ title?: string;
45
+ }
46
+
47
+ export interface UpgradePromptConfig {
48
+ title: string;
49
+ subtitle?: string;
50
+ benefits?: readonly Benefit[];
51
+ }
52
+
53
+ export interface SubscriptionDetailConfig {
54
+ display: SubscriptionDisplayFlags;
55
+ statusType: "active" | "expired" | "none" | "canceled";
56
+ isLifetime: boolean;
57
+ expirationDate?: string;
58
+ purchaseDate?: string;
59
+ daysRemaining?: number | null;
60
+ credits?: readonly CreditItem[];
61
+ translations: SubscriptionDetailTranslations;
62
+ upgradePrompt?: UpgradePromptConfig & { onUpgrade?: () => void };
63
+ devTools?: DevToolsConfig;
64
+ }
65
+
66
+ export interface SubscriptionDetailScreenProps {
67
+ config: SubscriptionDetailConfig;
68
+ }
30
69
 
31
70
  export const SubscriptionDetailScreen: React.FC<
32
71
  SubscriptionDetailScreenProps
@@ -98,7 +137,7 @@ export const SubscriptionDetailScreen: React.FC<
98
137
  subtitle={config.upgradePrompt.subtitle}
99
138
  benefits={config.upgradePrompt.benefits}
100
139
  upgradeButtonLabel={config.translations.upgradeButton}
101
- onUpgrade={config.onUpgrade}
140
+ onUpgrade={config.upgradePrompt.onUpgrade ?? (() => {})}
102
141
  />
103
142
  )}
104
143
  </View>
@@ -7,7 +7,20 @@ import React, { useMemo } from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
8
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
9
  import { CreditRow } from "../../components/details/CreditRow";
10
- import type { CreditsListProps } from "../../types/SubscriptionDetailTypes";
10
+
11
+ export interface CreditItem {
12
+ id: string;
13
+ label: string;
14
+ current: number;
15
+ total: number;
16
+ }
17
+
18
+ export interface CreditsListProps {
19
+ credits: readonly CreditItem[];
20
+ title?: string;
21
+ description?: string;
22
+ remainingLabel?: string;
23
+ }
11
24
 
12
25
  export const CreditsList: React.FC<CreditsListProps> = ({
13
26
  credits,
@@ -7,9 +7,17 @@
7
7
  import React, { useMemo } from "react";
8
8
  import { View, TouchableOpacity, StyleSheet } from "react-native";
9
9
  import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
10
- import type { DevTestSectionProps } from "../../types/SubscriptionDetailTypes";
11
10
 
12
- export type { DevTestActions, DevTestSectionProps } from "../../types/SubscriptionDetailTypes";
11
+ export interface DevTestActions {
12
+ onTestRenewal: () => void;
13
+ onCheckCredits: () => void;
14
+ onTestDuplicate: () => void;
15
+ }
16
+
17
+ export interface DevTestSectionProps {
18
+ actions: DevTestActions;
19
+ title?: string;
20
+ }
13
21
 
14
22
  /** Dev test button translations */
15
23
  export interface DevTestTranslations {
@@ -6,7 +6,12 @@
6
6
  import React, { useMemo } from "react";
7
7
  import { View, StyleSheet, TouchableOpacity } from "react-native";
8
8
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
- import type { SubscriptionActionsProps } from "../../types/SubscriptionDetailTypes";
9
+
10
+ export interface SubscriptionActionsProps {
11
+ isPremium: boolean;
12
+ upgradeButtonLabel?: string;
13
+ onUpgrade?: () => void;
14
+ }
10
15
 
11
16
  export const SubscriptionActions: React.FC<SubscriptionActionsProps> = ({
12
17
  isPremium,
@@ -8,7 +8,26 @@ import { View, StyleSheet } from "react-native";
8
8
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
9
9
  import { PremiumStatusBadge } from "../../components/details/PremiumStatusBadge";
10
10
  import { DetailRow } from "../../components/details/DetailRow";
11
- import type { SubscriptionHeaderProps } from "../../types/SubscriptionDetailTypes";
11
+
12
+ export interface SubscriptionHeaderProps {
13
+ statusType: "active" | "expired" | "none" | "canceled";
14
+ showExpirationDate: boolean;
15
+ isLifetime: boolean;
16
+ expirationDate?: string;
17
+ purchaseDate?: string;
18
+ daysRemaining?: number | null;
19
+ translations: {
20
+ title: string;
21
+ statusActive: string;
22
+ statusExpired: string;
23
+ statusFree: string;
24
+ statusCanceled: string;
25
+ statusLabel: string;
26
+ lifetimeLabel: string;
27
+ expiresLabel: string;
28
+ purchasedLabel: string;
29
+ };
30
+ }
12
31
 
13
32
  export const SubscriptionHeader: React.FC<SubscriptionHeaderProps> = ({
14
33
  statusType,
@@ -10,7 +10,19 @@ import {
10
10
  AtomicText,
11
11
  AtomicIcon,
12
12
  } from "@umituz/react-native-design-system";
13
- import type { UpgradePromptProps } from "../../types/SubscriptionDetailTypes";
13
+
14
+ export interface Benefit {
15
+ icon?: string;
16
+ text: string;
17
+ }
18
+
19
+ export interface UpgradePromptProps {
20
+ title: string;
21
+ subtitle?: string;
22
+ benefits?: readonly Benefit[];
23
+ upgradeButtonLabel: string;
24
+ onUpgrade: () => void;
25
+ }
14
26
 
15
27
  export const UpgradePrompt: React.FC<UpgradePromptProps> = ({
16
28
  title,
@@ -6,7 +6,7 @@
6
6
  import { useCallback } from "react";
7
7
  import type { PurchasesPackage } from "react-native-purchases";
8
8
  import { usePremium } from "./usePremium";
9
- import type { PurchaseSource } from "../../credits/core/Credits";
9
+ import type { PurchaseSource } from "../core/SubscriptionConstants";
10
10
 
11
11
  export interface PurchaseAuthProvider {
12
12
  isAuthenticated: () => boolean;
@@ -135,9 +135,9 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
135
135
  if (typeof __DEV__ !== "undefined" && __DEV__) {
136
136
  console.log("[useFeatureGate] requireFeature", {
137
137
  isAuthenticated,
138
- hasSubscription,
138
+ hasSubscription: hasSubscriptionRef.current,
139
139
  creditBalance: creditBalanceRef.current,
140
- requiredCredits,
140
+ requiredCredits: requiredCreditsRef.current,
141
141
  isCreditsLoaded,
142
142
  });
143
143
  }
@@ -155,25 +155,29 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
155
155
  return;
156
156
  }
157
157
 
158
- if (hasSubscription) {
158
+ // Use ref values to avoid stale closure
159
+ const currentHasSubscription = hasSubscriptionRef.current;
160
+ const currentBalance = creditBalanceRef.current;
161
+ const currentRequiredCredits = requiredCreditsRef.current;
162
+
163
+ if (currentHasSubscription) {
159
164
  action();
160
165
  return;
161
166
  }
162
167
 
163
- const currentBalance = creditBalanceRef.current;
164
- if (currentBalance < requiredCredits) {
168
+ if (currentBalance < currentRequiredCredits) {
165
169
  if (typeof __DEV__ !== "undefined" && __DEV__) {
166
170
  console.log("[useFeatureGate] No credits, showing paywall");
167
171
  }
168
172
  pendingActionRef.current = action;
169
173
  isWaitingForPurchaseRef.current = true;
170
- onShowPaywall(requiredCredits);
174
+ onShowPaywallRef.current(currentRequiredCredits);
171
175
  return;
172
176
  }
173
177
 
174
178
  action();
175
179
  },
176
- [isAuthenticated, hasSubscription, requiredCredits, onShowAuthModal, onShowPaywall, isCreditsLoaded]
180
+ [isAuthenticated, onShowAuthModal, isCreditsLoaded]
177
181
  );
178
182
 
179
183
  const hasCredits = creditBalance >= requiredCredits;
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { useCallback, useSyncExternalStore } from "react";
8
- import type { PurchaseSource } from "../../credits/core/Credits";
8
+ import type { PurchaseSource } from "../core/SubscriptionConstants";
9
9
 
10
10
  type Listener = () => void;
11
11
 
@@ -48,11 +48,7 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
48
48
  }
49
49
  },
50
50
  enabled: !!userId && SubscriptionManager.isInitializedForUser(userId),
51
- staleTime: 30 * 1000, // 30 seconds
52
- gcTime: 5 * 60 * 1000, // 5 minutes
53
- refetchOnMount: "always",
54
- refetchOnWindowFocus: true,
55
- refetchOnReconnect: true,
51
+
56
52
  });
57
53
 
58
54
  return {
package/src/init/index.ts CHANGED
@@ -7,6 +7,3 @@ export {
7
7
  createSubscriptionInitModule,
8
8
  type SubscriptionInitModuleConfig,
9
9
  } from './createSubscriptionInitModule';
10
-
11
- // Re-export InitModule from design-system for convenience
12
- export type { InitModule } from '@umituz/react-native-design-system';
@@ -1,13 +1,9 @@
1
1
  export * from "../../domains/subscription/presentation/useAuthAwarePurchase";
2
- export * from "../../domains/subscription/presentation/useAuthSubscriptionSync";
3
- export * from "../../domains/subscription/presentation/useSavedPurchaseAutoExecution";
4
2
  export * from "../../domains/credits/presentation/useCredits";
5
3
  export * from "../../domains/credits/presentation/useDeductCredit";
6
4
  export * from "../../domains/subscription/presentation/useFeatureGate";
7
5
  export * from "../../domains/subscription/presentation/usePaywallVisibility";
8
6
  export * from "../../domains/subscription/presentation/usePremium";
9
- export * from "../../domains/subscription/presentation/usePremiumGate";
10
- export * from "../../domains/subscription/presentation/useSubscriptionSettingsConfig";
11
7
  export * from "../../domains/subscription/presentation/useSubscriptionStatus";
12
8
  export * from "./feedback/usePaywallFeedback";
13
9
  export * from "./feedback/useFeedbackSubmit";
@@ -28,6 +28,11 @@ export class SubscriptionEventBus {
28
28
  const listeners = this.listeners[event];
29
29
  if (listeners) {
30
30
  this.listeners[event] = listeners.filter(l => l !== callback);
31
+
32
+ // Clean up empty event arrays to prevent memory leak
33
+ if (this.listeners[event].length === 0) {
34
+ delete this.listeners[event];
35
+ }
31
36
  }
32
37
  };
33
38
  }
@@ -42,6 +47,28 @@ export class SubscriptionEventBus {
42
47
  }
43
48
  });
44
49
  }
50
+
51
+ /**
52
+ * Clear all listeners for a specific event or all events
53
+ * Useful for cleanup during testing or app state reset
54
+ */
55
+ clear(event?: string): void {
56
+ if (event) {
57
+ delete this.listeners[event];
58
+ } else {
59
+ this.listeners = {};
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Get listener count for debugging
65
+ */
66
+ getListenerCount(event?: string): number {
67
+ if (event) {
68
+ return this.listeners[event]?.length ?? 0;
69
+ }
70
+ return Object.values(this.listeners).reduce((total, arr) => total + arr.length, 0);
71
+ }
45
72
  }
46
73
 
47
74
  export const subscriptionEventBus = SubscriptionEventBus.getInstance();
@@ -7,10 +7,6 @@ import { PACKAGE_TYPE, type PackageType } from "../domains/subscription/core/Sub
7
7
 
8
8
  export type SubscriptionPackageType = PackageType;
9
9
 
10
- /**
11
- * Check if identifier is a credit package (consumable purchase)
12
- * Credit packages use a different system and don't need type detection
13
- */
14
10
  /**
15
11
  * Check if identifier is a credit package (consumable purchase)
16
12
  * Credit packages use a different system and don't need type detection
@@ -1,22 +0,0 @@
1
- # Presentation Types
2
-
3
- TypeScript type definitions and interfaces for the presentation layer.
4
-
5
- ## Overview
6
-
7
- This directory contains all type definitions used by presentation components and hooks.
8
-
9
- ## Contents
10
-
11
- ### Subscription Types
12
-
13
- - **SubscriptionSettingsTypes.ts** - Configuration types for subscription settings UI
14
- - **PaywallTypes.ts** - Paywall component types
15
- - **SubscriptionTypes.ts** - General subscription UI types
16
-
17
- ## Key Exports
18
-
19
- ## Related
20
-
21
- - [Hooks](../hooks/README.md)
22
- - [Components](../components/README.md)