diff --git a/CHANGELOG.md b/CHANGELOG.md index f33bd22095..9d553f5fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Features + +- Warn during dev builds when multiple versions of Sentry JS SDK are detected ([#6269](https://github.com/getsentry/sentry-react-native/pull/6269)) + ## 8.14.0 ### Features diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx index 84cee9a088..2ddc9121e2 100644 --- a/packages/core/src/js/sdk.tsx +++ b/packages/core/src/js/sdk.tsx @@ -31,6 +31,7 @@ import { DEFAULT_BUFFER_SIZE, makeNativeTransportFactory } from './transports/na import { getDefaultEnvironment, isExpoGo, isRunningInMetroDevServer, isWeb } from './utils/environment'; import { getDefaultRelease } from './utils/release'; import { safeFactory, safeTracesSampler } from './utils/safe'; +import { checkSentryJsSdkVersionMismatch } from './utils/sdkVersionCheck'; import { RN_GLOBAL_OBJ } from './utils/worldwide'; import { NATIVE } from './wrapper'; @@ -172,6 +173,9 @@ export function init(passedOptions: ReactNativeOptions): void { defaultIntegrations, }); initAndBind(ReactNativeClient, options); + if (__DEV__) { + checkSentryJsSdkVersionMismatch(); + } if (isExpoGo()) { debug.log('Offline caching, native errors features are not available in Expo Go.'); diff --git a/packages/core/src/js/utils/sdkVersionCheck.ts b/packages/core/src/js/utils/sdkVersionCheck.ts new file mode 100644 index 0000000000..17fd5df84a --- /dev/null +++ b/packages/core/src/js/utils/sdkVersionCheck.ts @@ -0,0 +1,26 @@ +import { debug, getMainCarrier, SDK_VERSION as CORE_SDK_VERSION } from '@sentry/core'; + +export function checkSentryJsSdkVersionMismatch(): void { + try { + const carrier = getMainCarrier(); + const sentryCarrier = carrier.__SENTRY__; + if (!sentryCarrier) { + return; + } + + const versions = Object.keys(sentryCarrier).filter(key => key !== CORE_SDK_VERSION && key !== 'version'); + if (versions.length === 0) { + return; + } + + debug.warn( + `Multiple versions of Sentry JavaScript SDKs were detected in your application. ` + + `Found versions: ${[CORE_SDK_VERSION, ...versions].join(', ')}. ` + + `This may cause unexpected behavior. ` + + `Ensure all Sentry packages use the same version. ` + + `See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.`, + ); + } catch (_e) { + // Ignore errors from version check + } +} diff --git a/packages/core/test/utils/sdkVersionCheck.test.ts b/packages/core/test/utils/sdkVersionCheck.test.ts new file mode 100644 index 0000000000..33b021badb --- /dev/null +++ b/packages/core/test/utils/sdkVersionCheck.test.ts @@ -0,0 +1,78 @@ +const mockDebugWarn = jest.fn(); +const mockCarrier: Record = {}; + +jest.mock('@sentry/core', () => ({ + debug: { + get warn() { + return mockDebugWarn; + }, + }, + getMainCarrier: () => mockCarrier, + SDK_VERSION: '10.0.0', +})); + +import { checkSentryJsSdkVersionMismatch } from '../../src/js/utils/sdkVersionCheck'; + +describe('checkSentryJsSdkVersionMismatch', () => { + beforeEach(() => { + jest.clearAllMocks(); + delete mockCarrier.__SENTRY__; + }); + + it('does not warn when only one SDK version is present', () => { + mockCarrier.__SENTRY__ = { '10.0.0': {} }; + + checkSentryJsSdkVersionMismatch(); + + expect(mockDebugWarn).not.toHaveBeenCalled(); + }); + + it('warns when multiple SDK versions are present', () => { + mockCarrier.__SENTRY__ = { '10.0.0': {}, '9.0.0': {} }; + + checkSentryJsSdkVersionMismatch(); + + expect(mockDebugWarn).toHaveBeenCalledTimes(1); + expect(mockDebugWarn).toHaveBeenCalledWith(expect.stringContaining('Multiple versions of Sentry JavaScript SDKs')); + expect(mockDebugWarn).toHaveBeenCalledWith(expect.stringContaining('9.0.0')); + expect(mockDebugWarn).toHaveBeenCalledWith(expect.stringContaining('10.0.0')); + }); + + it('warns when more than two SDK versions are present', () => { + mockCarrier.__SENTRY__ = { '10.0.0': {}, '9.0.0': {}, '8.0.0': {} }; + + checkSentryJsSdkVersionMismatch(); + + expect(mockDebugWarn).toHaveBeenCalledTimes(1); + expect(mockDebugWarn).toHaveBeenCalledWith(expect.stringContaining('9.0.0')); + expect(mockDebugWarn).toHaveBeenCalledWith(expect.stringContaining('8.0.0')); + }); + + it('does not warn when carrier has the version key set by @sentry/core', () => { + mockCarrier.__SENTRY__ = { '10.0.0': {}, version: '10.0.0' }; + + checkSentryJsSdkVersionMismatch(); + + expect(mockDebugWarn).not.toHaveBeenCalled(); + }); + + it('does not warn when __SENTRY__ is not set', () => { + checkSentryJsSdkVersionMismatch(); + + expect(mockDebugWarn).not.toHaveBeenCalled(); + }); + + it('does not throw on unexpected errors', () => { + Object.defineProperty(mockCarrier, '__SENTRY__', { + get() { + throw new Error('test error'); + }, + configurable: true, + }); + + expect(() => checkSentryJsSdkVersionMismatch()).not.toThrow(); + expect(mockDebugWarn).not.toHaveBeenCalled(); + + delete mockCarrier.__SENTRY__; + }); +});