Skip to content

Commit 1cbdd9b

Browse files
Patchalvclaude
andauthored
Stop reporting transient RevenueCat network errors to Sentry (#87)
RC login (Purchases.logIn) and offerings (Purchases.getOfferings) both report to Sentry unconditionally on failure, including transient device-side network/DNS blips that aren't actionable app bugs. Filter those out by RC's own error code instead of message-string matching, and preserve visibility via PostHog instead of paging Sentry. Fixes MAPVAULT-G Fixes MAPVAULT-H Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 7b354e9 commit 1cbdd9b

4 files changed

Lines changed: 27 additions & 6 deletions

File tree

docs/analytics.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,18 @@ Entitlement is synced as a person property (`entitlement: 'free' | 'premium'`) i
128128
|---|---|---|---|
129129
| `review_prompted` | In-app review dialog triggered | `trigger`: `'place_visited' \| 'places_saved_milestone' \| 'directions_after_filter'` | `hooks/use-app-review.ts` |
130130

131-
### Payments (4 events)
131+
### Payments (6 events)
132132

133133
| Event | When | Properties | File |
134134
|---|---|---|---|
135135
| `paywall_viewed` | Paywall screen mounts | `trigger`: `'map_limit' \| 'place_limit' \| 'invite_limit' \| 'profile_tap' \| 'profile_cta'` | `app/(tabs)/profile/paywall.tsx` |
136136
| `purchase_started` | User taps Subscribe | _(none)_ | `app/(tabs)/profile/paywall.tsx` |
137137
| `purchase_completed` | Purchase succeeds | _(none)_ | `app/(tabs)/profile/paywall.tsx` |
138138
| `purchase_failed` | Purchase fails or is cancelled | `reason`: `'cancelled' \| 'error'` | `app/(tabs)/profile/paywall.tsx` |
139+
| `paywall_offerings_load_failed` | RC offerings query fails or returns empty | `reason`: `'error' \| 'empty' \| 'not_configured' \| 'network'` | `hooks/use-revenuecat.ts` |
140+
| `revenuecat_login_network_error` | RC `logIn()` fails with a network/connectivity error | _(none)_ | `hooks/use-revenuecat.ts` |
139141

140-
**Total: 30 events**
142+
**Total: 32 events**
141143

142144
## Rules for Adding New Events
143145

hooks/use-revenuecat.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
purchasePackage,
1515
restorePurchases,
1616
isPremium,
17+
isRevenueCatNetworkError,
1718
} from '@/lib/revenuecat';
1819
import type { Profile } from '@/types';
1920

@@ -69,7 +70,14 @@ export function useRevenueCat() {
6970
.catch((error: unknown) => {
7071
const message = error instanceof Error ? error.message : String(error);
7172
if (message.toLowerCase().includes('already in progress')) return; // known Android race
72-
Sentry.captureException(error, { tags: { context: 'revenuecat_login' } });
73+
if (isRevenueCatNetworkError(error)) {
74+
track('revenuecat_login_network_error', {}); // transient device connectivity — not actionable
75+
return;
76+
}
77+
Sentry.captureException(error, {
78+
tags: { context: 'revenuecat_login' },
79+
extra: { userId },
80+
});
7381
})
7482
.finally(() => {
7583
isIdentifyingRef.current = false;
@@ -118,13 +126,14 @@ export function useRevenueCat() {
118126
const key = `error:${offerings.errorUpdatedAt}`;
119127
if (reportedKeyRef.current === key) return;
120128
reportedKeyRef.current = key;
121-
if (IS_PRODUCTION) {
129+
const isNetworkError = isRevenueCatNetworkError(offerings.error);
130+
if (IS_PRODUCTION && !isNetworkError) {
122131
Sentry.captureException(offerings.error, {
123132
tags: { context: 'rc_offerings', platform: Platform.OS },
124133
extra: { userId, revenueCatReady },
125134
});
126135
}
127-
track('paywall_offerings_load_failed', { reason: 'error' });
136+
track('paywall_offerings_load_failed', { reason: isNetworkError ? 'network' : 'error' });
128137
return;
129138
}
130139
const data = offerings.data;

lib/analytics.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ type AnalyticsEvents = {
5151
purchase_started: Record<string, never>;
5252
purchase_completed: Record<string, never>;
5353
purchase_failed: { reason: 'cancelled' | 'error' };
54-
paywall_offerings_load_failed: { reason: 'error' | 'empty' | 'not_configured' };
54+
paywall_offerings_load_failed: { reason: 'error' | 'empty' | 'not_configured' | 'network' };
55+
revenuecat_login_network_error: Record<string, never>;
5556
review_prompted: { trigger: 'place_visited' | 'places_saved_milestone' | 'directions_after_filter' };
5657
settings_viewed: Record<string, never>;
5758
manage_maps_viewed: Record<string, never>;

lib/revenuecat.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Platform } from 'react-native';
22
import Purchases, {
33
LOG_LEVEL,
4+
PURCHASES_ERROR_CODE,
45
type CustomerInfo,
56
type PurchasesOfferings,
67
type PurchasesPackage,
@@ -90,3 +91,11 @@ export async function getCustomerInfo(): Promise<CustomerInfo | null> {
9091
export function isPremium(customerInfo: CustomerInfo): boolean {
9192
return !!customerInfo.entitlements.active['premium'];
9293
}
94+
95+
export function isRevenueCatNetworkError(error: unknown): boolean {
96+
const code = (error as { code?: string })?.code;
97+
return (
98+
code === PURCHASES_ERROR_CODE.NETWORK_ERROR ||
99+
code === PURCHASES_ERROR_CODE.OFFLINE_CONNECTION_ERROR
100+
);
101+
}

0 commit comments

Comments
 (0)