@umituz/react-native-subscription 2.3.8 → 2.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.3.8",
3
+ "version": "2.5.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",
@@ -35,6 +35,7 @@
35
35
  "@umituz/react-native-firestore": "latest",
36
36
  "@umituz/react-native-legal": "latest",
37
37
  "@umituz/react-native-localization": "latest",
38
+ "@umituz/react-native-sentry": "latest",
38
39
  "expo-constants": ">=16.0.0",
39
40
  "firebase": ">=10.0.0",
40
41
  "react": ">=18.2.0",
@@ -47,6 +48,7 @@
47
48
  "@umituz/react-native-firestore": "latest",
48
49
  "@umituz/react-native-legal": "latest",
49
50
  "@umituz/react-native-localization": "latest",
51
+ "@umituz/react-native-sentry": "latest",
50
52
  "@tanstack/react-query": "^5.0.0",
51
53
  "expo-constants": "~16.0.0",
52
54
  "firebase": "^10.0.0",
@@ -18,6 +18,10 @@ import {
18
18
  syncPremiumStatus,
19
19
  notifyPurchaseCompleted,
20
20
  } from "../utils/PremiumStatusSyncer";
21
+ import {
22
+ trackPackageError,
23
+ addPackageBreadcrumb,
24
+ } from "@umituz/react-native-sentry";
21
25
 
22
26
  export interface PurchaseHandlerDeps {
23
27
  config: RevenueCatConfig;
@@ -42,12 +46,20 @@ export async function handlePurchase(
42
46
  pkg: PurchasesPackage,
43
47
  userId: string
44
48
  ): Promise<PurchaseResult> {
45
- if (__DEV__) {
46
- console.log("[RevenueCat] handlePurchase() called for:", pkg.product.identifier);
47
- }
49
+ addPackageBreadcrumb("subscription", "Purchase started", {
50
+ productId: pkg.product.identifier,
51
+ userId,
52
+ });
48
53
 
49
54
  if (!deps.isInitialized()) {
50
- throw new RevenueCatInitializationError();
55
+ const error = new RevenueCatInitializationError();
56
+ trackPackageError(error, {
57
+ packageName: "subscription",
58
+ operation: "purchase",
59
+ userId,
60
+ productId: pkg.product.identifier,
61
+ });
62
+ throw error;
51
63
  }
52
64
 
53
65
 
@@ -85,15 +97,35 @@ export async function handlePurchase(
85
97
  return { success: true, isPremium: true, customerInfo };
86
98
  }
87
99
 
88
- throw new RevenueCatPurchaseError(
100
+ const entitlementError = new RevenueCatPurchaseError(
89
101
  "Purchase completed but premium entitlement not active",
90
102
  pkg.product.identifier
91
103
  );
104
+ trackPackageError(entitlementError, {
105
+ packageName: "subscription",
106
+ operation: "purchase",
107
+ userId,
108
+ productId: pkg.product.identifier,
109
+ reason: "entitlement_not_active",
110
+ });
111
+ throw entitlementError;
92
112
  } catch (error) {
93
113
  if (isUserCancelledError(error)) {
114
+ addPackageBreadcrumb("subscription", "Purchase cancelled by user", {
115
+ productId: pkg.product.identifier,
116
+ userId,
117
+ });
94
118
  return { success: false, isPremium: false };
95
119
  }
96
120
  const errorMessage = getErrorMessage(error, "Purchase failed");
97
- throw new RevenueCatPurchaseError(errorMessage, pkg.product.identifier);
121
+ const purchaseError = new RevenueCatPurchaseError(errorMessage, pkg.product.identifier);
122
+ trackPackageError(purchaseError, {
123
+ packageName: "subscription",
124
+ operation: "purchase",
125
+ userId,
126
+ productId: pkg.product.identifier,
127
+ originalError: error instanceof Error ? error.message : String(error),
128
+ });
129
+ throw purchaseError;
98
130
  }
99
131
  }
@@ -15,6 +15,10 @@ import {
15
15
  syncPremiumStatus,
16
16
  notifyRestoreCompleted,
17
17
  } from "../utils/PremiumStatusSyncer";
18
+ import {
19
+ trackPackageError,
20
+ addPackageBreadcrumb,
21
+ } from "@umituz/react-native-sentry";
18
22
 
19
23
  export interface RestoreHandlerDeps {
20
24
  config: RevenueCatConfig;
@@ -29,12 +33,18 @@ export async function handleRestore(
29
33
  deps: RestoreHandlerDeps,
30
34
  userId: string
31
35
  ): Promise<RestoreResult> {
36
+ addPackageBreadcrumb("subscription", "Restore started", { userId });
37
+
32
38
  if (!deps.isInitialized()) {
33
- throw new RevenueCatInitializationError();
39
+ const error = new RevenueCatInitializationError();
40
+ trackPackageError(error, {
41
+ packageName: "subscription",
42
+ operation: "restore",
43
+ userId,
44
+ });
45
+ throw error;
34
46
  }
35
47
 
36
-
37
-
38
48
  try {
39
49
  const customerInfo = await Purchases.restorePurchases();
40
50
  const entitlementIdentifier = deps.config.entitlementIdentifier;
@@ -42,12 +52,28 @@ export async function handleRestore(
42
52
 
43
53
  if (isPremium) {
44
54
  await syncPremiumStatus(deps.config, userId, customerInfo);
55
+ addPackageBreadcrumb("subscription", "Restore successful - premium active", {
56
+ userId,
57
+ entitlementId: entitlementIdentifier,
58
+ });
59
+ } else {
60
+ addPackageBreadcrumb("subscription", "Restore completed - no premium found", {
61
+ userId,
62
+ });
45
63
  }
64
+
46
65
  await notifyRestoreCompleted(deps.config, userId, isPremium, customerInfo);
47
66
 
48
67
  return { success: isPremium, isPremium, customerInfo };
49
68
  } catch (error) {
50
69
  const errorMessage = getErrorMessage(error, "Restore failed");
51
- throw new RevenueCatRestoreError(errorMessage);
70
+ const restoreError = new RevenueCatRestoreError(errorMessage);
71
+ trackPackageError(restoreError, {
72
+ packageName: "subscription",
73
+ operation: "restore",
74
+ userId,
75
+ originalError: error instanceof Error ? error.message : String(error),
76
+ });
77
+ throw restoreError;
52
78
  }
53
79
  }
@@ -19,6 +19,11 @@ import { handlePurchase } from "./PurchaseHandler";
19
19
  import { handleRestore } from "./RestoreHandler";
20
20
  import { CustomerInfoListenerManager } from "./CustomerInfoListenerManager";
21
21
  import { ServiceStateManager } from "./ServiceStateManager";
22
+ import {
23
+ trackPackageError,
24
+ addPackageBreadcrumb,
25
+ trackPackageWarning,
26
+ } from "@umituz/react-native-sentry";
22
27
 
23
28
  export class RevenueCatService implements IRevenueCatService {
24
29
  private stateManager: ServiceStateManager;
@@ -50,31 +55,46 @@ export class RevenueCatService implements IRevenueCatService {
50
55
  async initialize(userId: string, apiKey?: string): Promise<InitializeResult> {
51
56
  // If already initialized for this user, return success immediately
52
57
  if (this.isInitialized() && this.getCurrentUserId() === userId) {
53
- if (__DEV__) {
54
- console.log("[RevenueCat] Already initialized for user:", userId);
55
- }
56
- return { success: true, offering: (await this.fetchOfferings()), hasPremium: false }; // fetchOfferings handles cache usually
58
+ addPackageBreadcrumb("subscription", "Already initialized", { userId });
59
+ return { success: true, offering: (await this.fetchOfferings()), hasPremium: false };
57
60
  }
58
61
 
59
- const result = await initializeSDK(
60
- {
61
- config: this.stateManager.getConfig(),
62
- isUsingTestStore: () => this.isUsingTestStore(),
63
- isInitialized: () => this.isInitialized(),
64
- getCurrentUserId: () => this.stateManager.getCurrentUserId(),
65
- setInitialized: (value) => this.stateManager.setInitialized(value),
66
- setCurrentUserId: (id) => this.stateManager.setCurrentUserId(id),
67
- },
68
- userId,
69
- apiKey
70
- );
62
+ addPackageBreadcrumb("subscription", "Initialization started", { userId });
71
63
 
72
- if (result.success) {
73
- this.listenerManager.setUserId(userId);
74
- this.listenerManager.setupListener(this.stateManager.getConfig());
75
- }
64
+ try {
65
+ const result = await initializeSDK(
66
+ {
67
+ config: this.stateManager.getConfig(),
68
+ isUsingTestStore: () => this.isUsingTestStore(),
69
+ isInitialized: () => this.isInitialized(),
70
+ getCurrentUserId: () => this.stateManager.getCurrentUserId(),
71
+ setInitialized: (value) => this.stateManager.setInitialized(value),
72
+ setCurrentUserId: (id) => this.stateManager.setCurrentUserId(id),
73
+ },
74
+ userId,
75
+ apiKey
76
+ );
77
+
78
+ if (result.success) {
79
+ this.listenerManager.setUserId(userId);
80
+ this.listenerManager.setupListener(this.stateManager.getConfig());
81
+ addPackageBreadcrumb("subscription", "Initialization successful", { userId });
82
+ } else {
83
+ trackPackageWarning("subscription", "Initialization failed", {
84
+ userId,
85
+ hasOffering: !!result.offering,
86
+ });
87
+ }
76
88
 
77
- return result;
89
+ return result;
90
+ } catch (error) {
91
+ trackPackageError(error instanceof Error ? error : new Error(String(error)), {
92
+ packageName: "subscription",
93
+ operation: "initialize",
94
+ userId,
95
+ });
96
+ throw error;
97
+ }
78
98
  }
79
99
 
80
100
  async fetchOfferings(): Promise<PurchasesOffering | null> {
@@ -113,13 +133,20 @@ export class RevenueCatService implements IRevenueCatService {
113
133
  async reset(): Promise<void> {
114
134
  if (!this.isInitialized()) return;
115
135
 
136
+ addPackageBreadcrumb("subscription", "Reset started", {
137
+ userId: this.getCurrentUserId(),
138
+ });
139
+
116
140
  this.listenerManager.destroy();
117
141
 
118
142
  try {
119
143
  await Purchases.logOut();
120
144
  this.stateManager.setInitialized(false);
121
- } catch {
122
- // Reset errors are non-critical
145
+ addPackageBreadcrumb("subscription", "Reset successful", {});
146
+ } catch (error) {
147
+ trackPackageWarning("subscription", "Reset failed (non-critical)", {
148
+ error: error instanceof Error ? error.message : String(error),
149
+ });
123
150
  }
124
151
  }
125
152
  }
@@ -38,15 +38,16 @@ export function shouldUseTestStore(config: RevenueCatConfig): boolean {
38
38
  export function resolveApiKey(config: RevenueCatConfig): string | null {
39
39
  const useTestStore = shouldUseTestStore(config);
40
40
 
41
- if (__DEV__) {
42
- console.log("[RevenueCat] resolveApiKey:", {
43
- platform: Platform.OS,
44
- useTestStore,
45
- hasTestKey: !!config.testStoreKey,
46
- hasIosKey: !!config.iosApiKey,
47
- hasAndroidKey: !!config.androidApiKey,
48
- });
49
- }
41
+ // Always log in development, log warnings in production for debugging
42
+ /* eslint-disable-next-line no-console */
43
+ console.log("[RevenueCat] resolveApiKey:", {
44
+ platform: Platform.OS,
45
+ useTestStore,
46
+ hasTestKey: !!config.testStoreKey,
47
+ hasIosKey: !!config.iosApiKey,
48
+ hasAndroidKey: !!config.androidApiKey,
49
+ isProduction: isProductionBuild(),
50
+ });
50
51
 
51
52
  if (useTestStore) {
52
53
  return config.testStoreKey ?? null;
@@ -59,9 +60,8 @@ export function resolveApiKey(config: RevenueCatConfig): string | null {
59
60
  : config.iosApiKey;
60
61
 
61
62
  if (!key || key === "" || key.includes("YOUR_")) {
62
- if (__DEV__) {
63
- console.warn("[RevenueCat] No valid API key found for platform:", Platform.OS);
64
- }
63
+ /* eslint-disable-next-line no-console */
64
+ console.warn("[RevenueCat] ⚠️ NO API KEY - packages will not load. Check env vars.");
65
65
  return null;
66
66
  }
67
67