Skip to content

Commit 936481d

Browse files
feat(breadcrumbs): Add environment aware defaults for RN/Expo/Web (#4886)
1 parent 78c414f commit 936481d

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
- `Sentry.captureUserFeedback` removed, use `Sentry.captureFeedback` instead ([#4855](https://github.com/getsentry/sentry-react-native/pull/4855))
4444
- Use global `TextEncoder` (available with Hermes in React Native 0.74 or higher) to greatly improve envelope encoding performance. ([#4874](https://github.com/getsentry/sentry-react-native/pull/4874))
45+
- `breadcrumbsIntegration` disables React Native incompatible options automatically ([#4886](https://github.com/getsentry/sentry-react-native/pull/4886))
4546

4647
### Changes
4748

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { breadcrumbsIntegration as browserBreadcrumbsIntegration } from '@sentry/browser';
2+
import type { Integration } from '@sentry/core';
3+
import { isWeb } from '../utils/environment';
4+
5+
interface BreadcrumbsOptions {
6+
/**
7+
* Log calls to console.log, console.debug, and so on.
8+
*/
9+
console: boolean;
10+
11+
/**
12+
* Log all click and keypress events.
13+
*
14+
* Only available on web. In React Native this is a no-op.
15+
*/
16+
dom:
17+
| boolean
18+
| {
19+
serializeAttribute?: string | string[];
20+
maxStringLength?: number;
21+
};
22+
23+
/**
24+
* Log HTTP requests done with the global Fetch API.
25+
*
26+
* Disabled by default in React Native because fetch is built on XMLHttpRequest.
27+
* Enabled by default on web.
28+
*
29+
* Setting `fetch: true` and `xhr: true` will cause duplicates in React Native.
30+
*/
31+
fetch: boolean;
32+
33+
/**
34+
* Log calls to history.pushState and related APIs.
35+
*
36+
* Only available on web. In React Native this is a no-op.
37+
*/
38+
history: boolean;
39+
40+
/**
41+
* Log whenever we send an event to the server.
42+
*/
43+
sentry: boolean;
44+
45+
/**
46+
* Log HTTP requests done with the XHR API.
47+
*
48+
* Because React Native global fetch is built on XMLHttpRequest,
49+
* this will also log `fetch` network requests.
50+
*
51+
* Setting `fetch: true` and `xhr: true` will cause duplicates in React Native.
52+
*/
53+
xhr: boolean;
54+
}
55+
56+
export const breadcrumbsIntegration = (options: Partial<BreadcrumbsOptions> = {}): Integration => {
57+
const _options: BreadcrumbsOptions = {
58+
// FIXME: In mobile environment XHR is implemented by native APIs, which are instrumented by the Native SDK.
59+
// This will cause duplicates in React Native. On iOS `NSURLSession` is instrumented by default. On Android
60+
// `OkHttp` is only instrumented by SAGP.
61+
xhr: true,
62+
console: true,
63+
sentry: true,
64+
...options,
65+
fetch: options.fetch ?? (isWeb() ? true : false),
66+
dom: isWeb() ? options.dom ?? true : false,
67+
history: isWeb() ? options.history ?? true : false,
68+
};
69+
70+
// Historically we had very little issue using the browser breadcrumbs integration
71+
// and thus we don't cherry pick the implementation like for example the Sentry Deno SDK does.
72+
// https://github.com/getsentry/sentry-javascript/blob/d007407c2e51d93d6d3933f9dea1e03ff3f4a4ab/packages/deno/src/integrations/breadcrumbs.ts#L34
73+
return browserBreadcrumbsIntegration(_options);
74+
};

packages/core/src/js/integrations/exports.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ export { userInteractionIntegration } from '../tracing/integrations/userInteract
2222
export { createReactNativeRewriteFrames } from './rewriteframes';
2323
export { appRegistryIntegration } from './appRegistry';
2424
export { timeToDisplayIntegration } from '../tracing/integrations/timeToDisplayIntegration';
25+
export { breadcrumbsIntegration } from './breadcrumbs';
2526

2627
export {
27-
breadcrumbsIntegration,
2828
browserApiErrorsIntegration,
2929
dedupeIntegration,
3030
functionToStringIntegration,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { breadcrumbsIntegration as browserBreadcrumbsIntegration } from '@sentry/browser';
2+
import { breadcrumbsIntegration } from '../../src/js/integrations/breadcrumbs';
3+
import * as environment from '../../src/js/utils/environment';
4+
5+
jest.mock('@sentry/browser', () => ({
6+
breadcrumbsIntegration: jest.fn(),
7+
}));
8+
9+
describe('breadcrumbsIntegration', () => {
10+
afterEach(() => {
11+
jest.resetAllMocks();
12+
});
13+
14+
it('passes React Native defaults to browserBreadcrumbsIntegration', () => {
15+
jest.spyOn(environment, 'isWeb').mockReturnValue(false);
16+
17+
breadcrumbsIntegration();
18+
19+
expect(browserBreadcrumbsIntegration).toHaveBeenCalledWith({
20+
xhr: true,
21+
console: true,
22+
sentry: true,
23+
dom: false, // DOM is not available in React Native
24+
fetch: false, // fetch is built on XMLHttpRequest in React Native
25+
history: false, // history is not available in React Native
26+
});
27+
});
28+
29+
it('passes web defaults to browserBreadcrumbsIntegration when isWeb returns true', () => {
30+
jest.spyOn(environment, 'isWeb').mockReturnValue(true);
31+
32+
breadcrumbsIntegration();
33+
34+
expect(browserBreadcrumbsIntegration).toHaveBeenCalledWith({
35+
// Everything is enabled by default on web
36+
xhr: true,
37+
console: true,
38+
sentry: true,
39+
dom: true,
40+
fetch: true,
41+
history: true,
42+
});
43+
});
44+
45+
it('respects custom options React Native options', () => {
46+
jest.spyOn(environment, 'isWeb').mockReturnValue(false);
47+
48+
breadcrumbsIntegration({
49+
xhr: false,
50+
console: false,
51+
sentry: false,
52+
dom: {}, // Integration should not let user enable DOM breadcrumbs on React Native
53+
fetch: true, // If user enables it, we should log fetch requests
54+
history: true, // Integration should not let user enable history breadcrumbs on React Native
55+
});
56+
57+
expect(browserBreadcrumbsIntegration).toHaveBeenCalledWith({
58+
xhr: false,
59+
console: false,
60+
sentry: false,
61+
dom: false,
62+
fetch: true,
63+
history: false,
64+
});
65+
});
66+
67+
it('respects custom options when isWeb returns true', () => {
68+
jest.spyOn(environment, 'isWeb').mockReturnValue(true);
69+
70+
breadcrumbsIntegration({
71+
// Everything can be disabled on web
72+
xhr: false,
73+
console: false,
74+
sentry: false,
75+
dom: false,
76+
fetch: false,
77+
history: false,
78+
});
79+
80+
expect(browserBreadcrumbsIntegration).toHaveBeenCalledWith({
81+
xhr: false,
82+
console: false,
83+
sentry: false,
84+
dom: false,
85+
fetch: false,
86+
history: false,
87+
});
88+
});
89+
});

0 commit comments

Comments
 (0)