From fef95a9eeb6b3bc21bdc6abd2351826729bd367b Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:55:34 -0400 Subject: [PATCH 1/4] Add Sentry for error tracking and performance monitoring Adds @sentry/react-native with release health, navigation tracing, and slow/frozen frame tracking to catch non-crash regressions (JS errors, perf degradation) that Crashlytics misses. - Sentry.init in src/sentry.ts, imported first in App.tsx and wrapped with Sentry.wrap for touch breadcrumbs and profiling - React Navigation integration registered on the NavigationContainer for screen-load transactions and time-to-initial-display - Metro config switched to getSentryExpoConfig for debug ID injection and symbolicated stack traces - Expo config plugin for source map upload during EAS builds - SDK disabled unless EXPO_PUBLIC_SENTRY_DSN is set (and never in dev) Co-Authored-By: Claude Fable 5 --- App.tsx | 11 +- app.config.js | 9 + jest.config.ts | 2 +- metro.config.js | 6 +- package-lock.json | 467 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + src/Navigation.tsx | 6 +- src/sentry.ts | 25 +++ 8 files changed, 514 insertions(+), 13 deletions(-) create mode 100644 src/sentry.ts diff --git a/App.tsx b/App.tsx index cd62d5da..e66b9d2c 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,10 @@ import React from 'react'; +// Imported first for its side effect: Sentry.init must run before other modules +// (store creation, firebase) so their errors are captured. +// eslint-disable-next-line import/order +import Sentry from './src/sentry'; + import analytics from '@react-native-firebase/analytics'; import { StatusBar } from 'expo-status-bar'; import { View } from 'react-native'; @@ -24,7 +29,7 @@ if (process.env.EXPO_PUBLIC_FIREBASE_ANALYTICS == 'false') { analytics().setAnalyticsCollectionEnabled(false); } -export default function App() { +function App() { return ( @@ -47,4 +52,6 @@ export default function App() { ); -}; +} + +export default Sentry.wrap(App); diff --git a/app.config.js b/app.config.js index 46aa2df1..e85ff87c 100644 --- a/app.config.js +++ b/app.config.js @@ -142,5 +142,14 @@ export default { skipOnboarding: true, }, ], + [ + '@sentry/react-native/expo', + { + // Source map upload during EAS builds; skipped with a warning unless + // SENTRY_AUTH_TOKEN is set in the build environment. + organization: process.env.SENTRY_ORG || 'wyne', + project: process.env.SENTRY_PROJECT || 'scorepad-react-native', + }, + ], ], }; diff --git a/jest.config.ts b/jest.config.ts index 560b148a..fe480a2e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -6,7 +6,7 @@ const config: Config = { '^.+\\.tsx?$': 'babel-jest', }, transformIgnorePatterns: [ - 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|immer|react-redux|@reduxjs/toolkit)' + 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|@sentry/.*|native-base|react-native-svg|immer|react-redux|@reduxjs/toolkit)' ], testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', testPathIgnorePatterns: ['/e2e/', '/.claude/'], diff --git a/metro.config.js b/metro.config.js index e0656ade..7ab08b4d 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/no-require-imports */ -const { getDefaultConfig } = require('@expo/metro-config'); +const { getSentryExpoConfig } = require('@sentry/react-native/metro'); -const defaultConfig = getDefaultConfig(__dirname); +// Sentry's wrapper of Expo's default config: adds debug ID injection and +// source map options so stack traces symbolicate in Sentry. +const defaultConfig = getSentryExpoConfig(__dirname); // NOTE: do NOT add 'cjs' to assetExts — it prevents Metro from executing // .cjs files as JavaScript (e.g. superstruct/dist/index.cjs gets treated as // a binary asset instead of a module). diff --git a/package-lock.json b/package-lock.json index 20a04638..2b875e7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@react-navigation/native": "^7.2.4", "@react-navigation/native-stack": "^7.15.1", "@reduxjs/toolkit": "^2.12.0", + "@sentry/react-native": "~7.11.0", "colorsheet": "^1.0.5", "expo": "^56.0.0", "expo-application": "~56.0.3", @@ -5888,6 +5889,310 @@ "dev": true, "license": "MIT" }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.37.0.tgz", + "integrity": "sha512-rqdESYaVio9Ktz55lhUhtBsBUCF3wvvJuWia5YqoHDd+egyIfwWxITTAa0TSEyZl7283A4WNHNl0hyeEMblmfA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.37.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.37.0.tgz", + "integrity": "sha512-P0PVlfrDvfvCYg2KPIS7YUG/4i6ZPf8z1MicXx09C9Cz9W9UhSBh/nii13eBdDtLav2BFMKhvaFMcghXHX03Hw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.37.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.37.0.tgz", + "integrity": "sha512-snuk12ZaDerxesSnetNIwKoth/51R0y/h3eXD/bGtXp+hnSkeXN5HanI/RJl297llRjn4zJYRShW9Nx86Ay0Dw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.37.0", + "@sentry/core": "10.37.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.37.0.tgz", + "integrity": "sha512-PyIYSbjLs+L5essYV0MyIsh4n5xfv2eV7l0nhUoPJv9Bak3kattQY3tholOj0EP3SgKgb+8HSZnmazgF++Hbog==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.37.0", + "@sentry/core": "10.37.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/babel-plugin-component-annotate": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.8.0.tgz", + "integrity": "sha512-cy/9Eipkv23MsEJ4IuB4dNlVwS9UqOzI3Eu+QPake5BVFgPYCX0uP0Tr3Z43Ime6Rb+BiDnWC51AJK9i9afHYw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@sentry/browser": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.37.0.tgz", + "integrity": "sha512-kheqJNqGZP5TSBCPv4Vienv1sfZwXKHQDYR+xrdHHYdZqwWuZMJJW/cLO9XjYAe+B9NnJ4UwJOoY4fPvU+HQ1Q==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.37.0", + "@sentry-internal/feedback": "10.37.0", + "@sentry-internal/replay": "10.37.0", + "@sentry-internal/replay-canvas": "10.37.0", + "@sentry/core": "10.37.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/cli": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.4.tgz", + "integrity": "sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg==", + "hasInstallScript": true, + "license": "FSL-1.1-MIT", + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.58.4", + "@sentry/cli-linux-arm": "2.58.4", + "@sentry/cli-linux-arm64": "2.58.4", + "@sentry/cli-linux-i686": "2.58.4", + "@sentry/cli-linux-x64": "2.58.4", + "@sentry/cli-win32-arm64": "2.58.4", + "@sentry/cli-win32-i686": "2.58.4", + "@sentry/cli-win32-x64": "2.58.4" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.4.tgz", + "integrity": "sha512-kbTD+P4X8O+nsNwPxCywtj3q22ecyRHWff98rdcmtRrvwz8CKi/T4Jxn/fnn2i4VEchy08OWBuZAqaA5Kh2hRQ==", + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.4.tgz", + "integrity": "sha512-rdQ8beTwnN48hv7iV7e7ZKucPec5NJkRdrrycMJMZlzGBPi56LqnclgsHySJ6Kfq506A2MNuQnKGaf/sBC9REA==", + "cpu": [ + "arm" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.4.tgz", + "integrity": "sha512-0g0KwsOozkLtzN8/0+oMZoOuQ0o7W6O+hx+ydVU1bktaMGKEJLMAWxOQNjsh1TcBbNIXVOKM/I8l0ROhaAb8Ig==", + "cpu": [ + "arm64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.4.tgz", + "integrity": "sha512-NseoIQAFtkziHyjZNPTu1Gm1opeQHt7Wm1LbLrGWVIRvUOzlslO9/8i6wETUZ6TjlQxBVRgd3Q0lRBG2A8rFYA==", + "cpu": [ + "x86", + "ia32" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.4.tgz", + "integrity": "sha512-d3Arz+OO/wJYTqCYlSN3Ktm+W8rynQ/IMtSZLK8nu0ryh5mJOh+9XlXY6oDXw4YlsM8qCRrNquR8iEI1Y/IH+Q==", + "cpu": [ + "x64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-arm64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.4.tgz", + "integrity": "sha512-bqYrF43+jXdDBh0f8HIJU3tbvlOFtGyRjHB8AoRuMQv9TEDUfENZyCelhdjA+KwDKYl48R1Yasb4EHNzsoO83w==", + "cpu": [ + "arm64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.4.tgz", + "integrity": "sha512-3triFD6jyvhVcXOmGyttf+deKZcC1tURdhnmDUIBkiDPJKGT/N5xa4qAtHJlAB/h8L9jgYih9bvJnvvFVM7yug==", + "cpu": [ + "x86", + "ia32" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.4.tgz", + "integrity": "sha512-cSzN4PjM1RsCZ4pxMjI0VI7yNCkxiJ5jmWncyiwHXGiXrV1eXYdQ3n1LhUYLZ91CafyprR0OhDcE+RVZ26Qb5w==", + "cpu": [ + "x64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/core": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.37.0.tgz", + "integrity": "sha512-hkRz7S4gkKLgPf+p3XgVjVm7tAfvcEPZxeACCC6jmoeKhGkzN44nXwLiqqshJ25RMcSrhfFvJa/FlBg6zupz7g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/react": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.37.0.tgz", + "integrity": "sha512-XLnXJOHgsCeVAVBbO+9AuGlZWnCxLQHLOmKxpIr8wjE3g7dHibtug6cv8JLx78O4dd7aoCqv2TTyyKY9FLJ2EQ==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.37.0", + "@sentry/core": "10.37.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@sentry/react-native": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-7.11.0.tgz", + "integrity": "sha512-OiDaLCAGpRN18YG/o7IIwLhU0Xpb0tYKQ5QxkGHiwb+L3VHn+MqGCGfITYNdhqr06HHMvu9Lysm+UJxaNmGaJg==", + "license": "MIT", + "dependencies": { + "@sentry/babel-plugin-component-annotate": "4.8.0", + "@sentry/browser": "10.37.0", + "@sentry/cli": "2.58.4", + "@sentry/core": "10.37.0", + "@sentry/react": "10.37.0", + "@sentry/types": "10.37.0" + }, + "bin": { + "sentry-expo-upload-sourcemaps": "scripts/expo-upload-sourcemaps.js" + }, + "peerDependencies": { + "expo": ">=49.0.0", + "react": ">=17.0.0", + "react-native": ">=0.65.0" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@sentry/types": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-10.37.0.tgz", + "integrity": "sha512-umpnUKRC0AAbJrADg6SlFtqN2yzf7NHciCF9lkHau+ax2PIZ/NDmoG4RQujFVflVaVoD60Ly2t+CcPnYIWMPlw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.37.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "license": "MIT" @@ -7234,7 +7539,6 @@ }, "node_modules/agent-base": { "version": "6.0.2", - "dev": true, "license": "MIT", "dependencies": { "debug": "4" @@ -12720,7 +13024,6 @@ }, "node_modules/https-proxy-agent": { "version": "5.0.1", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -17411,7 +17714,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, "license": "MIT" }, "node_modules/psl": { @@ -24918,6 +25220,160 @@ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true }, + "@sentry-internal/browser-utils": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.37.0.tgz", + "integrity": "sha512-rqdESYaVio9Ktz55lhUhtBsBUCF3wvvJuWia5YqoHDd+egyIfwWxITTAa0TSEyZl7283A4WNHNl0hyeEMblmfA==", + "requires": { + "@sentry/core": "10.37.0" + } + }, + "@sentry-internal/feedback": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.37.0.tgz", + "integrity": "sha512-P0PVlfrDvfvCYg2KPIS7YUG/4i6ZPf8z1MicXx09C9Cz9W9UhSBh/nii13eBdDtLav2BFMKhvaFMcghXHX03Hw==", + "requires": { + "@sentry/core": "10.37.0" + } + }, + "@sentry-internal/replay": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.37.0.tgz", + "integrity": "sha512-snuk12ZaDerxesSnetNIwKoth/51R0y/h3eXD/bGtXp+hnSkeXN5HanI/RJl297llRjn4zJYRShW9Nx86Ay0Dw==", + "requires": { + "@sentry-internal/browser-utils": "10.37.0", + "@sentry/core": "10.37.0" + } + }, + "@sentry-internal/replay-canvas": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.37.0.tgz", + "integrity": "sha512-PyIYSbjLs+L5essYV0MyIsh4n5xfv2eV7l0nhUoPJv9Bak3kattQY3tholOj0EP3SgKgb+8HSZnmazgF++Hbog==", + "requires": { + "@sentry-internal/replay": "10.37.0", + "@sentry/core": "10.37.0" + } + }, + "@sentry/babel-plugin-component-annotate": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.8.0.tgz", + "integrity": "sha512-cy/9Eipkv23MsEJ4IuB4dNlVwS9UqOzI3Eu+QPake5BVFgPYCX0uP0Tr3Z43Ime6Rb+BiDnWC51AJK9i9afHYw==" + }, + "@sentry/browser": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.37.0.tgz", + "integrity": "sha512-kheqJNqGZP5TSBCPv4Vienv1sfZwXKHQDYR+xrdHHYdZqwWuZMJJW/cLO9XjYAe+B9NnJ4UwJOoY4fPvU+HQ1Q==", + "requires": { + "@sentry-internal/browser-utils": "10.37.0", + "@sentry-internal/feedback": "10.37.0", + "@sentry-internal/replay": "10.37.0", + "@sentry-internal/replay-canvas": "10.37.0", + "@sentry/core": "10.37.0" + } + }, + "@sentry/cli": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.4.tgz", + "integrity": "sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg==", + "requires": { + "@sentry/cli-darwin": "2.58.4", + "@sentry/cli-linux-arm": "2.58.4", + "@sentry/cli-linux-arm64": "2.58.4", + "@sentry/cli-linux-i686": "2.58.4", + "@sentry/cli-linux-x64": "2.58.4", + "@sentry/cli-win32-arm64": "2.58.4", + "@sentry/cli-win32-i686": "2.58.4", + "@sentry/cli-win32-x64": "2.58.4", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + } + }, + "@sentry/cli-darwin": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.4.tgz", + "integrity": "sha512-kbTD+P4X8O+nsNwPxCywtj3q22ecyRHWff98rdcmtRrvwz8CKi/T4Jxn/fnn2i4VEchy08OWBuZAqaA5Kh2hRQ==", + "optional": true + }, + "@sentry/cli-linux-arm": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.4.tgz", + "integrity": "sha512-rdQ8beTwnN48hv7iV7e7ZKucPec5NJkRdrrycMJMZlzGBPi56LqnclgsHySJ6Kfq506A2MNuQnKGaf/sBC9REA==", + "optional": true + }, + "@sentry/cli-linux-arm64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.4.tgz", + "integrity": "sha512-0g0KwsOozkLtzN8/0+oMZoOuQ0o7W6O+hx+ydVU1bktaMGKEJLMAWxOQNjsh1TcBbNIXVOKM/I8l0ROhaAb8Ig==", + "optional": true + }, + "@sentry/cli-linux-i686": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.4.tgz", + "integrity": "sha512-NseoIQAFtkziHyjZNPTu1Gm1opeQHt7Wm1LbLrGWVIRvUOzlslO9/8i6wETUZ6TjlQxBVRgd3Q0lRBG2A8rFYA==", + "optional": true + }, + "@sentry/cli-linux-x64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.4.tgz", + "integrity": "sha512-d3Arz+OO/wJYTqCYlSN3Ktm+W8rynQ/IMtSZLK8nu0ryh5mJOh+9XlXY6oDXw4YlsM8qCRrNquR8iEI1Y/IH+Q==", + "optional": true + }, + "@sentry/cli-win32-arm64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.4.tgz", + "integrity": "sha512-bqYrF43+jXdDBh0f8HIJU3tbvlOFtGyRjHB8AoRuMQv9TEDUfENZyCelhdjA+KwDKYl48R1Yasb4EHNzsoO83w==", + "optional": true + }, + "@sentry/cli-win32-i686": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.4.tgz", + "integrity": "sha512-3triFD6jyvhVcXOmGyttf+deKZcC1tURdhnmDUIBkiDPJKGT/N5xa4qAtHJlAB/h8L9jgYih9bvJnvvFVM7yug==", + "optional": true + }, + "@sentry/cli-win32-x64": { + "version": "2.58.4", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.4.tgz", + "integrity": "sha512-cSzN4PjM1RsCZ4pxMjI0VI7yNCkxiJ5jmWncyiwHXGiXrV1eXYdQ3n1LhUYLZ91CafyprR0OhDcE+RVZ26Qb5w==", + "optional": true + }, + "@sentry/core": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.37.0.tgz", + "integrity": "sha512-hkRz7S4gkKLgPf+p3XgVjVm7tAfvcEPZxeACCC6jmoeKhGkzN44nXwLiqqshJ25RMcSrhfFvJa/FlBg6zupz7g==" + }, + "@sentry/react": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.37.0.tgz", + "integrity": "sha512-XLnXJOHgsCeVAVBbO+9AuGlZWnCxLQHLOmKxpIr8wjE3g7dHibtug6cv8JLx78O4dd7aoCqv2TTyyKY9FLJ2EQ==", + "requires": { + "@sentry/browser": "10.37.0", + "@sentry/core": "10.37.0" + } + }, + "@sentry/react-native": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-7.11.0.tgz", + "integrity": "sha512-OiDaLCAGpRN18YG/o7IIwLhU0Xpb0tYKQ5QxkGHiwb+L3VHn+MqGCGfITYNdhqr06HHMvu9Lysm+UJxaNmGaJg==", + "requires": { + "@sentry/babel-plugin-component-annotate": "4.8.0", + "@sentry/browser": "10.37.0", + "@sentry/cli": "2.58.4", + "@sentry/core": "10.37.0", + "@sentry/react": "10.37.0", + "@sentry/types": "10.37.0" + } + }, + "@sentry/types": { + "version": "10.37.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-10.37.0.tgz", + "integrity": "sha512-umpnUKRC0AAbJrADg6SlFtqN2yzf7NHciCF9lkHau+ax2PIZ/NDmoG4RQujFVflVaVoD60Ly2t+CcPnYIWMPlw==", + "requires": { + "@sentry/core": "10.37.0" + } + }, "@sinclair/typebox": { "version": "0.27.8" }, @@ -25847,7 +26303,6 @@ }, "agent-base": { "version": "6.0.2", - "dev": true, "requires": { "debug": "4" } @@ -29338,7 +29793,6 @@ }, "https-proxy-agent": { "version": "5.0.1", - "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -32314,8 +32768,7 @@ "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "psl": { "version": "1.9.0", diff --git a/package.json b/package.json index 4c05db47..e259ad93 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@react-navigation/native": "^7.2.4", "@react-navigation/native-stack": "^7.15.1", "@reduxjs/toolkit": "^2.12.0", + "@sentry/react-native": "~7.11.0", "colorsheet": "^1.0.5", "expo": "^56.0.0", "expo-application": "~56.0.3", diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 4957c7d3..16dc8243 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native'; +import { DarkTheme, DefaultTheme, NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { Platform, View } from 'react-native'; @@ -19,6 +19,7 @@ import GameSheet from './components/Sheets/GameSheet'; import { GestureInfoSheetContextProvider } from './components/Sheets/GestureInfoSheetContext'; import EditPlayerScreen from './screens/EditPlayerScreen'; import ShareScreen from './screens/ShareScreen'; +import { navigationIntegration } from './sentry'; import { useTheme } from './theme'; export type RootStackParamList = { @@ -52,11 +53,14 @@ export const Navigation = () => { const fullscreen = useAppSelector(state => state.settings.home_fullscreen); const [showGameSheet, setShowGameSheet] = useState(false); + const navigationRef = useNavigationContainerRef(); return ( navigationIntegration.registerNavigationContainer(navigationRef)} > diff --git a/src/sentry.ts b/src/sentry.ts new file mode 100644 index 00000000..561f4fd9 --- /dev/null +++ b/src/sentry.ts @@ -0,0 +1,25 @@ +import * as Sentry from '@sentry/react-native'; + +// DSN is not a secret; it identifies the Sentry project for event ingestion. +// Set EXPO_PUBLIC_SENTRY_DSN in EAS environment variables (or replace the +// fallback below) once the Sentry project is created. With no DSN, the SDK +// stays fully disabled. +const SENTRY_DSN = process.env.EXPO_PUBLIC_SENTRY_DSN; + +export const navigationIntegration = Sentry.reactNavigationIntegration({ + enableTimeToInitialDisplay: true, +}); + +Sentry.init({ + dsn: SENTRY_DSN, + enabled: Boolean(SENTRY_DSN) && !__DEV__, + // Low event volume app: trace every session so slow/frozen frame and + // screen-load regressions are visible per release. + tracesSampleRate: 1.0, + integrations: [navigationIntegration], + enableNativeFramesTracking: true, + // Release health: track session adoption/crash-free rate per version. + enableAutoSessionTracking: true, +}); + +export default Sentry; From 3ae21bc9d150d7b523b156fb93723f6c9d43e3f0 Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Fri, 12 Jun 2026 21:08:51 -0400 Subject: [PATCH 2/4] Enable Sentry in dev builds and tag events by environment DSN presence is now the sole on/off switch so local dev clients can test reporting via a gitignored .env. Events are tagged with development/preview/production derived from the bundle id suffix. Co-Authored-By: Claude Fable 5 --- src/sentry.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/sentry.ts b/src/sentry.ts index 561f4fd9..43a6c3ee 100644 --- a/src/sentry.ts +++ b/src/sentry.ts @@ -1,18 +1,30 @@ import * as Sentry from '@sentry/react-native'; +import * as Application from 'expo-application'; // DSN is not a secret; it identifies the Sentry project for event ingestion. -// Set EXPO_PUBLIC_SENTRY_DSN in EAS environment variables (or replace the -// fallback below) once the Sentry project is created. With no DSN, the SDK -// stays fully disabled. +// Presence of the DSN is the on/off switch: set EXPO_PUBLIC_SENTRY_DSN in EAS +// environment variables for builds, or in a local .env (gitignored) to test +// from a dev client. With no DSN, the SDK stays fully disabled. const SENTRY_DSN = process.env.EXPO_PUBLIC_SENTRY_DSN; +// One Sentry project for all variants; the environment tag separates +// dev/preview/production events. Derived from the bundle id suffix +// (com.wyne.scorepad[.dev|.preview]) so no extra env plumbing is needed. +const appId = Application.applicationId ?? ''; +const environment = __DEV__ || appId.endsWith('.dev') + ? 'development' + : appId.endsWith('.preview') + ? 'preview' + : 'production'; + export const navigationIntegration = Sentry.reactNavigationIntegration({ enableTimeToInitialDisplay: true, }); Sentry.init({ dsn: SENTRY_DSN, - enabled: Boolean(SENTRY_DSN) && !__DEV__, + enabled: Boolean(SENTRY_DSN), + environment, // Low event volume app: trace every session so slow/frozen frame and // screen-load regressions are visible per release. tracesSampleRate: 1.0, From aa378ac80974700d4c0f088821940385056e7bba Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Fri, 12 Jun 2026 21:12:44 -0400 Subject: [PATCH 3/4] Set Sentry org slug to justin-wyne Co-Authored-By: Claude Fable 5 --- app.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.config.js b/app.config.js index e85ff87c..48902247 100644 --- a/app.config.js +++ b/app.config.js @@ -147,7 +147,7 @@ export default { { // Source map upload during EAS builds; skipped with a warning unless // SENTRY_AUTH_TOKEN is set in the build environment. - organization: process.env.SENTRY_ORG || 'wyne', + organization: process.env.SENTRY_ORG || 'justin-wyne', project: process.env.SENTRY_PROJECT || 'scorepad-react-native', }, ], From 0ca9afc6c3f93f4fc40eb74cdb3a93536c3d2a8d Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Fri, 12 Jun 2026 21:14:50 -0400 Subject: [PATCH 4/4] Add Sentry DSN to dev and preview EAS build profiles The DSN is a public ingestion identifier, not a secret. Production gets it via EAS environment variables once preview is validated. Co-Authored-By: Claude Fable 5 --- eas.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eas.json b/eas.json index 188f1211..efdc9a2b 100644 --- a/eas.json +++ b/eas.json @@ -10,7 +10,8 @@ "distribution": "internal", "env": { "APP_VARIANT": "development", - "EXPO_PUBLIC_FIREBASE_ANALYTICS": "false" + "EXPO_PUBLIC_FIREBASE_ANALYTICS": "false", + "EXPO_PUBLIC_SENTRY_DSN": "https://88dd6d7c83b64ed8870ff21a2a9f1ba7@o1326242.ingest.us.sentry.io/4504710808076288" } }, "preview": { @@ -18,7 +19,8 @@ "channel": "preview", "env": { "APP_VARIANT": "preview", - "EXPO_PUBLIC_FIREBASE_ANALYTICS": "false" + "EXPO_PUBLIC_FIREBASE_ANALYTICS": "false", + "EXPO_PUBLIC_SENTRY_DSN": "https://88dd6d7c83b64ed8870ff21a2a9f1ba7@o1326242.ingest.us.sentry.io/4504710808076288" } }, "production": {