Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
### Fixes

- crashedLastRun now returns the correct value ([#4829](https://github.com/getsentry/sentry-react-native/pull/4829))
- Use engine-specific promise rejection tracking ([#4826](https://github.com/getsentry/sentry-react-native/pull/4826))

## 6.14.0

Expand Down
115 changes: 81 additions & 34 deletions packages/core/src/js/integrations/reactnativeerrorhandlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import type { EventHint, Integration, SeverityLevel } from '@sentry/core';
import { addExceptionMechanism, captureException, getClient, getCurrentScope, logger } from '@sentry/core';

import {
addExceptionMechanism,
addGlobalUnhandledRejectionInstrumentationHandler,
captureException,
getClient,
getCurrentScope,
logger,
} from '@sentry/core';

import { isHermesEnabled, isWeb } from '../utils/environment';
import { createSyntheticError, isErrorLike } from '../utils/error';
import { RN_GLOBAL_OBJ } from '../utils/worldwide';
import { checkPromiseAndWarn, polyfillPromise, requireRejectionTracking } from './reactnativeerrorhandlersutils';
Expand Down Expand Up @@ -44,49 +52,88 @@ function setup(options: ReactNativeErrorHandlersOptions): void {
* Setup unhandled promise rejection tracking
*/
function setupUnhandledRejectionsTracking(patchGlobalPromise: boolean): void {
if (patchGlobalPromise) {
polyfillPromise();
}
try {
if (
isHermesEnabled() &&
RN_GLOBAL_OBJ.HermesInternal?.enablePromiseRejectionTracker &&
RN_GLOBAL_OBJ?.HermesInternal?.hasPromise?.()
) {
logger.log('Using Hermes native promise rejection tracking');

RN_GLOBAL_OBJ.HermesInternal.enablePromiseRejectionTracker({
allRejections: true,
onUnhandled: promiseRejectionTrackingOptions.onUnhandled,
onHandled: promiseRejectionTrackingOptions.onHandled,
});

attachUnhandledRejectionHandler();
checkPromiseAndWarn();
logger.log('Unhandled promise rejections will be caught by Sentry.');
} else if (isWeb()) {
logger.log('Using Browser JS promise rejection tracking for React Native Web');

// Use Sentry's built-in global unhandled rejection handler
addGlobalUnhandledRejectionInstrumentationHandler((error: unknown) => {
Comment thread
krystofwoldrich marked this conversation as resolved.
captureException(error, {
originalException: error,
syntheticException: isErrorLike(error) ? undefined : createSyntheticError(),
mechanism: { handled: false, type: 'onunhandledrejection' },
});
});

logger.warn(
'Unhandled promise rejections will not be caught by Sentry. ' +
'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.',
);
Comment thread
krystofwoldrich marked this conversation as resolved.
Outdated
} else if (patchGlobalPromise) {
// For JSC and other environments, use the existing approach
polyfillPromise();
attachUnhandledRejectionHandler();
checkPromiseAndWarn();
} else {
// For JSC and other environments, patching was disabled by user configuration
logger.log('Unhandled promise rejections will not be caught by Sentry.');
}
} catch (e) {
logger.warn(
'Failed to set up promise rejection tracking. ' +
'Unhandled promise rejections will not be caught by Sentry.' +
'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.',
);
}
}

function attachUnhandledRejectionHandler(): void {
const tracking = requireRejectionTracking();
const promiseRejectionTrackingOptions: PromiseRejectionTrackingOptions = {
onUnhandled: (id, error: unknown, rejection = {}) => {
if (__DEV__) {
logger.warn(`Possible Unhandled Promise Rejection (id: ${id}):\n${rejection}`);
}

const promiseRejectionTrackingOptions: PromiseRejectionTrackingOptions = {
onUnhandled: (id, rejection = {}) => {
// eslint-disable-next-line no-console
console.warn(`Possible Unhandled Promise Rejection (id: ${id}):\n${rejection}`);
},
onHandled: id => {
// eslint-disable-next-line no-console
console.warn(
// Marking the rejection as handled to avoid breaking crash rate calculations.
// See: https://github.com/getsentry/sentry-react-native/issues/4141
captureException(error, {
data: { id },
originalException: error,
syntheticException: isErrorLike(error) ? undefined : createSyntheticError(),
mechanism: { handled: true, type: 'onunhandledrejection' },
});
},
onHandled: id => {
if (__DEV__) {
logger.warn(
`Promise Rejection Handled (id: ${id})\n` +
'This means you can ignore any previous messages of the form ' +
`"Possible Unhandled Promise Rejection (id: ${id}):"`,
);
},
};
}
},
};

function attachUnhandledRejectionHandler(): void {
const tracking = requireRejectionTracking();

tracking.enable({
allRejections: true,
onUnhandled: (id: string, error: unknown) => {
if (__DEV__) {
promiseRejectionTrackingOptions.onUnhandled(id, error);
}

captureException(error, {
data: { id },
originalException: error,
syntheticException: isErrorLike(error) ? undefined : createSyntheticError(),
mechanism: { handled: true, type: 'onunhandledrejection' },
});
},
onHandled: (id: string) => {
promiseRejectionTrackingOptions.onHandled(id);
},
onUnhandled: promiseRejectionTrackingOptions.onUnhandled,
onHandled: promiseRejectionTrackingOptions.onHandled,
});
}

Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/js/utils/worldwide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ import { GLOBAL_OBJ } from '@sentry/core';
import type { ErrorUtils } from 'react-native/types';

import type { ExpoGlobalObject } from './expoglobalobject';
export interface HermesPromiseRejectionTrackingOptions {
allRejections: boolean;
onUnhandled: (id: string, error: unknown) => void;
onHandled: (id: string) => void;
}

/** Internal Global object interface with common and Sentry specific properties */
export interface ReactNativeInternalGlobal extends InternalGlobal {
__sentry_rn_v4_registered?: boolean;
__sentry_rn_v5_registered?: boolean;
HermesInternal?: {
getRuntimeProperties?: () => Record<string, string | undefined>;
enablePromiseRejectionTracker?: (options: HermesPromiseRejectionTrackingOptions) => void;
hasPromise?: () => boolean;
};
Promise: unknown;
__turboModuleProxy: unknown;
Expand Down
Loading
Loading