Skip to content

Commit e171ea3

Browse files
feat(replay): Allow using browserReplayIntegration without isWeb guard (#4858)
1 parent c6d4d0a commit e171ea3

File tree

6 files changed

+136
-14
lines changed

6 files changed

+136
-14
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
99
## Unreleased
1010

11+
### Changes
12+
13+
- Use `Replay` interface for `browserReplayIntegration` return type ([#4858](https://github.com/getsentry/sentry-react-native/pull/4858))
14+
- Allow using `browserReplayIntegration` without `isWeb` guard ([#4858](https://github.com/getsentry/sentry-react-native/pull/4858))
15+
- The integration returns noop in non-browser environments
16+
1117
### Dependencies
1218

1319
- Bump JavaScript SDK from v9.12.0 to v9.22.0 ([#4860](https://github.com/getsentry/sentry-react-native/pull/4860))
@@ -65,7 +71,7 @@ Version 7 of the SDK is compatible with Sentry self-hosted versions 24.4.2 or hi
6571

6672
- Fork `scope` if custom scope is passed to `startSpanManual` or `startSpan`
6773
- On React Native Web, `browserSessionIntegration` is added when `enableAutoSessionTracking` is set to `True` ([#4732](https://github.com/getsentry/sentry-react-native/pull/4732))
68-
Change `Cold/Warm App Start` span description to `Cold/Warm Start` ([#4636](https://github.com/getsentry/sentry-react-native/pull/4636))
74+
- Change `Cold/Warm App Start` span description to `Cold/Warm Start` ([#4636](https://github.com/getsentry/sentry-react-native/pull/4636))
6975

7076
### Dependencies
7177

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,49 @@
11
import { replayIntegration } from '@sentry/react';
22

3-
const browserReplayIntegration = (
4-
options: Parameters<typeof replayIntegration>[0] = {},
5-
): ReturnType<typeof replayIntegration> => {
3+
import { notWeb } from '../utils/environment';
4+
import type { Replay } from './replayInterface';
5+
6+
/**
7+
* ReplayConfiguration for browser replay integration.
8+
*
9+
* See the [Configuration documentation](https://docs.sentry.io/platforms/javascript/session-replay/configuration/) for more information.
10+
*/
11+
type ReplayConfiguration = Parameters<typeof replayIntegration>[0];
12+
13+
// https://github.com/getsentry/sentry-javascript/blob/e00cb04f1bbf494067cd8475d392266ba296987a/packages/replay-internal/src/integration.ts#L109
14+
const INTEGRATION_NAME = 'Replay';
15+
16+
/**
17+
* Browser Replay integration for React Native.
18+
*
19+
* See the [Browser Replay documentation](https://docs.sentry.io/platforms/javascript/session-replay/) for more information.
20+
*/
21+
const browserReplayIntegration = (options: ReplayConfiguration = {}): Replay => {
22+
if (notWeb()) {
23+
// This is required because `replayIntegration` browser check doesn't
24+
// work for React Native.
25+
return browserReplayIntegrationNoop();
26+
}
27+
628
return replayIntegration({
729
...options,
830
mask: ['.sentry-react-native-mask', ...(options.mask || [])],
931
unmask: ['.sentry-react-native-unmask:not(.sentry-react-native-mask *) > *', ...(options.unmask || [])],
1032
});
1133
};
1234

35+
const browserReplayIntegrationNoop = (): Replay => {
36+
return {
37+
name: INTEGRATION_NAME,
38+
// eslint-disable-next-line @typescript-eslint/no-empty-function
39+
start: () => {},
40+
// eslint-disable-next-line @typescript-eslint/no-empty-function
41+
startBuffering: () => {},
42+
stop: () => Promise.resolve(),
43+
flush: () => Promise.resolve(),
44+
getReplayId: () => undefined,
45+
getRecordingMode: () => undefined,
46+
};
47+
};
48+
1349
export { browserReplayIntegration };

packages/core/src/js/replay/mobilereplay.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,6 @@ export const mobileReplayIntegration = (initOptions: MobileReplayOptions = defau
149149
// https://github.com/getsentry/sentry-javascript/blob/develop/packages/replay-internal/src/integration.ts#L45
150150
return {
151151
name: MOBILE_REPLAY_INTEGRATION_NAME,
152-
setupOnce() {
153-
/* Noop */
154-
},
155152
setup,
156153
processEvent,
157154
options: options,
@@ -161,9 +158,6 @@ export const mobileReplayIntegration = (initOptions: MobileReplayOptions = defau
161158
const mobileReplayIntegrationNoop = (): MobileReplayIntegration => {
162159
return {
163160
name: MOBILE_REPLAY_INTEGRATION_NAME,
164-
setupOnce() {
165-
/* Noop */
166-
},
167161
options: defaultOptions,
168162
};
169163
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { Integration, ReplayRecordingMode } from '@sentry/core';
2+
3+
// Based on Replay Class https://github.com/getsentry/sentry-javascript/blob/e00cb04f1bbf494067cd8475d392266ba296987a/packages/replay-internal/src/integration.ts#L50
4+
5+
/**
6+
* Common interface for React Native Replay integrations.
7+
*
8+
* Both browser and mobile replay integrations should implement this interface
9+
* to allow user manually control the replay.
10+
*/
11+
export interface Replay extends Integration {
12+
/**
13+
* Start a replay regardless of sampling rate. Calling this will always
14+
* create a new session. Will log a message if replay is already in progress.
15+
*
16+
* Creates or loads a session, attaches listeners to varying events (DOM,
17+
* PerformanceObserver, Recording, Sentry SDK, etc)
18+
*/
19+
start(): void;
20+
21+
/**
22+
* Start replay buffering. Buffers until `flush()` is called or, if
23+
* `replaysOnErrorSampleRate` > 0, until an error occurs.
24+
*/
25+
startBuffering(): void;
26+
27+
/**
28+
* Currently, this needs to be manually called (e.g. for tests). Sentry SDK
29+
* does not support a teardown
30+
*/
31+
stop(): Promise<void>;
32+
33+
/**
34+
* If not in "session" recording mode, flush event buffer which will create a new replay.
35+
* If replay is not enabled, a new session replay is started.
36+
* Unless `continueRecording` is false, the replay will continue to record and
37+
* behave as a "session"-based replay.
38+
*
39+
* Otherwise, queue up a flush.
40+
*/
41+
flush(options?: { continueRecording?: boolean }): Promise<void>;
42+
43+
/**
44+
* Get the current session ID.
45+
*/
46+
getReplayId(): string | undefined;
47+
48+
/**
49+
* Get the current recording mode. This can be either `session` or `buffer`.
50+
*
51+
* `session`: Recording the whole session, sending it continuously
52+
* `buffer`: Always keeping the last 60s of recording, requires:
53+
* - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
54+
* - or calling `flush()` to send the replay
55+
*/
56+
getRecordingMode(): ReplayRecordingMode | undefined;
57+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, test } from '@jest/globals';
2+
import * as SentryReact from '@sentry/react';
3+
import { spyOn } from 'jest-mock';
4+
5+
import { browserReplayIntegration } from '../../src/js/replay/browserReplay';
6+
import * as environment from '../../src/js/utils/environment';
7+
8+
describe('Browser Replay', () => {
9+
afterEach(() => {
10+
jest.clearAllMocks();
11+
});
12+
13+
test('should not call replayIntegration if not web', () => {
14+
spyOn(environment, 'notWeb').mockReturnValue(true);
15+
spyOn(SentryReact, 'replayIntegration').mockImplementation(() => {
16+
throw new Error('replayIntegration should not be called');
17+
});
18+
19+
const integration = browserReplayIntegration();
20+
21+
expect(integration).toBeDefined();
22+
expect(SentryReact.replayIntegration).not.toHaveBeenCalled();
23+
});
24+
});

samples/expo/app/_layout.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import * as Sentry from '@sentry/react-native';
1010
import { ErrorEvent } from '@sentry/core';
1111
import { isExpoGo } from '../utils/isExpoGo';
1212
import { LogBox } from 'react-native';
13-
import { isWeb } from '../utils/isWeb';
1413
import * as ImagePicker from 'expo-image-picker';
1514

1615
export {
@@ -58,13 +57,19 @@ Sentry.init({
5857
}),
5958
navigationIntegration,
6059
Sentry.reactNativeTracingIntegration(),
60+
Sentry.mobileReplayIntegration({
61+
maskAllImages: true,
62+
maskAllText: true,
63+
maskAllVectors: true,
64+
}),
65+
Sentry.browserReplayIntegration({
66+
maskAllInputs: true,
67+
maskAllText: true,
68+
}),
6169
Sentry.feedbackIntegration({
6270
imagePicker: ImagePicker,
6371
}),
6472
);
65-
if (isWeb()) {
66-
integrations.push(Sentry.browserReplayIntegration());
67-
}
6873
return integrations.filter(i => i.name !== 'Dedupe');
6974
},
7075
enableAutoSessionTracking: true,

0 commit comments

Comments
 (0)