Skip to content

Commit ca94e8c

Browse files
Merge remote-tracking branch 'origin/expo' into kw-dynamic-resolve-cli-expo-script
2 parents f84dad5 + dc8966b commit ca94e8c

File tree

6 files changed

+180
-71
lines changed

6 files changed

+180
-71
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ This release is compatible with `[email protected]` and newer.
2727
- Sentry CLI binary path in `scripts/expo-upload-sourcemaps.js` is resolved dynamically ([#3507](https://github.com/getsentry/sentry-react-native/pull/3507))
2828
- Or can be overwritten by `SENTRY_CLI_EXECUTABLE` env
2929

30+
- Resolve Default Integrations based on current platform ([#3465](https://github.com/getsentry/sentry-react-native/pull/3465))
31+
- Native Integrations are only added if Native Module is available
32+
- Web Integrations only for React Native Web builds
33+
3034
### Fixes
3135

3236
- Includes fixes from version 5.15.2

src/js/integrations/default.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { hasTracingEnabled } from '@sentry/core';
2+
import { HttpClient } from '@sentry/integrations';
3+
import { Integrations as BrowserReactIntegrations } from '@sentry/react';
4+
import type { Integration } from '@sentry/types';
5+
6+
import type { ReactNativeClientOptions } from '../options';
7+
import { HermesProfiling } from '../profiling/integration';
8+
import { ReactNativeTracing } from '../tracing';
9+
import { notWeb } from '../utils/environment';
10+
import { DebugSymbolicator } from './debugsymbolicator';
11+
import { DeviceContext } from './devicecontext';
12+
import { EventOrigin } from './eventorigin';
13+
import { ModulesLoader } from './modulesloader';
14+
import { NativeLinkedErrors } from './nativelinkederrors';
15+
import { ReactNativeErrorHandlers } from './reactnativeerrorhandlers';
16+
import { ReactNativeInfo } from './reactnativeinfo';
17+
import { Release } from './release';
18+
import { createReactNativeRewriteFrames } from './rewriteframes';
19+
import { Screenshot } from './screenshot';
20+
import { SdkInfo } from './sdkinfo';
21+
import { ViewHierarchy } from './viewhierarchy';
22+
23+
/**
24+
* Returns the default ReactNative integrations based on the current environment.
25+
*
26+
* Native integrations are only returned when native is enabled.
27+
*
28+
* Web integrations are only returned when running on web.
29+
*/
30+
export function getDefaultIntegrations(options: ReactNativeClientOptions): Integration[] {
31+
const integrations: Integration[] = [];
32+
33+
if (notWeb()) {
34+
integrations.push(
35+
new ReactNativeErrorHandlers({
36+
patchGlobalPromise: options.patchGlobalPromise,
37+
}),
38+
);
39+
integrations.push(new NativeLinkedErrors());
40+
} else {
41+
integrations.push(new BrowserReactIntegrations.TryCatch());
42+
integrations.push(new BrowserReactIntegrations.GlobalHandlers());
43+
integrations.push(new BrowserReactIntegrations.LinkedErrors());
44+
}
45+
46+
// @sentry/react default integrations
47+
integrations.push(new BrowserReactIntegrations.InboundFilters());
48+
integrations.push(new BrowserReactIntegrations.FunctionToString());
49+
integrations.push(new BrowserReactIntegrations.Breadcrumbs());
50+
integrations.push(new BrowserReactIntegrations.Dedupe());
51+
integrations.push(new BrowserReactIntegrations.HttpContext());
52+
// end @sentry/react-native default integrations
53+
54+
integrations.push(new Release());
55+
integrations.push(new EventOrigin());
56+
integrations.push(new SdkInfo());
57+
integrations.push(new ReactNativeInfo());
58+
59+
if (__DEV__ && notWeb()) {
60+
integrations.push(new DebugSymbolicator());
61+
}
62+
63+
integrations.push(createReactNativeRewriteFrames());
64+
65+
if (options.enableNative) {
66+
integrations.push(new DeviceContext());
67+
integrations.push(new ModulesLoader());
68+
if (options.attachScreenshot) {
69+
integrations.push(new Screenshot());
70+
}
71+
if (options.attachViewHierarchy) {
72+
integrations.push(new ViewHierarchy());
73+
}
74+
if (options._experiments && typeof options._experiments.profilesSampleRate === 'number') {
75+
integrations.push(new HermesProfiling());
76+
}
77+
}
78+
79+
if (hasTracingEnabled(options) && options.enableAutoPerformanceTracing) {
80+
integrations.push(new ReactNativeTracing());
81+
}
82+
if (options.enableCaptureFailedRequests) {
83+
integrations.push(new HttpClient());
84+
}
85+
86+
return integrations;
87+
}

src/js/sdk.tsx

+5-68
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,17 @@
11
/* eslint-disable complexity */
22
import type { Scope } from '@sentry/core';
3-
import { getIntegrationsToSetup, hasTracingEnabled, Hub, initAndBind, makeMain, setExtra } from '@sentry/core';
4-
import { HttpClient } from '@sentry/integrations';
3+
import { getIntegrationsToSetup, Hub, initAndBind, makeMain, setExtra } from '@sentry/core';
54
import {
6-
defaultIntegrations as reactDefaultIntegrations,
75
defaultStackParser,
86
getCurrentHub,
97
makeFetchTransport,
108
} from '@sentry/react';
119
import type { Integration, UserFeedback } from '@sentry/types';
1210
import { logger, stackParserFromStackParserOptions } from '@sentry/utils';
1311
import * as React from 'react';
14-
import { Platform } from 'react-native';
1512

1613
import { ReactNativeClient } from './client';
17-
import {
18-
DebugSymbolicator,
19-
DeviceContext,
20-
EventOrigin,
21-
HermesProfiling,
22-
ModulesLoader,
23-
ReactNativeErrorHandlers,
24-
ReactNativeInfo,
25-
Release,
26-
SdkInfo,
27-
} from './integrations';
28-
import { NativeLinkedErrors } from './integrations/nativelinkederrors';
29-
import { createReactNativeRewriteFrames } from './integrations/rewriteframes';
30-
import { Screenshot } from './integrations/screenshot';
31-
import { ViewHierarchy } from './integrations/viewhierarchy';
14+
import { getDefaultIntegrations } from './integrations/default';
3215
import type { ReactNativeClientOptions, ReactNativeOptions, ReactNativeWrapperOptions } from './options';
3316
import { ReactNativeScope } from './scope';
3417
import { TouchEventBoundary } from './touchevents';
@@ -39,11 +22,6 @@ import { getDefaultEnvironment } from './utils/environment';
3922
import { safeFactory, safeTracesSampler } from './utils/safe';
4023
import { NATIVE } from './wrapper';
4124

42-
const IGNORED_DEFAULT_INTEGRATIONS = [
43-
'GlobalHandlers', // We will use the react-native internal handlers
44-
'TryCatch', // We don't need this
45-
'LinkedErrors', // We replace this with `NativeLinkedError`
46-
];
4725
const DEFAULT_OPTIONS: ReactNativeOptions = {
4826
enableNativeCrashHandling: true,
4927
enableNativeNagger: true,
@@ -102,50 +80,9 @@ export function init(passedOptions: ReactNativeOptions): void {
10280
options.environment = getDefaultEnvironment();
10381
}
10482

105-
const defaultIntegrations: Integration[] = passedOptions.defaultIntegrations || [];
106-
if (passedOptions.defaultIntegrations === undefined) {
107-
defaultIntegrations.push(new ModulesLoader());
108-
if (Platform.OS !== 'web') {
109-
defaultIntegrations.push(new ReactNativeErrorHandlers({
110-
patchGlobalPromise: options.patchGlobalPromise,
111-
}));
112-
}
113-
defaultIntegrations.push(new Release());
114-
defaultIntegrations.push(...[
115-
...reactDefaultIntegrations.filter(
116-
(i) => !IGNORED_DEFAULT_INTEGRATIONS.includes(i.name)
117-
),
118-
]);
119-
120-
defaultIntegrations.push(new NativeLinkedErrors());
121-
defaultIntegrations.push(new EventOrigin());
122-
defaultIntegrations.push(new SdkInfo());
123-
defaultIntegrations.push(new ReactNativeInfo());
124-
125-
if (__DEV__) {
126-
defaultIntegrations.push(new DebugSymbolicator());
127-
}
128-
129-
defaultIntegrations.push(createReactNativeRewriteFrames());
130-
if (options.enableNative) {
131-
defaultIntegrations.push(new DeviceContext());
132-
}
133-
if (options._experiments && typeof options._experiments.profilesSampleRate === 'number') {
134-
defaultIntegrations.push(new HermesProfiling());
135-
}
136-
if (hasTracingEnabled(options) && options.enableAutoPerformanceTracing) {
137-
defaultIntegrations.push(new ReactNativeTracing());
138-
}
139-
if (options.attachScreenshot) {
140-
defaultIntegrations.push(new Screenshot());
141-
}
142-
if (options.attachViewHierarchy) {
143-
defaultIntegrations.push(new ViewHierarchy());
144-
}
145-
if (options.enableCaptureFailedRequests) {
146-
defaultIntegrations.push(new HttpClient());
147-
}
148-
}
83+
const defaultIntegrations: false | Integration[] = passedOptions.defaultIntegrations === undefined
84+
? getDefaultIntegrations(options)
85+
: passedOptions.defaultIntegrations;
14986

15087
options.integrations = getIntegrationsToSetup({
15188
integrations: safeFactory(passedOptions.integrations, { loggerMessage: 'The integrations threw an error' }),

src/js/utils/environment.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Platform } from 'react-native';
2+
13
import { RN_GLOBAL_OBJ } from '../utils/worldwide';
24
import { ReactNativeLibraries } from './rnlibraries';
35

@@ -30,6 +32,11 @@ export function isExpo(): boolean {
3032
return RN_GLOBAL_OBJ.expo != null;
3133
}
3234

35+
/** Checks if the current platform is not web */
36+
export function notWeb(): boolean {
37+
return Platform.OS !== 'web';
38+
}
39+
3340
/** Returns Hermes Version if hermes is present in the runtime */
3441
export function getHermesVersion(): string | undefined {
3542
return (

test/profiling/integration.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as Sentry from '../../src/js';
1010
import { HermesProfiling } from '../../src/js/integrations';
1111
import type { NativeDeviceContextsResponse } from '../../src/js/NativeRNSentry';
1212
import { getDebugMetadata } from '../../src/js/profiling/debugid';
13-
import { getDefaultEnvironment, isHermesEnabled } from '../../src/js/utils/environment';
13+
import { getDefaultEnvironment, isHermesEnabled, notWeb } from '../../src/js/utils/environment';
1414
import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide';
1515
import { MOCK_DSN } from '../mockDsn';
1616
import { envelopeItemPayload, envelopeItems } from '../testutils';
@@ -24,6 +24,7 @@ describe('profiling integration', () => {
2424
};
2525

2626
beforeEach(() => {
27+
(notWeb as jest.Mock).mockReturnValue(true);
2728
(isHermesEnabled as jest.Mock).mockReturnValue(true);
2829
mockWrapper.NATIVE.startProfiling.mockReturnValue(true);
2930
mockWrapper.NATIVE.stopProfiling.mockReturnValue({

test/sdk.test.ts

+75-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ jest.mock('@sentry/react', () => {
3131
configureScope: mockedGetCurrentHubConfigureScope,
3232
};
3333
}),
34-
defaultIntegrations: [{ name: 'MockedDefaultReactIntegration', setupOnce: jest.fn() }],
3534
};
3635
});
3736

@@ -64,6 +63,7 @@ jest.mock('../src/js/client', () => {
6463
});
6564

6665
jest.mock('../src/js/wrapper');
66+
jest.mock('../src/js/utils/environment');
6767

6868
jest.spyOn(logger, 'error');
6969

@@ -75,6 +75,7 @@ import type { ReactNativeClientOptions } from '../src/js/options';
7575
import { configureScope, flush, init, withScope } from '../src/js/sdk';
7676
import { ReactNativeTracing, ReactNavigationInstrumentation } from '../src/js/tracing';
7777
import { makeNativeTransport } from '../src/js/transports/native';
78+
import { getDefaultEnvironment, notWeb } from '../src/js/utils/environment';
7879
import { firstArg, secondArg } from './testutils';
7980

8081
const mockedInitAndBind = initAndBind as jest.MockedFunction<typeof initAndBind>;
@@ -83,6 +84,11 @@ const usedOptions = (): ClientOptions<BaseTransportOptions> | undefined => {
8384
};
8485

8586
describe('Tests the SDK functionality', () => {
87+
beforeEach(() => {
88+
(NATIVE.isNativeAvailable as jest.Mock).mockImplementation(() => true);
89+
(notWeb as jest.Mock).mockImplementation(() => true);
90+
});
91+
8692
afterEach(() => {
8793
jest.clearAllMocks();
8894
});
@@ -186,6 +192,7 @@ describe('Tests the SDK functionality', () => {
186192

187193
describe('environment', () => {
188194
it('detect development environment', () => {
195+
(getDefaultEnvironment as jest.Mock).mockImplementation(() => 'development');
189196
init({
190197
enableNative: true,
191198
});
@@ -618,7 +625,73 @@ describe('Tests the SDK functionality', () => {
618625
const actualIntegrations = actualOptions.integrations;
619626

620627
expect(actualIntegrations).toEqual(
621-
expect.arrayContaining([expect.objectContaining({ name: 'MockedDefaultReactIntegration' })]),
628+
expect.arrayContaining([
629+
expect.objectContaining({ name: 'InboundFilters' }),
630+
expect.objectContaining({ name: 'FunctionToString' }),
631+
expect.objectContaining({ name: 'Breadcrumbs' }),
632+
expect.objectContaining({ name: 'Dedupe' }),
633+
expect.objectContaining({ name: 'HttpContext' }),
634+
]),
635+
);
636+
});
637+
638+
it('adds all platform default integrations', () => {
639+
init({});
640+
641+
const actualOptions = mockedInitAndBind.mock.calls[0][secondArg] as ReactNativeClientOptions;
642+
const actualIntegrations = actualOptions.integrations;
643+
644+
expect(actualIntegrations).toEqual(
645+
expect.arrayContaining([
646+
expect.objectContaining({ name: 'Release' }),
647+
expect.objectContaining({ name: 'EventOrigin' }),
648+
expect.objectContaining({ name: 'SdkInfo' }),
649+
expect.objectContaining({ name: 'ReactNativeInfo' }),
650+
]),
651+
);
652+
});
653+
654+
it('adds web platform specific default integrations', () => {
655+
(notWeb as jest.Mock).mockImplementation(() => false);
656+
init({});
657+
658+
const actualOptions = mockedInitAndBind.mock.calls[0][secondArg] as ReactNativeClientOptions;
659+
const actualIntegrations = actualOptions.integrations;
660+
661+
expect(actualIntegrations).toEqual(
662+
expect.arrayContaining([
663+
expect.objectContaining({ name: 'TryCatch' }),
664+
expect.objectContaining({ name: 'GlobalHandlers' }),
665+
expect.objectContaining({ name: 'LinkedErrors' }),
666+
]),
667+
);
668+
});
669+
670+
it('does not add native integrations if native disabled', () => {
671+
(NATIVE.isNativeAvailable as jest.Mock).mockImplementation(() => false);
672+
init({
673+
attachScreenshot: true,
674+
attachViewHierarchy: true,
675+
_experiments: {
676+
profilesSampleRate: 0.7,
677+
},
678+
});
679+
680+
const actualOptions = mockedInitAndBind.mock.calls[0][secondArg] as ReactNativeClientOptions;
681+
const actualIntegrations = actualOptions.integrations;
682+
683+
expect(actualIntegrations).toEqual(
684+
expect.not.arrayContaining([expect.objectContaining({ name: 'DeviceContext' })]),
685+
);
686+
expect(actualIntegrations).toEqual(
687+
expect.not.arrayContaining([expect.objectContaining({ name: 'ModulesLoader' })]),
688+
);
689+
expect(actualIntegrations).toEqual(expect.not.arrayContaining([expect.objectContaining({ name: 'Screenshot' })]));
690+
expect(actualIntegrations).toEqual(
691+
expect.not.arrayContaining([expect.objectContaining({ name: 'ViewHierarchy' })]),
692+
);
693+
expect(actualIntegrations).toEqual(
694+
expect.not.arrayContaining([expect.objectContaining({ name: 'HermesProfiling' })]),
622695
);
623696
});
624697
});

0 commit comments

Comments
 (0)