From efbcb3744a67a6919aee2cb0e1ef791ec71925c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kry=C5=A1tof=20Wold=C5=99ich?= <31292499+krystofwoldrich@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:50:49 +0200 Subject: [PATCH] fix(promise): Warn users about multiple versions of `promise` package which can cause unexpected behaviour (#3162) Co-authored-by: LucasZF --- CHANGELOG.md | 4 ++ .../integrations/reactnativeerrorhandlers.ts | 42 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ded43b12..a739f290d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ - Use `android.namespace` for AGP 8 and RN 0.73 ([#3133](https://github.com/getsentry/sentry-react-native/pull/3133)) +### Fixes + +- Warn users about multiple versions of `promise` package which can cause unexpected behavior like undefined `Promise.allSettled` ([#3162](https://github.com/getsentry/sentry-react-native/pull/3162)) + ### Dependencies - Bump JavaScript SDK from v7.54.0 to v7.57.0 ([#3119](https://github.com/getsentry/sentry-react-native/pull/3119), [#3153](https://github.com/getsentry/sentry-react-native/pull/3153)) diff --git a/src/js/integrations/reactnativeerrorhandlers.ts b/src/js/integrations/reactnativeerrorhandlers.ts index 401b7ba8b..9a6a493de 100644 --- a/src/js/integrations/reactnativeerrorhandlers.ts +++ b/src/js/integrations/reactnativeerrorhandlers.ts @@ -76,8 +76,7 @@ export class ReactNativeErrorHandlers implements Integration { /* eslint-disable import/no-extraneous-dependencies,@typescript-eslint/no-var-requires */ const { polyfillGlobal } = require('react-native/Libraries/Utilities/PolyfillFunctions'); - // Below, we follow the exact way React Native initializes its promise library, and we globally replace it. - const Promise = require('promise/setimmediate/es6-extensions'); + const Promise = this._getPromisePolyfill(); // As of RN 0.67 only done and finally are used require('promise/setimmediate/done'); @@ -86,6 +85,17 @@ export class ReactNativeErrorHandlers implements Integration { polyfillGlobal('Promise', () => Promise); /* eslint-enable import/no-extraneous-dependencies,@typescript-eslint/no-var-requires */ } + + /** + * Single source of truth for the Promise implementation we want to use. + * This is important for verifying that the rejected promise tracing will work as expected. + */ + private _getPromisePolyfill(): unknown { + /* eslint-disable import/no-extraneous-dependencies,@typescript-eslint/no-var-requires */ + // Below, we follow the exact way React Native initializes its promise library, and we globally replace it. + return require('promise/setimmediate/es6-extensions'); + } + /** * Attach the unhandled rejection handler */ @@ -133,12 +143,31 @@ export class ReactNativeErrorHandlers implements Integration { */ private _checkPromiseAndWarn(): void { try { + // `promise` package is a dependency of react-native, therefore it is always available. + // but it is possible that the user has installed a different version of promise + // or dependency that uses a different version. + // We have to check if the React Native Promise and the `promise` package Promise are using the same reference. + // If they are not, likely there are multiple versions of the `promise` package installed. + // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-extraneous-dependencies + const ReactNativePromise = require('react-native/Libraries/Promise'); // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-extraneous-dependencies - const Promise = require('promise/setimmediate/es6-extensions'); + const PromisePackagePromise = require('promise/setimmediate/es6-extensions'); + const UsedPromisePolyfill = this._getPromisePolyfill(); + + if (ReactNativePromise !== PromisePackagePromise) { + logger.warn( + 'You appear to have multiple versions of the "promise" package installed. ' + + 'This may cause unexpected behavior like undefined `Promise.allSettled`. ' + + 'Please install the `promise` package manually using the exact version as the React Native package. ' + + 'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.', + ); + } - if (Promise !== RN_GLOBAL_OBJ.Promise) { + // This only make sense if the user disabled the integration Polyfill + if (UsedPromisePolyfill !== RN_GLOBAL_OBJ.Promise) { logger.warn( - 'Unhandled promise rejections will not be caught by Sentry. Read about how to fix this on our troubleshooting page.', + 'Unhandled promise rejections will not be caught by Sentry. ' + + 'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.', ); } else { logger.log('Unhandled promise rejections will be caught by Sentry.'); @@ -146,7 +175,8 @@ export class ReactNativeErrorHandlers implements Integration { } catch (e) { // Do Nothing logger.warn( - 'Unhandled promise rejections will not be caught by Sentry. Read about how to fix this on our troubleshooting page.', + 'Unhandled promise rejections will not be caught by Sentry. ' + + 'See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.', ); } }