From 73adf8862799637a88c9aadf31b09aa71e7d709c Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 17 Dec 2024 11:45:06 +0100 Subject: [PATCH 01/10] feat: Session Replay is GA --- CHANGELOG.md | 21 ++++++++++++++ .../io/sentry/react/RNSentryModuleImpl.java | 28 +++++++++--------- packages/core/ios/RNSentryReplay.mm | 25 +++++----------- packages/core/src/js/integrations/default.ts | 29 ++++++++++--------- packages/core/src/js/options.ts | 24 +++++++++++---- samples/react-native/src/App.tsx | 6 ++-- 6 files changed, 81 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7275947b..4c93fe2964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,26 @@ ### Features +- Mobile Session Replay is now generally available and ready for production use ([#4383](https://github.com/getsentry/sentry-react-native/pull/4383)) + + To learn about privacy, custom masking or performance overhead visit [the documentation](https://docs.sentry.io/platforms/react-native/session-replay/). + + ```js + import * as Sentry from '@sentry/react-native'; + + Sentry.init({ + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 1.0, + integrations: [ + Sentry.mobileReplayIntegration({ + maskAllImages: true, + maskAllVectors: true, + maskAllText: true, + }), + ], + }); + ``` + - Adds new `captureFeedback` and deprecates the `captureUserFeedback` API ([#4320](https://github.com/getsentry/sentry-react-native/pull/4320)) ```jsx @@ -47,6 +67,7 @@ ### Changes - Falsy values of `options.environment` (empty string, undefined...) default to `production` +- Deprecated `_experiments.replaysSessionSampleRate` and `_experiments.replaysOnErrorSampleRate` use `replaysSessionSampleRate` and `replaysOnErrorSampleRate` ([#4383](https://github.com/getsentry/sentry-react-native/pull/4383)) ### Dependencies diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 6fd2247002..3ae68e7571 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -276,8 +276,10 @@ protected void getSentryAndroidOptions( options.setSpotlightConnectionUrl(rnOptions.getString("spotlight")); } } - if (rnOptions.hasKey("_experiments")) { - options.getExperimental().setSessionReplay(getReplayOptions(rnOptions)); + + SentryReplayOptions replayOptions = getReplayOptions(rnOptions); + options.setSessionReplay(replayOptions); + if (isReplayEnabled(replayOptions)) { options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); } @@ -329,26 +331,26 @@ protected void getSentryAndroidOptions( } } + private boolean isReplayEnabled(SentryReplayOptions replayOptions) { + return replayOptions.getSessionSampleRate() != null + || replayOptions.getOnErrorSampleRate() != null; + } + private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { @NotNull final SentryReplayOptions androidReplayOptions = new SentryReplayOptions(false); - @Nullable final ReadableMap rnExperimentsOptions = rnOptions.getMap("_experiments"); - if (rnExperimentsOptions == null) { - return androidReplayOptions; - } - - if (!(rnExperimentsOptions.hasKey("replaysSessionSampleRate") - || rnExperimentsOptions.hasKey("replaysOnErrorSampleRate"))) { + if (!(rnOptions.hasKey("replaysSessionSampleRate") + || rnOptions.hasKey("replaysOnErrorSampleRate"))) { return androidReplayOptions; } androidReplayOptions.setSessionSampleRate( - rnExperimentsOptions.hasKey("replaysSessionSampleRate") - ? rnExperimentsOptions.getDouble("replaysSessionSampleRate") + rnOptions.hasKey("replaysSessionSampleRate") + ? rnOptions.getDouble("replaysSessionSampleRate") : null); androidReplayOptions.setOnErrorSampleRate( - rnExperimentsOptions.hasKey("replaysOnErrorSampleRate") - ? rnExperimentsOptions.getDouble("replaysOnErrorSampleRate") + rnOptions.hasKey("replaysOnErrorSampleRate") + ? rnOptions.getDouble("replaysOnErrorSampleRate") : null); if (!rnOptions.hasKey("mobileReplayOptions")) { diff --git a/packages/core/ios/RNSentryReplay.mm b/packages/core/ios/RNSentryReplay.mm index 8c941741c4..2b7a992a3a 100644 --- a/packages/core/ios/RNSentryReplay.mm +++ b/packages/core/ios/RNSentryReplay.mm @@ -12,15 +12,8 @@ @implementation RNSentryReplay { + (void)updateOptions:(NSMutableDictionary *)options { - NSDictionary *experiments = options[@"_experiments"]; - [options removeObjectForKey:@"_experiments"]; - if (experiments == nil) { - NSLog(@"Session replay disabled via configuration"); - return; - } - - if (experiments[@"replaysSessionSampleRate"] == nil - && experiments[@"replaysOnErrorSampleRate"] == nil) { + if (options[@"replaysSessionSampleRate"] == nil + && options[@"replaysOnErrorSampleRate"] == nil) { NSLog(@"Session replay disabled via configuration"); return; } @@ -29,15 +22,13 @@ + (void)updateOptions:(NSMutableDictionary *)options NSDictionary *replayOptions = options[@"mobileReplayOptions"] ?: @{}; [options setValue:@{ - @"sessionReplay" : @ { - @"sessionSampleRate" : experiments[@"replaysSessionSampleRate"] ?: [NSNull null], - @"errorSampleRate" : experiments[@"replaysOnErrorSampleRate"] ?: [NSNull null], - @"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null], - @"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null], - @"maskedViewClasses" : [RNSentryReplay getReplayRNRedactClasses:replayOptions], - } + @"sessionSampleRate" : options[@"replaysSessionSampleRate"] ?: [NSNull null], + @"errorSampleRate" : options[@"replaysOnErrorSampleRate"] ?: [NSNull null], + @"maskAllImages" : replayOptions[@"maskAllImages"] ?: [NSNull null], + @"maskAllText" : replayOptions[@"maskAllText"] ?: [NSNull null], + @"maskedViewClasses" : [RNSentryReplay getReplayRNRedactClasses:replayOptions], } - forKey:@"experimental"]; + forKey:@"sessionReplay"]; } + (NSArray *_Nonnull)getReplayRNRedactClasses:(NSDictionary *_Nullable)replayOptions diff --git a/packages/core/src/js/integrations/default.ts b/packages/core/src/js/integrations/default.ts index 0f32ba6b16..64f7dc08ca 100644 --- a/packages/core/src/js/integrations/default.ts +++ b/packages/core/src/js/integrations/default.ts @@ -1,10 +1,9 @@ /* eslint-disable complexity */ -import type { BrowserOptions } from '@sentry/react'; import type { Integration } from '@sentry/types'; import type { ReactNativeClientOptions } from '../options'; import { reactNativeTracingIntegration } from '../tracing'; -import { isExpoGo, isWeb, notWeb } from '../utils/environment'; +import { isExpoGo, notWeb } from '../utils/environment'; import { appStartIntegration, breadcrumbsIntegration, @@ -127,18 +126,22 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ integrations.push(spotlightIntegration({ sidecarUrl })); } - if ( + const hasReplayOptions = + typeof options.replaysOnErrorSampleRate === 'number' || typeof options.replaysSessionSampleRate === 'number'; + const hasExperimentsReplayOptions = (options._experiments && typeof options._experiments.replaysOnErrorSampleRate === 'number') || - (options._experiments && typeof options._experiments.replaysSessionSampleRate === 'number') - ) { - if (isWeb()) { - // We can't create and add browserReplayIntegration as it overrides the users supplied one - // The browser replay integration works differently than the rest of default integrations - (options as BrowserOptions).replaysOnErrorSampleRate = options._experiments.replaysOnErrorSampleRate; - (options as BrowserOptions).replaysSessionSampleRate = options._experiments.replaysSessionSampleRate; - } else { - integrations.push(mobileReplayIntegration()); - } + (options._experiments && typeof options._experiments.replaysSessionSampleRate === 'number'); + + if (!hasReplayOptions && hasExperimentsReplayOptions) { + // Remove in the next major version (v7) + options.replaysOnErrorSampleRate = options._experiments.replaysOnErrorSampleRate; + options.replaysSessionSampleRate = options._experiments.replaysSessionSampleRate; + } + + if ((hasReplayOptions || hasExperimentsReplayOptions) && notWeb()) { + // We can't create and add browserReplayIntegration as it overrides the users supplied one + // The browser replay integration works differently than the rest of default integrations + integrations.push(mobileReplayIntegration()); } if (__DEV__ && notWeb()) { diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 4d7f46a92f..57e0ef55a2 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -221,6 +221,19 @@ export interface BaseReactNativeOptions { */ profilesSampleRate?: number; + /** + * The sample rate for session-long replays. + * 1.0 will record all sessions and 0 will record none. + */ + replaysSessionSampleRate?: number; + + /** + * The sample rate for sessions that has had an error occur. + * This is independent of `sessionSampleRate`. + * 1.0 will record all sessions and 0 will record none. + */ + replaysOnErrorSampleRate?: number; + /** * Options which are in beta, or otherwise not guaranteed to be stable. */ @@ -228,15 +241,16 @@ export interface BaseReactNativeOptions { [key: string]: unknown; /** - * The sample rate for session-long replays. - * 1.0 will record all sessions and 0 will record none. + * @deprecated Use `replaysSessionSampleRate` in the options root instead. + * + * This will be removed in the next major version. */ replaysSessionSampleRate?: number; /** - * The sample rate for sessions that has had an error occur. - * This is independent of `sessionSampleRate`. - * 1.0 will record all sessions and 0 will record none. + * @deprecated Use `replaysOnErrorSampleRate` in the options root instead. + * + * This will be removed in the next major version. */ replaysOnErrorSampleRate?: number; }; diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index 04348fa5c6..2cd5511041 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -125,10 +125,8 @@ Sentry.init({ // release: 'myapp@1.2.3+1', // dist: `1`, profilesSampleRate: 1.0, - _experiments: { - replaysSessionSampleRate: 1.0, - replaysOnErrorSampleRate: 1.0, - }, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 1.0, spotlight: true, // This should be disabled when manually initializing the native SDK // Note that options from JS are not passed to the native SDKs when initialized manually From 0ebf9c3c933f6b9436d256a8ad85bd4d2fda263f Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 17 Dec 2024 12:03:14 +0100 Subject: [PATCH 02/10] update cocoa tests --- .../RNSentryReplayOptionsTests.swift | 69 +++++++------------ 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift index 4e8b35c477..e73dc38b8e 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift @@ -10,24 +10,14 @@ final class RNSentryReplayOptions: XCTestCase { XCTAssertEqual(optionsDict.count, 0) } - func testExperimentalOptionsWithoutReplaySampleRatesAreRemoved() { - let optionsDict = (["_experiments": [:]] as NSDictionary).mutableCopy() as! NSMutableDictionary - RNSentryReplay.updateOptions(optionsDict) - - XCTAssertEqual(optionsDict.count, 0) - } - func testReplayOptionsDictContainsAllOptionsKeysWhenSessionSampleRateUsed() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ - "replaysSessionSampleRate": 0.75 - ] + "replaysSessionSampleRate": 0.75 ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) - let experimental = optionsDict["experimental"] as! [String: Any] - let sessionReplay = experimental["sessionReplay"] as! [String: Any] + let sessionReplay = optionsDict["sessionReplay"] as! [String: Any] assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay) } @@ -35,14 +25,11 @@ final class RNSentryReplayOptions: XCTestCase { func testReplayOptionsDictContainsAllOptionsKeysWhenErrorSampleRateUsed() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ - "replaysOnErrorSampleRate": 0.75 - ] + "replaysOnErrorSampleRate": 0.75 ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) - let experimental = optionsDict["experimental"] as! [String: Any] - let sessionReplay = experimental["sessionReplay"] as! [String: Any] + let sessionReplay = optionsDict["sessionReplay"] as! [String: Any] assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay) } @@ -50,15 +37,12 @@ final class RNSentryReplayOptions: XCTestCase { func testReplayOptionsDictContainsAllOptionsKeysWhenErrorAndSessionSampleRatesUsed() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ - "replaysOnErrorSampleRate": 0.75, - "replaysSessionSampleRate": 0.75 - ] + "replaysOnErrorSampleRate": 0.75, + "replaysSessionSampleRate": 0.75 ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) - let experimental = optionsDict["experimental"] as! [String: Any] - let sessionReplay = experimental["sessionReplay"] as! [String: Any] + let sessionReplay = optionsDict["sessionReplay"] as! [String: Any] assertAllDefaultReplayOptionsAreNotNil(replayOptions: sessionReplay) } @@ -75,29 +59,29 @@ final class RNSentryReplayOptions: XCTestCase { func testSessionSampleRate() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ "replaysSessionSampleRate": 0.75 ] + "replaysSessionSampleRate": 0.75 ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) let actualOptions = try! Options(dict: optionsDict as! [String: Any]) - XCTAssertEqual(actualOptions.experimental.sessionReplay.sessionSampleRate, 0.75) + XCTAssertEqual(actualOptions.sessionReplay.sessionSampleRate, 0.75) } func testOnErrorSampleRate() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ "replaysOnErrorSampleRate": 0.75 ] + "replaysOnErrorSampleRate": 0.75 ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) let actualOptions = try! Options(dict: optionsDict as! [String: Any]) - XCTAssertEqual(actualOptions.experimental.sessionReplay.onErrorSampleRate, 0.75) + XCTAssertEqual(actualOptions.sessionReplay.onErrorSampleRate, 0.75) } func testMaskAllVectors() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "replaysOnErrorSampleRate": 0.75, "mobileReplayOptions": [ "maskAllVectors": true ] ] as NSDictionary).mutableCopy() as! NSMutableDictionary @@ -105,8 +89,7 @@ final class RNSentryReplayOptions: XCTestCase { XCTAssertEqual(optionsDict.count, 3) - let experimental = optionsDict["experimental"] as! [String: Any] - let sessionReplay = experimental["sessionReplay"] as! [String: Any] + let sessionReplay = optionsDict["sessionReplay"] as! [String: Any] let maskedViewClasses = sessionReplay["maskedViewClasses"] as! [String] XCTAssertTrue(maskedViewClasses.contains("RNSVGSvgView")) @@ -115,7 +98,7 @@ final class RNSentryReplayOptions: XCTestCase { func testMaskAllImages() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "replaysOnErrorSampleRate": 0.75, "mobileReplayOptions": [ "maskAllImages": true ] ] as NSDictionary).mutableCopy() as! NSMutableDictionary @@ -123,14 +106,14 @@ final class RNSentryReplayOptions: XCTestCase { let actualOptions = try! Options(dict: optionsDict as! [String: Any]) - XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllImages, true) - assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTImageView") + XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, true) + assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTImageView") } func testMaskAllImagesFalse() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "replaysOnErrorSampleRate": 0.75, "mobileReplayOptions": [ "maskAllImages": false ] ] as NSDictionary).mutableCopy() as! NSMutableDictionary @@ -138,14 +121,14 @@ final class RNSentryReplayOptions: XCTestCase { let actualOptions = try! Options(dict: optionsDict as! [String: Any]) - XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllImages, false) - XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0) + XCTAssertEqual(actualOptions.sessionReplay.maskAllImages, false) + XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0) } func testMaskAllText() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "replaysOnErrorSampleRate": 0.75, "mobileReplayOptions": [ "maskAllText": true ] ] as NSDictionary).mutableCopy() as! NSMutableDictionary @@ -153,9 +136,9 @@ final class RNSentryReplayOptions: XCTestCase { let actualOptions = try! Options(dict: optionsDict as! [String: Any]) - XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllText, true) - assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTTextView") - assertContainsClass(classArray: actualOptions.experimental.sessionReplay.maskedViewClasses, stringClass: "RCTParagraphComponentView") + XCTAssertEqual(actualOptions.sessionReplay.maskAllText, true) + assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTTextView") + assertContainsClass(classArray: actualOptions.sessionReplay.maskedViewClasses, stringClass: "RCTParagraphComponentView") } func assertContainsClass(classArray: [AnyClass], stringClass: String) { @@ -169,7 +152,7 @@ final class RNSentryReplayOptions: XCTestCase { func testMaskAllTextFalse() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "_experiments": [ "replaysOnErrorSampleRate": 0.75 ], + "replaysOnErrorSampleRate": 0.75, "mobileReplayOptions": [ "maskAllText": false ] ] as NSDictionary).mutableCopy() as! NSMutableDictionary @@ -177,8 +160,8 @@ final class RNSentryReplayOptions: XCTestCase { let actualOptions = try! Options(dict: optionsDict as! [String: Any]) - XCTAssertEqual(actualOptions.experimental.sessionReplay.maskAllText, false) - XCTAssertEqual(actualOptions.experimental.sessionReplay.maskedViewClasses.count, 0) + XCTAssertEqual(actualOptions.sessionReplay.maskAllText, false) + XCTAssertEqual(actualOptions.sessionReplay.maskedViewClasses.count, 0) } } From 87b8ee1961bcfe1f189101c77a9a24ccc34c119b Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 17 Dec 2024 12:10:24 +0100 Subject: [PATCH 03/10] fix changelog pr num --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c93fe2964..d4586f080c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Features -- Mobile Session Replay is now generally available and ready for production use ([#4383](https://github.com/getsentry/sentry-react-native/pull/4383)) +- Mobile Session Replay is now generally available and ready for production use ([#4384](https://github.com/getsentry/sentry-react-native/pull/4384)) To learn about privacy, custom masking or performance overhead visit [the documentation](https://docs.sentry.io/platforms/react-native/session-replay/). @@ -67,7 +67,7 @@ ### Changes - Falsy values of `options.environment` (empty string, undefined...) default to `production` -- Deprecated `_experiments.replaysSessionSampleRate` and `_experiments.replaysOnErrorSampleRate` use `replaysSessionSampleRate` and `replaysOnErrorSampleRate` ([#4383](https://github.com/getsentry/sentry-react-native/pull/4383)) +- Deprecated `_experiments.replaysSessionSampleRate` and `_experiments.replaysOnErrorSampleRate` use `replaysSessionSampleRate` and `replaysOnErrorSampleRate` ([#4384](https://github.com/getsentry/sentry-react-native/pull/4384)) ### Dependencies From 1171bb8038448a50c4a0f849ecfe4ebd7fb285a8 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 17 Dec 2024 12:40:51 +0100 Subject: [PATCH 04/10] add js tests --- packages/core/test/sdk.test.ts | 88 +++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/packages/core/test/sdk.test.ts b/packages/core/test/sdk.test.ts index 94cbed0aa4..a5542103c9 100644 --- a/packages/core/test/sdk.test.ts +++ b/packages/core/test/sdk.test.ts @@ -14,7 +14,7 @@ import { init, withScope } from '../src/js/sdk'; import type { ReactNativeTracingIntegration } from '../src/js/tracing'; import { REACT_NATIVE_TRACING_INTEGRATION_NAME, reactNativeTracingIntegration } from '../src/js/tracing'; import { makeNativeTransport } from '../src/js/transports/native'; -import { getDefaultEnvironment, isExpoGo, isWeb, notWeb } from '../src/js/utils/environment'; +import { getDefaultEnvironment, isExpoGo, notWeb } from '../src/js/utils/environment'; import { NATIVE } from './mockWrapper'; import { firstArg, secondArg } from './testutils'; @@ -864,6 +864,14 @@ describe('Tests the SDK functionality', () => { expectIntegration('MobileReplay'); }); + it('adds mobile replay integration when replaysOnErrorSampleRate is set', () => { + init({ + replaysOnErrorSampleRate: 1.0, + }); + + expectIntegration('MobileReplay'); + }); + it('adds mobile replay integration when _experiments.replaysSessionSampleRate is set', () => { init({ _experiments: { @@ -874,6 +882,14 @@ describe('Tests the SDK functionality', () => { expectIntegration('MobileReplay'); }); + it('adds mobile replay integration when replaysSessionSampleRate is set', () => { + init({ + replaysSessionSampleRate: 1.0, + }); + + expectIntegration('MobileReplay'); + }); + it('does not add mobile replay integration when no replay sample rates are set', () => { init({ _experiments: {}, @@ -882,8 +898,8 @@ describe('Tests the SDK functionality', () => { expectNotIntegration('MobileReplay'); }); - it('does not add any replay integration when on web even with on error sample rate', () => { - (isWeb as jest.Mock).mockImplementation(() => true); + it('does not add any replay integration when on web even with on experimental error sample rate', () => { + (notWeb as jest.Mock).mockImplementation(() => false); init({ _experiments: { replaysOnErrorSampleRate: 1.0, @@ -894,8 +910,8 @@ describe('Tests the SDK functionality', () => { expectNotIntegration('MobileReplay'); }); - it('does not add any replay integration when on web even with session sample rate', () => { - (isWeb as jest.Mock).mockImplementation(() => true); + it('does not add any replay integration when on web even with experimental session sample rate', () => { + (notWeb as jest.Mock).mockImplementation(() => false); init({ _experiments: { replaysSessionSampleRate: 1.0, @@ -906,16 +922,74 @@ describe('Tests the SDK functionality', () => { expectNotIntegration('MobileReplay'); }); + it('does not add any replay integration when on web even with on error sample rate', () => { + (notWeb as jest.Mock).mockImplementation(() => false); + init({ + replaysOnErrorSampleRate: 1.0, + }); + + expectNotIntegration('Replay'); + expectNotIntegration('MobileReplay'); + }); + + it('does not add any replay integration when on web even with session sample rate', () => { + (notWeb as jest.Mock).mockImplementation(() => false); + init({ + replaysSessionSampleRate: 1.0, + }); + + expectNotIntegration('Replay'); + expectNotIntegration('MobileReplay'); + }); + it('does not add any replay integration when on web', () => { - (isWeb as jest.Mock).mockImplementation(() => true); + (notWeb as jest.Mock).mockImplementation(() => false); init({}); expectNotIntegration('Replay'); expectNotIntegration('MobileReplay'); }); + it('ignores experimental replay options when ga options are set', () => { + (notWeb as jest.Mock).mockImplementation(() => false); + init({ + replaysOnErrorSampleRate: 0.1, + replaysSessionSampleRate: 0.2, + _experiments: { + replaysOnErrorSampleRate: 0.3, + replaysSessionSampleRate: 0.4, + }, + }); + + const actualOptions = usedOptions(); + expect(actualOptions).toEqual( + expect.objectContaining({ + replaysOnErrorSampleRate: 0.1, + replaysSessionSampleRate: 0.2, + }), + ); + }); + it('converts experimental replay options to standard web options when on web', () => { - (isWeb as jest.Mock).mockImplementation(() => true); + (notWeb as jest.Mock).mockImplementation(() => false); + init({ + _experiments: { + replaysOnErrorSampleRate: 0.5, + replaysSessionSampleRate: 0.1, + }, + }); + + const actualOptions = usedOptions(); + expect(actualOptions).toEqual( + expect.objectContaining({ + replaysOnErrorSampleRate: 0.5, + replaysSessionSampleRate: 0.1, + }), + ); + }); + + it('converts experimental replay options to standard web options when on mobile', () => { + (notWeb as jest.Mock).mockImplementation(() => true); init({ _experiments: { replaysOnErrorSampleRate: 0.5, From c05d071faad2828ba3c7254d7fa57d1c1ee76290 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 30 Dec 2024 11:11:37 -0500 Subject: [PATCH 05/10] Update packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift Co-authored-by: Antonis Lilis --- .../RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift index e73dc38b8e..5b51234239 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift @@ -37,7 +37,7 @@ final class RNSentryReplayOptions: XCTestCase { func testReplayOptionsDictContainsAllOptionsKeysWhenErrorAndSessionSampleRatesUsed() { let optionsDict = ([ "dsn": "https://abc@def.ingest.sentry.io/1234567", - "replaysOnErrorSampleRate": 0.75, + "replaysOnErrorSampleRate": 0.75, "replaysSessionSampleRate": 0.75 ] as NSDictionary).mutableCopy() as! NSMutableDictionary RNSentryReplay.updateOptions(optionsDict) From fa5944da788917dbe8ee8c99ccd188f3a7211309 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:52:26 +0100 Subject: [PATCH 06/10] chore(deps): update Android SDK to v7.20.0 (#4411) Co-authored-by: GitHub Co-authored-by: Roman Zavarnitsyn --- CHANGELOG.md | 6 +++--- packages/core/android/build.gradle | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 916da0b0a6..c74ce90e28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,9 +67,9 @@ - Bump CLI from v2.38.2 to v2.39.1 ([#4305](https://github.com/getsentry/sentry-react-native/pull/4305), [#4316](https://github.com/getsentry/sentry-react-native/pull/4316)) - [changelog](https://github.com/getsentry/sentry-cli/blob/master/CHANGELOG.md#2391) - [diff](https://github.com/getsentry/sentry-cli/compare/2.38.2...2.39.1) -- Bump Android SDK from v7.18.0 to v7.19.1 ([#4329](https://github.com/getsentry/sentry-react-native/pull/4329), [#4365](https://github.com/getsentry/sentry-react-native/pull/4365), [#4405](https://github.com/getsentry/sentry-react-native/pull/4405)) - - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7191) - - [diff](https://github.com/getsentry/sentry-java/compare/7.18.0...7.19.1) +- Bump Android SDK from v7.18.0 to v7.20.0 ([#4329](https://github.com/getsentry/sentry-react-native/pull/4329), [#4365](https://github.com/getsentry/sentry-react-native/pull/4365), [#4405](https://github.com/getsentry/sentry-react-native/pull/4405), [#4411](https://github.com/getsentry/sentry-react-native/pull/4411)) + - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7200) + - [diff](https://github.com/getsentry/sentry-java/compare/7.18.0...7.20.0) - Bump JavaScript SDK from v8.40.0 to v8.47.0 ([#4351](https://github.com/getsentry/sentry-react-native/pull/4351), [#4325](https://github.com/getsentry/sentry-react-native/pull/4325), [#4371](https://github.com/getsentry/sentry-react-native/pull/4371), [#4382](https://github.com/getsentry/sentry-react-native/pull/4382), [#4388](https://github.com/getsentry/sentry-react-native/pull/4388), [#4393](https://github.com/getsentry/sentry-react-native/pull/4393)) - [changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#8470) - [diff](https://github.com/getsentry/sentry-javascript/compare/8.40.0...8.47.0) diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 0349c9f185..d352df859f 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -54,5 +54,5 @@ android { dependencies { implementation 'com.facebook.react:react-native:+' - api 'io.sentry:sentry-android:7.19.1' + api 'io.sentry:sentry-android:7.20.0' } From e41e767e6c13abe91f1dcf2e203f99a898656f9a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:29:04 +0100 Subject: [PATCH 07/10] chore(deps): update Cocoa SDK to v8.43.0 (#4410) Co-authored-by: GitHub Co-authored-by: Roman Zavarnitsyn --- CHANGELOG.md | 6 +++--- packages/core/RNSentry.podspec | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c74ce90e28..733381a95d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,9 +73,9 @@ - Bump JavaScript SDK from v8.40.0 to v8.47.0 ([#4351](https://github.com/getsentry/sentry-react-native/pull/4351), [#4325](https://github.com/getsentry/sentry-react-native/pull/4325), [#4371](https://github.com/getsentry/sentry-react-native/pull/4371), [#4382](https://github.com/getsentry/sentry-react-native/pull/4382), [#4388](https://github.com/getsentry/sentry-react-native/pull/4388), [#4393](https://github.com/getsentry/sentry-react-native/pull/4393)) - [changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#8470) - [diff](https://github.com/getsentry/sentry-javascript/compare/8.40.0...8.47.0) -- Bump Cocoa SDK from v8.41.0 to v8.42.1 ([#4387](https://github.com/getsentry/sentry-react-native/pull/4387), [#4399](https://github.com/getsentry/sentry-react-native/pull/4399)) - - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8421) - - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.41.0...8.42.1) +- Bump Cocoa SDK from v8.41.0 to v8.43.0 ([#4387](https://github.com/getsentry/sentry-react-native/pull/4387), [#4399](https://github.com/getsentry/sentry-react-native/pull/4399), [#4410](https://github.com/getsentry/sentry-react-native/pull/4410)) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8430) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.41.0...8.43.0) ## 6.4.0 diff --git a/packages/core/RNSentry.podspec b/packages/core/RNSentry.podspec index 449d195877..97c4fd315c 100644 --- a/packages/core/RNSentry.podspec +++ b/packages/core/RNSentry.podspec @@ -37,7 +37,7 @@ Pod::Spec.new do |s| s.compiler_flags = other_cflags - s.dependency 'Sentry/HybridSDK', '8.42.1' + s.dependency 'Sentry/HybridSDK', '8.43.0' if defined? install_modules_dependencies # Default React Native dependencies for 0.71 and above (new and legacy architecture) From bb38effc74c5a409e19afc9639e4c13644fd958a Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 3 Jan 2025 13:24:27 +0100 Subject: [PATCH 08/10] Set SdkVersion to react native for replay events on Android --- .../src/main/java/io/sentry/react/RNSentryModuleImpl.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index eb0e2698a6..b3be280d8d 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -338,7 +338,13 @@ private boolean isReplayEnabled(SentryReplayOptions replayOptions) { } private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { - @NotNull final SentryReplayOptions androidReplayOptions = new SentryReplayOptions(false); + final SdkVersion replaySdkVersion = + new SdkVersion( + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME, + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); + @NotNull + final SentryReplayOptions androidReplayOptions = + new SentryReplayOptions(false, replaySdkVersion); if (!(rnOptions.hasKey("replaysSessionSampleRate") || rnOptions.hasKey("replaysOnErrorSampleRate"))) { From f3bf7e52ec9b29915b205e3232527e8457e64d9f Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 3 Jan 2025 14:00:29 +0100 Subject: [PATCH 09/10] Use new options in samples --- packages/core/src/js/replay/mobilereplay.ts | 6 ++---- samples/expo/app/_layout.tsx | 6 ++---- samples/react-native-macos/src/App.tsx | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/core/src/js/replay/mobilereplay.ts b/packages/core/src/js/replay/mobilereplay.ts index de27fcb247..4f4501b138 100644 --- a/packages/core/src/js/replay/mobilereplay.ts +++ b/packages/core/src/js/replay/mobilereplay.ts @@ -49,10 +49,8 @@ type MobileReplayIntegration = Integration & { * * ```javascript * Sentry.init({ - * _experiments: { - * replaysOnErrorSampleRate: 1.0, - * replaysSessionSampleRate: 1.0, - * }, + * replaysOnErrorSampleRate: 1.0, + * replaysSessionSampleRate: 1.0, * integrations: [mobileReplayIntegration({ * // Adjust the default options * })], diff --git a/samples/expo/app/_layout.tsx b/samples/expo/app/_layout.tsx index 1dd559efe8..e950ff4a66 100644 --- a/samples/expo/app/_layout.tsx +++ b/samples/expo/app/_layout.tsx @@ -79,10 +79,8 @@ Sentry.init({ // release: 'myapp@1.2.3+1', // dist: `1`, profilesSampleRate: 1.0, - _experiments: { - // replaysOnErrorSampleRate: 1.0, - replaysSessionSampleRate: 1.0, - }, + // replaysOnErrorSampleRate: 1.0, + replaysSessionSampleRate: 1.0, spotlight: true, }); diff --git a/samples/react-native-macos/src/App.tsx b/samples/react-native-macos/src/App.tsx index bf6a3c4be9..08873a81da 100644 --- a/samples/react-native-macos/src/App.tsx +++ b/samples/react-native-macos/src/App.tsx @@ -110,10 +110,8 @@ Sentry.init({ // release: 'myapp@1.2.3+1', // dist: `1`, profilesSampleRate: 1.0, - _experiments: { - // replaysSessionSampleRate: 1.0, - replaysOnErrorSampleRate: 1.0, - }, + // replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 1.0, spotlight: true, // This should be disabled when manually initializing the native SDK // Note that options from JS are not passed to the native SDKs when initialized manually From c12f51e47f1ef30192efd0541e318f5ef85a3c2e Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 3 Jan 2025 15:56:42 +0200 Subject: [PATCH 10/10] Fixes testMaskAllVectors failing test --- .../RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift index 5b51234239..936d55bb1a 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryReplayOptionsTests.swift @@ -87,7 +87,7 @@ final class RNSentryReplayOptions: XCTestCase { RNSentryReplay.updateOptions(optionsDict) - XCTAssertEqual(optionsDict.count, 3) + XCTAssertEqual(optionsDict.count, 4) let sessionReplay = optionsDict["sessionReplay"] as! [String: Any]