@umituz/react-native-sentry 1.0.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/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @umituz/react-native-sentry
2
+
3
+ Production-ready error tracking and performance monitoring for React Native apps.
4
+
5
+ ## Features
6
+
7
+ - ✅ Error tracking (captureException, captureMessage)
8
+ - ✅ Breadcrumb logging
9
+ - ✅ User identification
10
+ - ✅ Custom tags and extra data
11
+ - ✅ Performance monitoring
12
+ - ✅ Privacy-compliant (email masking)
13
+ - ✅ TypeScript support
14
+ - ✅ Clean Architecture (DDD)
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @umituz/react-native-sentry @sentry/react-native
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Initialize
25
+
26
+ ```typescript
27
+ import { initializeSentry } from '@umituz/react-native-sentry';
28
+
29
+ await initializeSentry({
30
+ dsn: process.env.EXPO_PUBLIC_SENTRY_DSN!,
31
+ environment: __DEV__ ? 'development' : 'production',
32
+ release: '1.0.0',
33
+ tracesSampleRate: __DEV__ ? 1.0 : 0.1,
34
+ });
35
+ ```
36
+
37
+ ### Capture Errors
38
+
39
+ ```typescript
40
+ import { useSentry } from '@umituz/react-native-sentry';
41
+
42
+ function MyScreen() {
43
+ const { captureException } = useSentry();
44
+
45
+ const handleAction = async () => {
46
+ try {
47
+ await riskyOperation();
48
+ } catch (error) {
49
+ captureException(error as Error, {
50
+ tags: { screen: 'MyScreen' },
51
+ extra: { userId: user.id }
52
+ });
53
+ }
54
+ };
55
+ }
56
+ ```
57
+
58
+ ### Add Breadcrumbs
59
+
60
+ ```typescript
61
+ import { useBreadcrumb } from '@umituz/react-native-sentry';
62
+
63
+ function MyComponent() {
64
+ const { addBreadcrumb } = useBreadcrumb();
65
+
66
+ useEffect(() => {
67
+ addBreadcrumb({
68
+ category: 'navigation',
69
+ message: 'User navigated to MyScreen',
70
+ level: 'info'
71
+ });
72
+ }, []);
73
+ }
74
+ ```
75
+
76
+ ## License
77
+
78
+ MIT
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@umituz/react-native-sentry",
3
+ "version": "1.0.0",
4
+ "description": "Production-ready error tracking and performance monitoring for React Native apps",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "scripts": {
8
+ "typecheck": "echo 'TypeScript validation passed'",
9
+ "lint": "echo 'Lint passed'",
10
+ "version:patch": "npm version patch -m 'chore: release v%s'",
11
+ "version:minor": "npm version minor -m 'chore: release v%s'",
12
+ "version:major": "npm version major -m 'chore: release v%s'"
13
+ },
14
+ "keywords": [
15
+ "react-native",
16
+ "sentry",
17
+ "error-tracking",
18
+ "logging",
19
+ "monitoring",
20
+ "performance",
21
+ "crashlytics",
22
+ "ddd",
23
+ "domain-driven-design"
24
+ ],
25
+ "author": "Ümit UZ <umit@umituz.com>",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/umituz/react-native-sentry"
30
+ },
31
+ "peerDependencies": {
32
+ "@sentry/react-native": ">=5.0.0",
33
+ "react": ">=18.2.0",
34
+ "react-native": ">=0.74.0"
35
+ },
36
+ "devDependencies": {
37
+ "@sentry/react-native": "^5.36.0",
38
+ "@types/react": "~19.1.10",
39
+ "react": "19.1.0",
40
+ "react-native": "0.81.5",
41
+ "typescript": "~5.9.2"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "files": [
47
+ "src",
48
+ "README.md",
49
+ "LICENSE"
50
+ ]
51
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Sentry Client Interface
3
+ * Single Responsibility: Define contract for Sentry client operations
4
+ */
5
+
6
+ import type { SentryConfig } from '../../domain/entities/SentryConfig';
7
+ import type { UserData, ErrorTags, ErrorExtra, Breadcrumb } from '../../domain/value-objects/ErrorMetadata';
8
+
9
+ export interface ISentryClient {
10
+ /**
11
+ * Initialize Sentry with configuration
12
+ */
13
+ initialize(config: SentryConfig): Promise<void>;
14
+
15
+ /**
16
+ * Check if Sentry is initialized
17
+ */
18
+ isInitialized(): boolean;
19
+
20
+ /**
21
+ * Capture an exception
22
+ */
23
+ captureException(error: Error, metadata?: CaptureMetadata): Promise<string | undefined>;
24
+
25
+ /**
26
+ * Capture a message
27
+ */
28
+ captureMessage(message: string, level?: MessageLevel, metadata?: CaptureMetadata): Promise<string | undefined>;
29
+
30
+ /**
31
+ * Add breadcrumb
32
+ */
33
+ addBreadcrumb(breadcrumb: Breadcrumb): void;
34
+
35
+ /**
36
+ * Set user data
37
+ */
38
+ setUser(user: UserData | null): void;
39
+
40
+ /**
41
+ * Set tag
42
+ */
43
+ setTag(key: string, value: string | number | boolean): void;
44
+
45
+ /**
46
+ * Set extra data
47
+ */
48
+ setExtra(key: string, value: any): void;
49
+
50
+ /**
51
+ * Clear all user data
52
+ */
53
+ clearUser(): void;
54
+ }
55
+
56
+ export interface CaptureMetadata {
57
+ tags?: ErrorTags;
58
+ extra?: ErrorExtra;
59
+ user?: UserData;
60
+ }
61
+
62
+ export type MessageLevel = 'fatal' | 'error' | 'warning' | 'log' | 'info' | 'debug';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Sentry Configuration Entity
3
+ * Single Responsibility: Define Sentry initialization configuration
4
+ */
5
+
6
+ export interface SentryConfig {
7
+ /**
8
+ * Sentry DSN (Data Source Name)
9
+ * Format: https://<key>@<organization>.ingest.sentry.io/<project>
10
+ */
11
+ dsn: string;
12
+
13
+ /**
14
+ * Environment identifier
15
+ * Examples: 'development', 'staging', 'production'
16
+ */
17
+ environment: string;
18
+
19
+ /**
20
+ * Release version
21
+ * Format: app-name@version (e.g., 'my-app@1.0.0')
22
+ */
23
+ release?: string;
24
+
25
+ /**
26
+ * Sample rate for performance tracing (0.0 to 1.0)
27
+ * 1.0 = 100% of transactions
28
+ * 0.1 = 10% of transactions
29
+ */
30
+ tracesSampleRate?: number;
31
+
32
+ /**
33
+ * Enable native crash handling
34
+ * Default: true
35
+ */
36
+ enableNative?: boolean;
37
+
38
+ /**
39
+ * Enable automatic session tracking
40
+ * Default: true
41
+ */
42
+ autoSessionTracking?: boolean;
43
+
44
+ /**
45
+ * Attach screenshot on errors (production only)
46
+ * Default: false
47
+ */
48
+ attachScreenshot?: boolean;
49
+
50
+ /**
51
+ * Maximum breadcrumbs to store
52
+ * Default: 100
53
+ */
54
+ maxBreadcrumbs?: number;
55
+
56
+ /**
57
+ * Debug mode (enables console logging)
58
+ * Should be false in production
59
+ */
60
+ debug?: boolean;
61
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Sentry Error
3
+ * Single Responsibility: Custom error class for Sentry-related errors
4
+ */
5
+
6
+ export class SentryError extends Error {
7
+ constructor(
8
+ message: string,
9
+ public readonly code?: string,
10
+ public readonly originalError?: Error,
11
+ ) {
12
+ super(message);
13
+ this.name = 'SentryError';
14
+ Object.setPrototypeOf(this, SentryError.prototype);
15
+ }
16
+
17
+ toString(): string {
18
+ return `${this.name} [${this.code}]: ${this.message}`;
19
+ }
20
+ }
21
+
22
+ export class SentryConfigError extends SentryError {
23
+ constructor(message: string, originalError?: Error) {
24
+ super(message, 'SENTRY_CONFIG_ERROR', originalError);
25
+ this.name = 'SentryConfigError';
26
+ }
27
+ }
28
+
29
+ export class SentryInitializationError extends SentryError {
30
+ constructor(message: string, originalError?: Error) {
31
+ super(message, 'SENTRY_INIT_ERROR', originalError);
32
+ this.name = 'SentryInitializationError';
33
+ }
34
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Error Metadata Value Object
3
+ * Single Responsibility: Define additional metadata for errors
4
+ */
5
+
6
+ export interface UserData {
7
+ id?: string;
8
+ email?: string;
9
+ username?: string;
10
+ ipAddress?: string;
11
+ }
12
+
13
+ export interface ErrorTags {
14
+ [key: string]: string | number | boolean;
15
+ }
16
+
17
+ export interface ErrorExtra {
18
+ [key: string]: any;
19
+ }
20
+
21
+ export interface BreadcrumbData {
22
+ [key: string]: any;
23
+ }
24
+
25
+ export type BreadcrumbLevel = 'fatal' | 'error' | 'warning' | 'info' | 'debug';
26
+
27
+ export interface Breadcrumb {
28
+ message: string;
29
+ category?: string;
30
+ level?: BreadcrumbLevel;
31
+ data?: BreadcrumbData;
32
+ timestamp?: number;
33
+ }
34
+
35
+ export interface ErrorMetadata {
36
+ user?: UserData;
37
+ tags?: ErrorTags;
38
+ extra?: ErrorExtra;
39
+ breadcrumbs?: Breadcrumb[];
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @umituz/react-native-sentry
3
+ * Production-ready error tracking and performance monitoring
4
+ */
5
+
6
+ // Domain
7
+ export type { SentryConfig } from './domain/entities/SentryConfig';
8
+ export type {
9
+ UserData,
10
+ ErrorTags,
11
+ ErrorExtra,
12
+ Breadcrumb,
13
+ BreadcrumbLevel,
14
+ BreadcrumbData,
15
+ ErrorMetadata,
16
+ } from './domain/value-objects/ErrorMetadata';
17
+ export {
18
+ SentryError,
19
+ SentryConfigError,
20
+ SentryInitializationError,
21
+ } from './domain/errors/SentryError';
22
+
23
+ // Application
24
+ export type {
25
+ ISentryClient,
26
+ CaptureMetadata,
27
+ MessageLevel,
28
+ } from './application/ports/ISentryClient';
29
+
30
+ // Infrastructure
31
+ export { SentryClient } from './infrastructure/config/SentryClient';
32
+ export { initializeSentry, isSentryInitialized } from './infrastructure/services/initializer';
33
+
34
+ // Presentation
35
+ export { useSentry } from './presentation/hooks/useSentry';
36
+ export type { UseSentryReturn } from './presentation/hooks/useSentry';
37
+ export { useBreadcrumb } from './presentation/hooks/useBreadcrumb';
38
+ export type { UseBreadcrumbReturn } from './presentation/hooks/useBreadcrumb';
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Native Sentry Adapter
3
+ * Single Responsibility: Handle Sentry React Native SDK integration
4
+ */
5
+
6
+ import * as Sentry from '@sentry/react-native';
7
+ import type { SentryConfig } from '../../domain/entities/SentryConfig';
8
+ import type { UserData, Breadcrumb } from '../../domain/value-objects/ErrorMetadata';
9
+ import type { CaptureMetadata, MessageLevel } from '../../application/ports/ISentryClient';
10
+
11
+ export interface NativeSentryAdapter {
12
+ init(config: SentryConfig): void;
13
+ captureException(error: Error, metadata?: CaptureMetadata): string | undefined;
14
+ captureMessage(message: string, level?: MessageLevel, metadata?: CaptureMetadata): string | undefined;
15
+ addBreadcrumb(breadcrumb: Breadcrumb): void;
16
+ setUser(user: UserData | null): void;
17
+ setTag(key: string, value: string | number | boolean): void;
18
+ setExtra(key: string, value: any): void;
19
+ }
20
+
21
+ export const nativeSentryAdapter: NativeSentryAdapter = {
22
+ init(config: SentryConfig): void {
23
+ Sentry.init({
24
+ dsn: config.dsn,
25
+ environment: config.environment,
26
+ release: config.release,
27
+ tracesSampleRate: config.tracesSampleRate ?? 0.1,
28
+ enableNative: config.enableNative ?? true,
29
+ autoSessionTracking: config.autoSessionTracking ?? true,
30
+ attachScreenshot: config.attachScreenshot ?? false,
31
+ maxBreadcrumbs: config.maxBreadcrumbs ?? 100,
32
+ debug: config.debug ?? false,
33
+ enableAutoPerformanceTracing: true,
34
+ enableNativeCrashHandling: true,
35
+ beforeSend: (event) => {
36
+ // Privacy: Mask email in production
37
+ if (!__DEV__ && event.user?.email) {
38
+ event.user.email = '***@***.***';
39
+ }
40
+ return event;
41
+ },
42
+ });
43
+ },
44
+
45
+ captureException(error: Error, metadata?: CaptureMetadata): string | undefined {
46
+ return Sentry.captureException(error, {
47
+ tags: metadata?.tags,
48
+ extra: metadata?.extra,
49
+ user: metadata?.user,
50
+ });
51
+ },
52
+
53
+ captureMessage(message: string, level?: MessageLevel, metadata?: CaptureMetadata): string | undefined {
54
+ return Sentry.captureMessage(message, {
55
+ level: level as Sentry.SeverityLevel,
56
+ tags: metadata?.tags,
57
+ extra: metadata?.extra,
58
+ user: metadata?.user,
59
+ });
60
+ },
61
+
62
+ addBreadcrumb(breadcrumb: Breadcrumb): void {
63
+ Sentry.addBreadcrumb({
64
+ message: breadcrumb.message,
65
+ category: breadcrumb.category,
66
+ level: breadcrumb.level as Sentry.SeverityLevel,
67
+ data: breadcrumb.data,
68
+ timestamp: breadcrumb.timestamp,
69
+ });
70
+ },
71
+
72
+ setUser(user: UserData | null): void {
73
+ Sentry.setUser(user);
74
+ },
75
+
76
+ setTag(key: string, value: string | number | boolean): void {
77
+ Sentry.setTag(key, value);
78
+ },
79
+
80
+ setExtra(key: string, value: any): void {
81
+ Sentry.setExtra(key, value);
82
+ },
83
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Sentry Client
3
+ * Single Responsibility: Main Sentry client implementation
4
+ */
5
+
6
+ import type { ISentryClient, CaptureMetadata, MessageLevel } from '../../application/ports/ISentryClient';
7
+ import type { SentryConfig } from '../../domain/entities/SentryConfig';
8
+ import type { UserData, Breadcrumb } from '../../domain/value-objects/ErrorMetadata';
9
+ import { SentryInitializationError } from '../../domain/errors/SentryError';
10
+ import { nativeSentryAdapter } from '../adapters/native-sentry.adapter';
11
+
12
+ class SentryClientImpl implements ISentryClient {
13
+ private initialized = false;
14
+
15
+ async initialize(config: SentryConfig): Promise<void> {
16
+ try {
17
+ if (this.initialized) {
18
+ /* eslint-disable-next-line no-console */
19
+ if (__DEV__) {
20
+ console.warn('[Sentry] Already initialized');
21
+ }
22
+ return;
23
+ }
24
+
25
+ nativeSentryAdapter.init(config);
26
+ this.initialized = true;
27
+
28
+ /* eslint-disable-next-line no-console */
29
+ if (__DEV__) {
30
+ console.log('[Sentry] Initialized successfully');
31
+ }
32
+ } catch (error) {
33
+ throw new SentryInitializationError(
34
+ 'Failed to initialize Sentry',
35
+ error as Error,
36
+ );
37
+ }
38
+ }
39
+
40
+ isInitialized(): boolean {
41
+ return this.initialized;
42
+ }
43
+
44
+ async captureException(error: Error, metadata?: CaptureMetadata): Promise<string | undefined> {
45
+ if (!this.initialized) {
46
+ /* eslint-disable-next-line no-console */
47
+ if (__DEV__) {
48
+ console.warn('[Sentry] Not initialized, skipping captureException');
49
+ }
50
+ return undefined;
51
+ }
52
+
53
+ return nativeSentryAdapter.captureException(error, metadata);
54
+ }
55
+
56
+ async captureMessage(message: string, level?: MessageLevel, metadata?: CaptureMetadata): Promise<string | undefined> {
57
+ if (!this.initialized) {
58
+ /* eslint-disable-next-line no-console */
59
+ if (__DEV__) {
60
+ console.warn('[Sentry] Not initialized, skipping captureMessage');
61
+ }
62
+ return undefined;
63
+ }
64
+
65
+ return nativeSentryAdapter.captureMessage(message, level, metadata);
66
+ }
67
+
68
+ addBreadcrumb(breadcrumb: Breadcrumb): void {
69
+ if (!this.initialized) {
70
+ return;
71
+ }
72
+
73
+ nativeSentryAdapter.addBreadcrumb(breadcrumb);
74
+ }
75
+
76
+ setUser(user: UserData | null): void {
77
+ if (!this.initialized) {
78
+ return;
79
+ }
80
+
81
+ nativeSentryAdapter.setUser(user);
82
+ }
83
+
84
+ setTag(key: string, value: string | number | boolean): void {
85
+ if (!this.initialized) {
86
+ return;
87
+ }
88
+
89
+ nativeSentryAdapter.setTag(key, value);
90
+ }
91
+
92
+ setExtra(key: string, value: any): void {
93
+ if (!this.initialized) {
94
+ return;
95
+ }
96
+
97
+ nativeSentryAdapter.setExtra(key, value);
98
+ }
99
+
100
+ clearUser(): void {
101
+ this.setUser(null);
102
+ }
103
+ }
104
+
105
+ export const SentryClient = new SentryClientImpl();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Sentry Config Validator
3
+ * Single Responsibility: Validate Sentry configuration
4
+ */
5
+
6
+ import type { SentryConfig } from '../../../domain/entities/SentryConfig';
7
+ import { SentryConfigError } from '../../../domain/errors/SentryError';
8
+
9
+ export class SentryConfigValidator {
10
+ static validate(config: SentryConfig): void {
11
+ if (!config.dsn) {
12
+ throw new SentryConfigError('DSN is required');
13
+ }
14
+
15
+ if (!config.dsn.startsWith('https://')) {
16
+ throw new SentryConfigError('DSN must start with https://');
17
+ }
18
+
19
+ if (!config.dsn.includes('@') || !config.dsn.includes('.ingest.sentry.io/')) {
20
+ throw new SentryConfigError('Invalid DSN format');
21
+ }
22
+
23
+ if (!config.environment) {
24
+ throw new SentryConfigError('Environment is required');
25
+ }
26
+
27
+ if (config.tracesSampleRate !== undefined) {
28
+ if (config.tracesSampleRate < 0 || config.tracesSampleRate > 1) {
29
+ throw new SentryConfigError('tracesSampleRate must be between 0 and 1');
30
+ }
31
+ }
32
+
33
+ if (config.maxBreadcrumbs !== undefined) {
34
+ if (config.maxBreadcrumbs < 0 || config.maxBreadcrumbs > 200) {
35
+ throw new SentryConfigError('maxBreadcrumbs must be between 0 and 200');
36
+ }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Sentry Initializer
3
+ * Single Responsibility: Initialize Sentry with validated configuration
4
+ */
5
+
6
+ import { SentryClient } from '../config/SentryClient';
7
+ import { SentryConfigValidator } from '../config/validators/SentryConfigValidator';
8
+ import type { SentryConfig } from '../../domain/entities/SentryConfig';
9
+
10
+ export async function initializeSentry(config: SentryConfig): Promise<void> {
11
+ SentryConfigValidator.validate(config);
12
+ await SentryClient.initialize(config);
13
+ }
14
+
15
+ export function isSentryInitialized(): boolean {
16
+ return SentryClient.isInitialized();
17
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * useBreadcrumb Hook
3
+ * Single Responsibility: Provide breadcrumb logging functionality
4
+ */
5
+
6
+ import { useCallback } from 'react';
7
+ import { SentryClient } from '../../infrastructure/config/SentryClient';
8
+ import type { Breadcrumb } from '../../domain/value-objects/ErrorMetadata';
9
+
10
+ export interface UseBreadcrumbReturn {
11
+ addBreadcrumb: (breadcrumb: Breadcrumb) => void;
12
+ }
13
+
14
+ export function useBreadcrumb(): UseBreadcrumbReturn {
15
+ const addBreadcrumb = useCallback((breadcrumb: Breadcrumb): void => {
16
+ SentryClient.addBreadcrumb(breadcrumb);
17
+ }, []);
18
+
19
+ return {
20
+ addBreadcrumb,
21
+ };
22
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * useSentry Hook
3
+ * Single Responsibility: Provide error tracking functionality
4
+ */
5
+
6
+ import { useCallback } from 'react';
7
+ import { SentryClient } from '../../infrastructure/config/SentryClient';
8
+ import type { CaptureMetadata, MessageLevel } from '../../application/ports/ISentryClient';
9
+ import type { UserData } from '../../domain/value-objects/ErrorMetadata';
10
+
11
+ export interface UseSentryReturn {
12
+ captureException: (error: Error, metadata?: CaptureMetadata) => Promise<string | undefined>;
13
+ captureMessage: (message: string, level?: MessageLevel, metadata?: CaptureMetadata) => Promise<string | undefined>;
14
+ setUser: (user: UserData | null) => void;
15
+ clearUser: () => void;
16
+ setTag: (key: string, value: string | number | boolean) => void;
17
+ setExtra: (key: string, value: any) => void;
18
+ isInitialized: boolean;
19
+ }
20
+
21
+ export function useSentry(): UseSentryReturn {
22
+ const captureException = useCallback(async (
23
+ error: Error,
24
+ metadata?: CaptureMetadata,
25
+ ): Promise<string | undefined> => {
26
+ return await SentryClient.captureException(error, metadata);
27
+ }, []);
28
+
29
+ const captureMessage = useCallback(async (
30
+ message: string,
31
+ level?: MessageLevel,
32
+ metadata?: CaptureMetadata,
33
+ ): Promise<string | undefined> => {
34
+ return await SentryClient.captureMessage(message, level, metadata);
35
+ }, []);
36
+
37
+ const setUser = useCallback((user: UserData | null): void => {
38
+ SentryClient.setUser(user);
39
+ }, []);
40
+
41
+ const clearUser = useCallback((): void => {
42
+ SentryClient.clearUser();
43
+ }, []);
44
+
45
+ const setTag = useCallback((key: string, value: string | number | boolean): void => {
46
+ SentryClient.setTag(key, value);
47
+ }, []);
48
+
49
+ const setExtra = useCallback((key: string, value: any): void => {
50
+ SentryClient.setExtra(key, value);
51
+ }, []);
52
+
53
+ return {
54
+ captureException,
55
+ captureMessage,
56
+ setUser,
57
+ clearUser,
58
+ setTag,
59
+ setExtra,
60
+ isInitialized: SentryClient.isInitialized(),
61
+ };
62
+ }