@umituz/react-native-subscription 2.7.0 → 2.9.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.7.0",
3
+ "version": "2.9.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",
@@ -9,6 +9,7 @@ import Purchases, {
9
9
  } from "react-native-purchases";
10
10
  import type { RevenueCatConfig } from "../../domain/value-objects/RevenueCatConfig";
11
11
  import { syncPremiumStatus } from "../utils/PremiumStatusSyncer";
12
+ import { addPackageBreadcrumb } from "@umituz/react-native-sentry";
12
13
 
13
14
  export class CustomerInfoListenerManager {
14
15
  private listener: CustomerInfoUpdateListener | null = null;
@@ -30,12 +31,22 @@ export class CustomerInfoListenerManager {
30
31
  setupListener(config: RevenueCatConfig): void {
31
32
  this.removeListener();
32
33
 
34
+ addPackageBreadcrumb("subscription", "Setting up customer info listener", {
35
+ userId: this.currentUserId,
36
+ });
37
+
33
38
  this.listener = (customerInfo: CustomerInfo) => {
34
39
  if (!this.currentUserId) return;
35
40
 
36
41
  const hasPremium =
37
42
  !!customerInfo.entitlements.active[this.entitlementIdentifier];
38
43
 
44
+ addPackageBreadcrumb("subscription", "Customer info updated", {
45
+ userId: this.currentUserId,
46
+ hasPremium,
47
+ entitlementIdentifier: this.entitlementIdentifier,
48
+ });
49
+
39
50
  if (__DEV__) {
40
51
  console.log("[RevenueCat] CustomerInfo updated", {
41
52
  userId: this.currentUserId,
@@ -52,6 +63,7 @@ export class CustomerInfoListenerManager {
52
63
 
53
64
  removeListener(): void {
54
65
  if (this.listener) {
66
+ addPackageBreadcrumb("subscription", "Removing customer info listener", {});
55
67
  Purchases.removeCustomerInfoUpdateListener(this.listener);
56
68
  this.listener = null;
57
69
  }
@@ -4,7 +4,11 @@
4
4
  */
5
5
 
6
6
  import Purchases, { type PurchasesOffering } from "react-native-purchases";
7
-
7
+ import {
8
+ trackPackageError,
9
+ addPackageBreadcrumb,
10
+ trackPackageWarning,
11
+ } from "@umituz/react-native-sentry";
8
12
 
9
13
  export interface OfferingsFetcherDeps {
10
14
  isInitialized: () => boolean;
@@ -14,6 +18,10 @@ export interface OfferingsFetcherDeps {
14
18
  export async function fetchOfferings(
15
19
  deps: OfferingsFetcherDeps
16
20
  ): Promise<PurchasesOffering | null> {
21
+ addPackageBreadcrumb("subscription", "Fetch offerings started", {
22
+ isInitialized: deps.isInitialized(),
23
+ });
24
+
17
25
  if (__DEV__) {
18
26
  console.log(
19
27
  "[RevenueCat] fetchOfferings() called, isInitialized:",
@@ -22,26 +30,41 @@ export async function fetchOfferings(
22
30
  }
23
31
 
24
32
  if (!deps.isInitialized()) {
33
+ trackPackageWarning("subscription", "Fetch offerings called before initialization", {});
34
+
25
35
  if (__DEV__) {
26
36
  console.log("[RevenueCat] fetchOfferings() - NOT initialized");
27
37
  }
28
38
  return null;
29
39
  }
30
40
 
31
-
32
-
33
41
  try {
34
42
  const offerings = await Purchases.getOfferings();
35
43
 
44
+ const packagesCount = offerings.current?.availablePackages?.length ?? 0;
45
+
46
+ addPackageBreadcrumb("subscription", "Fetch offerings success", {
47
+ hasCurrent: !!offerings.current,
48
+ packagesCount,
49
+ });
50
+
36
51
  if (__DEV__) {
37
52
  console.log("[RevenueCat] fetchOfferings() result:", {
38
53
  hasCurrent: !!offerings.current,
39
- packagesCount: offerings.current?.availablePackages?.length ?? 0,
54
+ packagesCount,
40
55
  });
41
56
  }
42
57
 
43
58
  return offerings.current;
44
59
  } catch (error) {
60
+ trackPackageError(
61
+ error instanceof Error ? error : new Error(String(error)),
62
+ {
63
+ packageName: "subscription",
64
+ operation: "fetch_offerings",
65
+ }
66
+ );
67
+
45
68
  if (__DEV__) {
46
69
  console.log("[RevenueCat] fetchOfferings() error:", error);
47
70
  }
@@ -7,8 +7,12 @@ import Purchases from "react-native-purchases";
7
7
  import type { InitializeResult } from "../../application/ports/IRevenueCatService";
8
8
  import type { RevenueCatConfig } from "../../domain/value-objects/RevenueCatConfig";
9
9
  import { getErrorMessage } from "../../domain/types/RevenueCatTypes";
10
-
11
10
  import { resolveApiKey } from "../utils/ApiKeyResolver";
11
+ import {
12
+ trackPackageError,
13
+ addPackageBreadcrumb,
14
+ trackPackageWarning,
15
+ } from "@umituz/react-native-sentry";
12
16
 
13
17
  export interface InitializerDeps {
14
18
  config: RevenueCatConfig;
@@ -24,6 +28,11 @@ export async function initializeSDK(
24
28
  userId: string,
25
29
  apiKey?: string
26
30
  ): Promise<InitializeResult> {
31
+ addPackageBreadcrumb("subscription", "SDK initialization started", {
32
+ userId,
33
+ hasApiKey: !!apiKey,
34
+ });
35
+
27
36
  if (__DEV__) {
28
37
  console.log("[RevenueCat] initializeSDK() called with userId:", userId);
29
38
  }
@@ -32,6 +41,10 @@ export async function initializeSDK(
32
41
  if (deps.isInitialized()) {
33
42
  const currentUserId = deps.getCurrentUserId();
34
43
  if (currentUserId === userId) {
44
+ addPackageBreadcrumb("subscription", "Already initialized, fetching current state", {
45
+ userId,
46
+ });
47
+
35
48
  if (__DEV__) {
36
49
  console.log("[RevenueCat] Already initialized with same userId, skipping configure");
37
50
  }
@@ -45,20 +58,38 @@ export async function initializeSDK(
45
58
  const hasPremium = !!customerInfo.entitlements.active[entitlementId];
46
59
  return { success: true, offering: offerings.current, hasPremium };
47
60
  } catch (error) {
61
+ trackPackageError(
62
+ error instanceof Error ? error : new Error(String(error)),
63
+ {
64
+ packageName: "subscription",
65
+ operation: "get_current_state",
66
+ userId,
67
+ }
68
+ );
69
+
48
70
  if (__DEV__) {
49
71
  console.log("[RevenueCat] Failed to get current state:", error);
50
72
  }
51
73
  return { success: false, offering: null, hasPremium: false };
52
74
  }
53
75
  } else {
76
+ addPackageBreadcrumb("subscription", "User changed, logging out previous user", {
77
+ previousUserId: currentUserId,
78
+ newUserId: userId,
79
+ });
80
+
54
81
  if (__DEV__) {
55
82
  console.log("[RevenueCat] Different userId, will re-configure");
56
83
  }
57
84
  // Different userId - need to logout first
58
85
  try {
59
86
  await Purchases.logOut();
60
- } catch {
61
- // Ignore logout errors
87
+ } catch (error) {
88
+ trackPackageWarning("subscription", "Logout failed during user change", {
89
+ error: error instanceof Error ? error.message : String(error),
90
+ previousUserId: currentUserId,
91
+ newUserId: userId,
92
+ });
62
93
  }
63
94
  }
64
95
  }
@@ -67,6 +98,13 @@ export async function initializeSDK(
67
98
 
68
99
  const key = apiKey || resolveApiKey(deps.config);
69
100
  if (!key) {
101
+ const error = new Error("No RevenueCat API key available");
102
+ trackPackageError(error, {
103
+ packageName: "subscription",
104
+ operation: "sdk_init_no_key",
105
+ userId,
106
+ });
107
+
70
108
  if (__DEV__) {
71
109
  console.log("[RevenueCat] No API key available");
72
110
  }
@@ -75,11 +113,17 @@ export async function initializeSDK(
75
113
 
76
114
  try {
77
115
  if (deps.isUsingTestStore()) {
116
+ addPackageBreadcrumb("subscription", "Using test store configuration", {
117
+ userId,
118
+ });
119
+
78
120
  if (__DEV__) {
79
121
  console.log("[RevenueCat] Using Test Store key");
80
122
  }
81
123
  }
82
124
 
125
+ addPackageBreadcrumb("subscription", "Configuring SDK", { userId });
126
+
83
127
  if (__DEV__) {
84
128
  console.log("[RevenueCat] Calling Purchases.configure()...");
85
129
  }
@@ -88,6 +132,10 @@ export async function initializeSDK(
88
132
  deps.setInitialized(true);
89
133
  deps.setCurrentUserId(userId);
90
134
 
135
+ addPackageBreadcrumb("subscription", "SDK configured successfully", {
136
+ userId,
137
+ });
138
+
91
139
  if (__DEV__) {
92
140
  console.log("[RevenueCat] SDK configured successfully");
93
141
  }
@@ -97,10 +145,19 @@ export async function initializeSDK(
97
145
  Purchases.getOfferings(),
98
146
  ]);
99
147
 
148
+ const packagesCount = offerings.current?.availablePackages?.length ?? 0;
149
+
150
+ addPackageBreadcrumb("subscription", "Offerings fetched", {
151
+ userId,
152
+ hasCurrent: !!offerings.current,
153
+ packagesCount,
154
+ allOfferingsCount: Object.keys(offerings.all).length,
155
+ });
156
+
100
157
  if (__DEV__) {
101
158
  console.log("[RevenueCat] Fetched offerings:", {
102
159
  hasCurrent: !!offerings.current,
103
- packagesCount: offerings.current?.availablePackages?.length ?? 0,
160
+ packagesCount,
104
161
  allOfferingsCount: Object.keys(offerings.all).length,
105
162
  });
106
163
  }
@@ -111,6 +168,17 @@ export async function initializeSDK(
111
168
  return { success: true, offering: offerings.current, hasPremium };
112
169
  } catch (error) {
113
170
  const errorMessage = getErrorMessage(error, "RevenueCat init failed");
171
+
172
+ trackPackageError(
173
+ error instanceof Error ? error : new Error(errorMessage),
174
+ {
175
+ packageName: "subscription",
176
+ operation: "sdk_init",
177
+ userId,
178
+ errorMessage,
179
+ }
180
+ );
181
+
114
182
  if (__DEV__) {
115
183
  console.log("[RevenueCat] Init failed:", errorMessage);
116
184
  }
@@ -7,6 +7,10 @@ import type { CustomerInfo } from "react-native-purchases";
7
7
  import type { RevenueCatConfig } from "../../domain/value-objects/RevenueCatConfig";
8
8
  import { getPremiumEntitlement } from "../../domain/types/RevenueCatTypes";
9
9
  import { getExpirationDate } from "./ExpirationDateCalculator";
10
+ import {
11
+ trackPackageError,
12
+ addPackageBreadcrumb,
13
+ } from "@umituz/react-native-sentry";
10
14
 
11
15
  export async function syncPremiumStatus(
12
16
  config: RevenueCatConfig,
@@ -23,6 +27,14 @@ export async function syncPremiumStatus(
23
27
  entitlementIdentifier
24
28
  );
25
29
 
30
+ const isPremium = !!premiumEntitlement;
31
+
32
+ addPackageBreadcrumb("subscription", "Syncing premium status", {
33
+ userId,
34
+ isPremium,
35
+ productId: premiumEntitlement?.productIdentifier,
36
+ });
37
+
26
38
  try {
27
39
  if (premiumEntitlement) {
28
40
  const productId = premiumEntitlement.productIdentifier;
@@ -36,7 +48,22 @@ export async function syncPremiumStatus(
36
48
  } else {
37
49
  await config.onPremiumStatusChanged(userId, false);
38
50
  }
51
+
52
+ addPackageBreadcrumb("subscription", "Premium status synced successfully", {
53
+ userId,
54
+ isPremium,
55
+ });
39
56
  } catch (error) {
57
+ trackPackageError(
58
+ error instanceof Error ? error : new Error(String(error)),
59
+ {
60
+ packageName: "subscription",
61
+ operation: "sync_premium_status",
62
+ userId,
63
+ isPremium,
64
+ }
65
+ );
66
+
40
67
  if (__DEV__) {
41
68
  const message =
42
69
  error instanceof Error ? error.message : "Premium sync failed";
@@ -55,9 +82,29 @@ export async function notifyPurchaseCompleted(
55
82
  return;
56
83
  }
57
84
 
85
+ addPackageBreadcrumb("subscription", "Notifying purchase completed", {
86
+ userId,
87
+ productId,
88
+ });
89
+
58
90
  try {
59
91
  await config.onPurchaseCompleted(userId, productId, customerInfo);
92
+
93
+ addPackageBreadcrumb("subscription", "Purchase callback completed", {
94
+ userId,
95
+ productId,
96
+ });
60
97
  } catch (error) {
98
+ trackPackageError(
99
+ error instanceof Error ? error : new Error(String(error)),
100
+ {
101
+ packageName: "subscription",
102
+ operation: "purchase_callback",
103
+ userId,
104
+ productId,
105
+ }
106
+ );
107
+
61
108
  if (__DEV__) {
62
109
  const message =
63
110
  error instanceof Error ? error.message : "Purchase callback failed";
@@ -76,9 +123,29 @@ export async function notifyRestoreCompleted(
76
123
  return;
77
124
  }
78
125
 
126
+ addPackageBreadcrumb("subscription", "Notifying restore completed", {
127
+ userId,
128
+ isPremium,
129
+ });
130
+
79
131
  try {
80
132
  await config.onRestoreCompleted(userId, isPremium, customerInfo);
133
+
134
+ addPackageBreadcrumb("subscription", "Restore callback completed", {
135
+ userId,
136
+ isPremium,
137
+ });
81
138
  } catch (error) {
139
+ trackPackageError(
140
+ error instanceof Error ? error : new Error(String(error)),
141
+ {
142
+ packageName: "subscription",
143
+ operation: "restore_callback",
144
+ userId,
145
+ isPremium,
146
+ }
147
+ );
148
+
82
149
  if (__DEV__) {
83
150
  const message =
84
151
  error instanceof Error ? error.message : "Restore callback failed";
@@ -10,6 +10,7 @@ import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionM
10
10
  import {
11
11
  trackPackageError,
12
12
  addPackageBreadcrumb,
13
+ trackPackageEvent,
13
14
  } from "@umituz/react-native-sentry";
14
15
 
15
16
  /**
@@ -111,6 +112,11 @@ export const usePurchasePackage = (userId: string | undefined) => {
111
112
  throw new Error("User not authenticated");
112
113
  }
113
114
 
115
+ trackPackageEvent("subscription", "purchase_started", {
116
+ packageId: pkg.identifier,
117
+ userId,
118
+ });
119
+
114
120
  addPackageBreadcrumb("subscription", "Purchase mutation started", {
115
121
  packageId: pkg.identifier,
116
122
  userId,
@@ -119,11 +125,21 @@ export const usePurchasePackage = (userId: string | undefined) => {
119
125
  const success = await SubscriptionManager.purchasePackage(pkg);
120
126
 
121
127
  if (success) {
128
+ trackPackageEvent("subscription", "purchase_success", {
129
+ packageId: pkg.identifier,
130
+ userId,
131
+ });
132
+
122
133
  addPackageBreadcrumb("subscription", "Purchase mutation success", {
123
134
  packageId: pkg.identifier,
124
135
  userId,
125
136
  });
126
137
  } else {
138
+ trackPackageEvent("subscription", "purchase_cancelled", {
139
+ packageId: pkg.identifier,
140
+ userId,
141
+ });
142
+
127
143
  addPackageBreadcrumb("subscription", "Purchase mutation failed", {
128
144
  packageId: pkg.identifier,
129
145
  userId,
@@ -162,6 +178,10 @@ export const useRestorePurchase = (userId: string | undefined) => {
162
178
  throw new Error("User not authenticated");
163
179
  }
164
180
 
181
+ trackPackageEvent("subscription", "restore_started", {
182
+ userId,
183
+ });
184
+
165
185
  addPackageBreadcrumb("subscription", "Restore mutation started", {
166
186
  userId,
167
187
  });
@@ -169,6 +189,10 @@ export const useRestorePurchase = (userId: string | undefined) => {
169
189
  const success = await SubscriptionManager.restore();
170
190
 
171
191
  if (success) {
192
+ trackPackageEvent("subscription", "restore_success", {
193
+ userId,
194
+ });
195
+
172
196
  addPackageBreadcrumb("subscription", "Restore mutation success", {
173
197
  userId,
174
198
  });