diff --git a/CHANGELOG.md b/CHANGELOG.md
index 86300d8b65..a64d8297cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
## Unreleased
+- fix: Fix unhandle promise rejections not being tracked #1367
## 2.2.1
diff --git a/sample/src/screens/EndToEndTestsScreen.tsx b/sample/src/screens/EndToEndTestsScreen.tsx
index a1be477ba7..0801c68bae 100644
--- a/sample/src/screens/EndToEndTestsScreen.tsx
+++ b/sample/src/screens/EndToEndTestsScreen.tsx
@@ -52,6 +52,15 @@ const EndToEndTestsScreen = () => {
}}>
throw new Error
+ {
+ new Promise(() => {
+ throw new Error('Unhandled Promise Rejection');
+ });
+ }}
+ {...getTestProps('unhandledPromiseRejection')}>
+ Unhandled Promise Rejection
+
{
Sentry.nativeCrash();
diff --git a/sample/src/screens/HomeScreen.tsx b/sample/src/screens/HomeScreen.tsx
index f6a516816d..4dc9ade94c 100644
--- a/sample/src/screens/HomeScreen.tsx
+++ b/sample/src/screens/HomeScreen.tsx
@@ -171,6 +171,15 @@ const HomeScreen = (props: Props) => {
Uncaught Thrown Error
+ {
+ new Promise(() => {
+ throw new Error('Unhandled Promise Rejection');
+ });
+ }}>
+ Unhandled Promise Rejection
+
+
{
Sentry.nativeCrash();
diff --git a/sample/test/e2e.test.ts b/sample/test/e2e.test.ts
index 48da3c6ca5..f8f11474b3 100644
--- a/sample/test/e2e.test.ts
+++ b/sample/test/e2e.test.ts
@@ -93,4 +93,29 @@ describe('End to end tests for common events', () => {
expect(sentryEvent.eventID).toMatch(eventId);
});
+
+ test('unhandledPromiseRejection', async () => {
+ expect(
+ await driver.hasElementByAccessibilityId('unhandledPromiseRejection'),
+ ).toBe(true);
+
+ const element = await driver.elementByAccessibilityId(
+ 'unhandledPromiseRejection',
+ );
+ await element.click();
+
+ // Promises needs a while to fail
+ await driver.sleep(5000);
+
+ expect(await driver.hasElementByAccessibilityId('eventId')).toBe(true);
+
+ const eventIdElement = await driver.elementByAccessibilityId('eventId');
+ const eventId = await eventIdElement.text();
+
+ await driver.sleep(10000);
+
+ const sentryEvent = await fetchEvent(eventId);
+
+ expect(sentryEvent.eventID).toMatch(eventId);
+ });
});
diff --git a/src/js/integrations/reactnativeerrorhandlers.ts b/src/js/integrations/reactnativeerrorhandlers.ts
index 28c2ce6a63..a557fd9645 100644
--- a/src/js/integrations/reactnativeerrorhandlers.ts
+++ b/src/js/integrations/reactnativeerrorhandlers.ts
@@ -1,6 +1,6 @@
import { getCurrentHub } from "@sentry/core";
import { Integration, Severity } from "@sentry/types";
-import { logger } from "@sentry/utils";
+import { getGlobalObject, logger } from "@sentry/utils";
import { ReactNativeClient } from "../client";
@@ -54,27 +54,63 @@ export class ReactNativeErrorHandlers implements Integration {
enable: (arg: unknown) => void;
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-extraneous-dependencies
} = require("promise/setimmediate/rejection-tracking");
+
tracking.disable();
tracking.enable({
allRejections: true,
- onHandled: () => {
- // We do nothing
- },
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
onUnhandled: (id: any, error: any) => {
if (__DEV__) {
+ // We mimic the behavior of unhandled promise rejections showing up as a warning.
// eslint-disable-next-line no-console
console.warn(id, error);
}
-
getCurrentHub().captureException(error, {
data: { id },
originalException: error,
});
},
});
+
+ /* eslint-disable
+ @typescript-eslint/no-var-requires,
+ import/no-extraneous-dependencies,
+ @typescript-eslint/no-explicit-any,
+ @typescript-eslint/no-unsafe-member-access
+ */
+ const Promise = require("promise/setimmediate/core");
+ const _global = getGlobalObject();
+
+ /* In newer RN versions >=0.63, the global promise is not the same reference as the one imported from the promise library.
+ Due to this, we need to take the methods that tracking.enable sets, and then set them on the global promise.
+ Note: We do not want to overwrite the whole promise in case there are extensions present.
+
+ If the global promise is the same as the imported promise (expected in RN <0.63), we do nothing.
+ */
+ const _onHandle = Promise._onHandle ?? Promise._Y;
+ const _onReject = Promise._onReject ?? Promise._Z;
+
+ if (
+ Promise !== _global.Promise &&
+ typeof _onHandle !== "undefined" &&
+ typeof _onReject !== "undefined"
+ ) {
+ if ("_onHandle" in _global.Promise && "_onReject" in _global.Promise) {
+ _global.Promise._onHandle = _onHandle;
+ _global.Promise._onReject = _onReject;
+ } else if ("_Y" in _global.Promise && "_Z" in _global.Promise) {
+ _global.Promise._Y = _onHandle;
+ _global.Promise._Z = _onReject;
+ }
+ }
+ /* eslint-enable
+ @typescript-eslint/no-var-requires,
+ import/no-extraneous-dependencies,
+ @typescript-eslint/no-explicit-any,
+ @typescript-eslint/no-unsafe-member-access
+ */
}
}
-
/**
* Handle erros
*/