@umituz/react-native-subscription 2.37.42 → 2.37.44

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.37.42",
3
+ "version": "2.37.44",
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",
@@ -11,7 +11,7 @@ import {
11
11
  import { PURCHASE_ID_PREFIXES, PROCESSED_PURCHASES_WINDOW } from "../core/CreditsConstants";
12
12
 
13
13
 
14
- export function calculateNewCredits({ metadata, existingData, creditLimit, purchaseId }: CalculateCreditsParams): number {
14
+ export function calculateNewCredits({ metadata, existingData, creditLimit }: CalculateCreditsParams): number {
15
15
  const isExpired = metadata.expirationDate ? isPast(metadata.expirationDate) : false;
16
16
  const isPremium = metadata.isPremium;
17
17
  const status = resolveSubscriptionStatus({
@@ -19,23 +19,26 @@ interface InitializeCreditsParams {
19
19
  type?: PurchaseType;
20
20
  }
21
21
 
22
- function isTransientError(error: any): boolean {
22
+ function isTransientError(error: unknown): boolean {
23
+ const code = error instanceof Error ? (error as Error & { code?: string }).code : undefined;
24
+ const message = error instanceof Error ? error.message : '';
25
+
23
26
  return (
24
- error?.code === 'already-exists' ||
25
- error?.code === 'DEADLINE_EXCEEDED' ||
26
- error?.code === 'UNAVAILABLE' ||
27
- error?.code === 'RESOURCE_EXHAUSTED' ||
27
+ code === 'already-exists' ||
28
+ code === 'DEADLINE_EXCEEDED' ||
29
+ code === 'UNAVAILABLE' ||
30
+ code === 'RESOURCE_EXHAUSTED' ||
28
31
  // Firestore transaction contention: document was modified between our read and write.
29
32
  // runTransaction does not auto-retry on failed-precondition, so we do it here.
30
- error?.code === 'failed-precondition' ||
31
- error?.code === 'FAILED_PRECONDITION' ||
33
+ code === 'failed-precondition' ||
34
+ code === 'FAILED_PRECONDITION' ||
32
35
  // Firestore transaction aborted due to concurrent modification.
33
- error?.code === 'aborted' ||
34
- error?.code === 'ABORTED' ||
35
- error?.message?.includes('already-exists') ||
36
- error?.message?.includes('timeout') ||
37
- error?.message?.includes('unavailable') ||
38
- error?.message?.includes('failed-precondition')
36
+ code === 'aborted' ||
37
+ code === 'ABORTED' ||
38
+ message.includes('already-exists') ||
39
+ message.includes('timeout') ||
40
+ message.includes('unavailable') ||
41
+ message.includes('failed-precondition')
39
42
  );
40
43
  }
41
44
 
@@ -46,7 +49,7 @@ export async function initializeCreditsWithRetry(params: InitializeCreditsParams
46
49
  const cfg = { ...config, creditLimit };
47
50
 
48
51
  const maxRetries = 3;
49
- let lastError: any;
52
+ let lastError: unknown;
50
53
 
51
54
  for (let attempt = 0; attempt < maxRetries; attempt++) {
52
55
  try {
@@ -76,7 +79,7 @@ export async function initializeCreditsWithRetry(params: InitializeCreditsParams
76
79
  data: result.finalData ? mapCreditsDocumentToEntity(result.finalData) : null,
77
80
  error: null,
78
81
  };
79
- } catch (error: any) {
82
+ } catch (error: unknown) {
80
83
  lastError = error;
81
84
 
82
85
  if (isTransientError(error) && attempt < maxRetries - 1) {
@@ -98,7 +101,9 @@ export async function initializeCreditsWithRetry(params: InitializeCreditsParams
98
101
  ? lastError
99
102
  : 'Unknown error during credit initialization';
100
103
 
101
- const errorCode = lastError?.code ?? 'UNKNOWN_ERROR';
104
+ const errorCode = lastError instanceof Error
105
+ ? (lastError as Error & { code?: string }).code ?? 'UNKNOWN_ERROR'
106
+ : 'UNKNOWN_ERROR';
102
107
 
103
108
  return {
104
109
  success: false,
@@ -18,7 +18,8 @@ class UserSwitchMutexImpl {
18
18
  }
19
19
  try {
20
20
  await this.activeSwitchPromise;
21
- } catch {
21
+ } catch (_ignored) {
22
+ // Intentional: waiting for active switch to complete without failing
22
23
  }
23
24
 
24
25
  const timeSinceLastSwitch = Date.now() - this.lastSwitchTime;
@@ -1,4 +1,4 @@
1
- import Purchases, { type CustomerInfo } from "react-native-purchases";
1
+ import Purchases, { type CustomerInfo, type PurchasesOfferings } from "react-native-purchases";
2
2
  import type { InitializeResult } from "../../../../shared/application/ports/IRevenueCatService";
3
3
  import type { InitializerDeps } from "./RevenueCatInitializer.types";
4
4
  import { FAILED_INITIALIZATION_RESULT } from "./initializerConstants";
@@ -7,7 +7,7 @@ import { getPremiumEntitlement } from "../../core/types";
7
7
 
8
8
  declare const __DEV__: boolean;
9
9
 
10
- function buildSuccessResult(deps: InitializerDeps, customerInfo: CustomerInfo, offerings: any): InitializeResult {
10
+ function buildSuccessResult(deps: InitializerDeps, customerInfo: CustomerInfo, offerings: PurchasesOfferings | null): InitializeResult {
11
11
  const isPremium = !!customerInfo.entitlements.active[deps.config.entitlementIdentifier];
12
12
  return { success: true, offering: offerings?.current ?? null, isPremium };
13
13
  }
@@ -17,14 +17,14 @@ function buildSuccessResult(deps: InitializerDeps, customerInfo: CustomerInfo, o
17
17
  * Empty offerings (no products configured in RevenueCat dashboard) should NOT
18
18
  * block SDK initialization. The SDK is still usable for premium checks, purchases, etc.
19
19
  */
20
- async function fetchOfferingsSafe(): Promise<any> {
20
+ async function fetchOfferingsSafe(): Promise<PurchasesOfferings | null> {
21
21
  try {
22
22
  return await Purchases.getOfferings();
23
23
  } catch (error) {
24
24
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
25
25
  console.warn('[UserSwitchHandler] Offerings fetch failed (non-fatal):', error);
26
26
  }
27
- return { current: null, all: {} };
27
+ return null;
28
28
  }
29
29
  }
30
30
 
@@ -165,7 +165,7 @@ export async function handleInitialConfiguration(
165
165
  console.log('[UserSwitchHandler] ✅ Initial configuration completed:', {
166
166
  revenueCatUserId: currentUserId,
167
167
  activeEntitlements: Object.keys(customerInfo.entitlements.active),
168
- offeringsCount: offerings.all ? Object.keys(offerings.all).length : 0,
168
+ offeringsCount: offerings?.all ? Object.keys(offerings.all).length : 0,
169
169
  });
170
170
  }
171
171
 
@@ -17,8 +17,7 @@ export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId:
17
17
  throw new Error('[extractRevenueCatData] entitlementId is required');
18
18
  }
19
19
 
20
- const entitlement = customerInfo.entitlements.active[entitlementId]
21
- ?? customerInfo.entitlements.all[entitlementId];
20
+ const entitlement = customerInfo.entitlements.active[entitlementId];
22
21
 
23
22
  const isPremium = !!customerInfo.entitlements.active[entitlementId];
24
23
 
@@ -24,18 +24,20 @@ export async function handleAlreadyPurchasedError(
24
24
  ): Promise<PurchaseResult> {
25
25
  try {
26
26
  const restoreResult = await handleRestore(deps, userId);
27
- if (restoreResult.success && restoreResult.isPremium && restoreResult.customerInfo) {
28
- await notifyPurchaseCompleted(
29
- deps.config,
30
- userId,
31
- pkg.product.identifier,
32
- restoreResult.customerInfo,
33
- getSavedPurchase()?.source
34
- );
27
+ if (restoreResult.success && restoreResult.customerInfo) {
28
+ if (restoreResult.isPremium) {
29
+ await notifyPurchaseCompleted(
30
+ deps.config,
31
+ userId,
32
+ pkg.product.identifier,
33
+ restoreResult.customerInfo,
34
+ getSavedPurchase()?.source
35
+ );
36
+ }
35
37
  clearSavedPurchase();
36
38
  return {
37
39
  success: true,
38
- isPremium: true,
40
+ isPremium: restoreResult.isPremium,
39
41
  customerInfo: restoreResult.customerInfo,
40
42
  productId: restoreResult.productId || pkg.product.identifier,
41
43
  };
@@ -17,7 +17,12 @@ async function executeConsumablePurchase(
17
17
  clearSavedPurchase();
18
18
  }
19
19
 
20
- await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
20
+ try {
21
+ await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
22
+ } catch (syncError) {
23
+ // Non-fatal: RevenueCat purchase succeeded, credits sync can be recovered on next session
24
+ console.error('[PurchaseExecutor] Post-purchase sync failed (purchase was successful):', syncError);
25
+ }
21
26
 
22
27
  return {
23
28
  success: true,
@@ -55,7 +60,12 @@ async function executeSubscriptionPurchase(
55
60
  });
56
61
  }
57
62
 
58
- await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
63
+ try {
64
+ await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
65
+ } catch (syncError) {
66
+ // Non-fatal: RevenueCat purchase succeeded, credits sync can be recovered on next session
67
+ console.error('[PurchaseExecutor] Post-purchase sync failed (purchase was successful):', syncError);
68
+ }
59
69
 
60
70
  return {
61
71
  success: true,
@@ -44,7 +44,7 @@ export function createFallbackEligibilityMap(
44
44
  for (const productId of productIds) {
45
45
  result[productId] = {
46
46
  productId,
47
- eligible: true,
47
+ eligible: false,
48
48
  trialDurationDays: DEFAULT_TRIAL_DURATION_DAYS,
49
49
  };
50
50
  }
@@ -22,23 +22,28 @@ export class DeviceTrialRepository {
22
22
  if (!this.db) return false;
23
23
  const ref = doc(this.db, DEVICE_TRIALS_COLLECTION, deviceId);
24
24
 
25
- // Atomic check-then-act: ensure createdAt is set only once
26
- await runTransaction(async (tx: Transaction) => {
27
- const snap = await tx.get(ref);
28
- const existingData = snap.data();
29
-
30
- const updateData: Record<string, unknown> = {
31
- ...data,
32
- updatedAt: serverTimestamp(),
33
- };
34
-
35
- if (!existingData?.createdAt) {
36
- updateData.createdAt = serverTimestamp();
37
- }
38
-
39
- tx.set(ref, updateData, { merge: true });
40
- });
41
-
42
- return true;
25
+ try {
26
+ // Atomic check-then-act: ensure createdAt is set only once
27
+ await runTransaction(async (tx: Transaction) => {
28
+ const snap = await tx.get(ref);
29
+ const existingData = snap.data();
30
+
31
+ const updateData: Record<string, unknown> = {
32
+ ...data,
33
+ updatedAt: serverTimestamp(),
34
+ };
35
+
36
+ if (!existingData?.createdAt) {
37
+ updateData.createdAt = serverTimestamp();
38
+ }
39
+
40
+ tx.set(ref, updateData, { merge: true });
41
+ });
42
+
43
+ return true;
44
+ } catch (error) {
45
+ console.error('[DeviceTrialRepository] Failed to save record:', error instanceof Error ? error.message : String(error));
46
+ return false;
47
+ }
43
48
  }
44
49
  }