Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

## Unreleased

### Changes

- Use `Replay` interface for `browserReplayIntegration` return type ([#4858](https://github.com/getsentry/sentry-react-native/pull/4858))
- Allow using `browserReplayIntegration` without `isWeb` guard ([#4858](https://github.com/getsentry/sentry-react-native/pull/4858))
- The integration returns noop in non-browser environments

### Dependencies

- Bump Android SDK from v8.11.1 to v8.12.0 ([#4847](https://github.com/getsentry/sentry-react-native/pull/4847))
Expand Down Expand Up @@ -59,7 +65,7 @@ Version 7 of the SDK is compatible with Sentry self-hosted versions 24.4.2 or hi

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

### Dependencies

Expand Down
42 changes: 39 additions & 3 deletions packages/core/src/js/replay/browserReplay.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
import { replayIntegration } from '@sentry/react';

const browserReplayIntegration = (
options: Parameters<typeof replayIntegration>[0] = {},
): ReturnType<typeof replayIntegration> => {
import { notWeb } from '../utils/environment';
import type { Replay } from './replayInterface';

/**
* ReplayConfiguration for browser replay integration.
*
* See the [Configuration documentation](https://docs.sentry.io/platforms/javascript/session-replay/configuration/) for more information.
*/
type ReplayConfiguration = Parameters<typeof replayIntegration>[0];

// https://github.com/getsentry/sentry-javascript/blob/e00cb04f1bbf494067cd8475d392266ba296987a/packages/replay-internal/src/integration.ts#L109
const INTEGRATION_NAME = 'Replay';

/**
* Browser Replay integration for React Native.
*
* See the [Browser Replay documentation](https://docs.sentry.io/platforms/javascript/session-replay/) for more information.
*/
const browserReplayIntegration = (options: ReplayConfiguration = {}): Replay => {
if (notWeb()) {
// This is required because because `replayIntegration` browser check doesn't
Comment thread
krystofwoldrich marked this conversation as resolved.
Outdated
// work for React Native.
return browserReplayIntegrationNoop();
}

return replayIntegration({
...options,
mask: ['.sentry-react-native-mask', ...(options.mask || [])],
unmask: ['.sentry-react-native-unmask:not(.sentry-react-native-mask *) > *', ...(options.unmask || [])],
});
};

const browserReplayIntegrationNoop = (): Replay => {
return {
name: INTEGRATION_NAME,
// eslint-disable-next-line @typescript-eslint/no-empty-function
start: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
startBuffering: () => {},
stop: () => Promise.resolve(),
flush: () => Promise.resolve(),
getReplayId: () => undefined,
getRecordingMode: () => undefined,
};
};

export { browserReplayIntegration };
6 changes: 0 additions & 6 deletions packages/core/src/js/replay/mobilereplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,6 @@ export const mobileReplayIntegration = (initOptions: MobileReplayOptions = defau
// https://github.com/getsentry/sentry-javascript/blob/develop/packages/replay-internal/src/integration.ts#L45
return {
name: MOBILE_REPLAY_INTEGRATION_NAME,
setupOnce() {
/* Noop */
},
setup,
processEvent,
options: options,
Expand All @@ -162,9 +159,6 @@ export const mobileReplayIntegration = (initOptions: MobileReplayOptions = defau
const mobileReplayIntegrationNoop = (): MobileReplayIntegration => {
return {
name: MOBILE_REPLAY_INTEGRATION_NAME,
setupOnce() {
/* Noop */
},
options: defaultOptions,
};
};
57 changes: 57 additions & 0 deletions packages/core/src/js/replay/replayInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Integration, ReplayRecordingMode } from '@sentry/core';

// Based on Replay Class https://github.com/getsentry/sentry-javascript/blob/e00cb04f1bbf494067cd8475d392266ba296987a/packages/replay-internal/src/integration.ts#L50

/**
* Common interface for React Native Replay integrations.
*
* Both browser and mobile replay integrations should implement this interface
* to allow user manually control the replay.
*/
export interface Replay extends Integration {
/**
* Start a replay regardless of sampling rate. Calling this will always
* create a new session. Will log a message if replay is already in progress.
*
* Creates or loads a session, attaches listeners to varying events (DOM,
* PerformanceObserver, Recording, Sentry SDK, etc)
*/
start(): void;

/**
* Start replay buffering. Buffers until `flush()` is called or, if
* `replaysOnErrorSampleRate` > 0, until an error occurs.
*/
startBuffering(): void;

/**
* Currently, this needs to be manually called (e.g. for tests). Sentry SDK
* does not support a teardown
*/
stop(): Promise<void>;

/**
* If not in "session" recording mode, flush event buffer which will create a new replay.
* If replay is not enabled, a new session replay is started.
* Unless `continueRecording` is false, the replay will continue to record and
* behave as a "session"-based replay.
*
* Otherwise, queue up a flush.
*/
flush(options?: { continueRecording?: boolean }): Promise<void>;

/**
* Get the current session ID.
*/
getReplayId(): string | undefined;

/**
* Get the current recording mode. This can be either `session` or `buffer`.
*
* `session`: Recording the whole session, sending it continuously
* `buffer`: Always keeping the last 60s of recording, requires:
* - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
* - or calling `flush()` to send the replay
*/
getRecordingMode(): ReplayRecordingMode | undefined;
}
24 changes: 24 additions & 0 deletions packages/core/test/replay/browserReplay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, test } from '@jest/globals';
import * as SentryReact from '@sentry/react';
import { spyOn } from 'jest-mock';

import { browserReplayIntegration } from '../../src/js/replay/browserReplay';
import * as environment from '../../src/js/utils/environment';

describe('Browser Replay', () => {
afterEach(() => {
jest.clearAllMocks();
});

test('should not call replayIntegration if not web', () => {
spyOn(environment, 'notWeb').mockReturnValue(true);
spyOn(SentryReact, 'replayIntegration').mockImplementation(() => {
throw new Error('replayIntegration should not be called');
});

const integration = browserReplayIntegration();

expect(integration).toBeDefined();
expect(SentryReact.replayIntegration).not.toHaveBeenCalled();
});
});
13 changes: 9 additions & 4 deletions samples/expo/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import * as Sentry from '@sentry/react-native';
import { ErrorEvent } from '@sentry/core';
import { isExpoGo } from '../utils/isExpoGo';
import { LogBox } from 'react-native';
import { isWeb } from '../utils/isWeb';
import * as ImagePicker from 'expo-image-picker';

export {
Expand Down Expand Up @@ -58,13 +57,19 @@ Sentry.init({
}),
navigationIntegration,
Sentry.reactNativeTracingIntegration(),
Sentry.mobileReplayIntegration({
maskAllImages: true,
maskAllText: true,
maskAllVectors: true,
}),
Sentry.browserReplayIntegration({
maskAllInputs: true,
maskAllText: true,
}),
Sentry.feedbackIntegration({
imagePicker: ImagePicker,
}),
);
if (isWeb()) {
integrations.push(Sentry.browserReplayIntegration());
}
return integrations.filter(i => i.name !== 'Dedupe');
},
enableAutoSessionTracking: true,
Expand Down
Loading