Skip to content

Commit

Permalink
fix(promise): Warn users about multiple versions of promise package…
Browse files Browse the repository at this point in the history
… which can cause unexpected behaviour (#3162)

Co-authored-by: LucasZF <[email protected]>
  • Loading branch information
krystofwoldrich and lucas-zimerman committed Jul 17, 2023
1 parent 248ea69 commit efbcb37
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
42 changes: 36 additions & 6 deletions src/js/integrations/reactnativeerrorhandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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
*/
Expand Down Expand Up @@ -133,20 +143,40 @@ 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.');
}
} 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.',
);
}
}
Expand Down

0 comments on commit efbcb37

Please sign in to comment.