Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(replay): Add Mobile Replay Alpha #3714

Merged
merged 47 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
cbaa720
wip: Add Mobile Replay
krystofwoldrich Mar 22, 2024
8a2580b
Add mobile replay js interface
krystofwoldrich Mar 25, 2024
29c3ad9
Simplify integration interface, always use native replayId
krystofwoldrich Mar 25, 2024
de6669b
remove unwanted style changes
krystofwoldrich Mar 25, 2024
4a66cdd
add pod fix for sentry-cocoa replay version
krystofwoldrich Mar 25, 2024
2afdcc1
fix java impl
krystofwoldrich Mar 25, 2024
10379f9
Add RN Components playground
krystofwoldrich Mar 25, 2024
f8eb045
fix missing hard crash
krystofwoldrich Mar 26, 2024
f49f2fb
Merge remote-tracking branch 'origin/main' into kw-add-replay-v0
krystofwoldrich Apr 22, 2024
1c9001b
revert clags changes
krystofwoldrich Apr 22, 2024
3abfca8
Bump native SDKs to alphas with replay and add changelog
krystofwoldrich Apr 22, 2024
82fbf4c
Add replay tag in JS
krystofwoldrich Apr 23, 2024
a0727c9
add more robust is exception event check
krystofwoldrich Apr 23, 2024
8a291ed
Add on error sample rate to the sample app
krystofwoldrich Apr 23, 2024
7f1c85f
Use cocoa impl of captureReplay
krystofwoldrich Apr 23, 2024
48be9e9
Add Android native replay options, capture replay and EMPTY_ID check
krystofwoldrich Apr 24, 2024
f9acb41
add redacting RCT text and images for iOS replay
krystofwoldrich Apr 24, 2024
e7eb005
revert replayID tag and context functions
krystofwoldrich Apr 24, 2024
f792d2a
fix lint
krystofwoldrich Apr 24, 2024
2f8b8c8
move replay options under experiments
krystofwoldrich Apr 25, 2024
615faaf
enable replay in expo sample
krystofwoldrich Apr 25, 2024
5164930
pass replay options to enabled web
krystofwoldrich Apr 25, 2024
6227788
update changelog
krystofwoldrich Apr 26, 2024
a46da31
add mask options
krystofwoldrich Apr 26, 2024
0512490
update changelog
krystofwoldrich Apr 26, 2024
9f747c5
Pass replay options to native during sdk initialization
krystofwoldrich Apr 26, 2024
1c15afa
satisfies breaks tests
krystofwoldrich Apr 26, 2024
1e651fb
fix client init
krystofwoldrich Apr 26, 2024
c14d9cd
bump sentry cocoa
krystofwoldrich Apr 26, 2024
0a7996b
bump sentry-android
krystofwoldrich Apr 26, 2024
b081bd5
Merge branch 'main' into kw-add-replay-v0
krystofwoldrich Apr 29, 2024
982610d
use class from string to fix dynamically linked frameworks
krystofwoldrich Apr 29, 2024
8f61ad1
fix rn 0.65 e2e tests
krystofwoldrich Apr 29, 2024
d014385
Add tsDoc to mobile replay integration
krystofwoldrich Apr 29, 2024
fef4a4e
fix 0.65 build
krystofwoldrich Apr 29, 2024
d9997ed
release: 5.23.0-alpha.0
getsentry-bot Apr 29, 2024
7503acb
Merge branch 'release/5.23.0-alpha.0' into kw-add-replay-v0
Apr 29, 2024
a9f9f39
fix changelog _experiments
krystofwoldrich Apr 29, 2024
badbf77
Merge remote-tracking branch 'origin/main' into kw-add-replay-v0
krystofwoldrich Apr 30, 2024
5fd158e
Merge remote-tracking branch 'origin/main' into kw-add-replay-v0
krystofwoldrich May 6, 2024
592f513
fix session sample rate on Android
krystofwoldrich May 6, 2024
e293d5f
Merge remote-tracking branch 'origin/main' into kw-add-replay-v0
krystofwoldrich May 9, 2024
5519309
release: 5.23.0-alpha.1
getsentry-bot May 9, 2024
0bd517d
Merge branch 'release/5.23.0-alpha.1' into kw-add-replay-v0
May 9, 2024
48d8b35
Merge branch 'feat/replay' into kw-add-replay-v0
krystofwoldrich May 15, 2024
3177b5c
rename startReplay to captureReplay
krystofwoldrich May 15, 2024
50cbcda
Update src/js/utils/clientutils.ts
krystofwoldrich May 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 48 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Changelog

## Unreleased
## 5.23.0-alpha.1
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved

### Fixes

- Pass `replaysSessionSampleRate` option to Android ([#3714](https://github.com/getsentry/sentry-react-native/pull/3714))

Access to Mobile Replay is limited to early access orgs on Sentry. If you're interested, [sign up for the waitlist](https://sentry.io/lp/mobile-replay-beta/)
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved

### Features

Expand Down Expand Up @@ -69,6 +75,47 @@
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8250)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.24.0...8.25.0)

## 5.23.0-alpha.0

### Features

- Mobile Session Replay Alpha ([#3714](https://github.com/getsentry/sentry-react-native/pull/3714))

To enable Replay for React Native on mobile and web add the following options.

```js
Sentry.init({
_experiments: {
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
},
});
```

To change the default Mobile Replay options add the `mobileReplayIntegration`.

```js
Sentry.init({
_experiments: {
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
},
integration: [
Sentry.mobileReplayIntegration({
maskAllText: true,
maskAllImages: true,
}),
],
});
```

Access is limited to early access orgs on Sentry. If you're interested, [sign up for the waitlist](https://sentry.io/lp/mobile-replay-beta/)

### Dependencies

- Bump Cocoa SDK to [8.25.0-alpha.0](https://github.com/getsentry/sentry-cocoa/releases/tag/8.25.0-alpha.0)
- Bump Android SDK to [7.9.0-alpha.1](https://github.com/getsentry/sentry-java/releases/tag/7.9.0-alpha.1)

## 5.22.0

### Features
Expand Down
2 changes: 1 addition & 1 deletion RNSentry.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Pod::Spec.new do |s|
s.preserve_paths = '*.js'

s.dependency 'React-Core'
s.dependency 'Sentry/HybridSDK', '8.25.2'
s.dependency 'Sentry/HybridSDK', '8.25.0-alpha.0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can target 8.26.0 now

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll do the bump.


s.source_files = 'ios/**/*.{h,m,mm}'
s.public_header_files = 'ios/RNSentry.h'
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ android {

dependencies {
implementation 'com.facebook.react:react-native:+'
api 'io.sentry:sentry-android:7.8.0'
api 'io.sentry:sentry-android:7.9.0-alpha.1'
}
55 changes: 54 additions & 1 deletion android/src/main/java/io/sentry/react/RNSentryModuleImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import io.sentry.SentryExecutorService;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.SentryReplayOptions;
import io.sentry.UncaughtExceptionHandlerIntegration;
import io.sentry.android.core.AndroidLogger;
import io.sentry.android.core.AndroidProfiler;
Expand All @@ -79,6 +80,7 @@
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.protocol.SdkVersion;
import io.sentry.protocol.SentryException;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.SentryPackage;
import io.sentry.protocol.User;
import io.sentry.protocol.ViewHierarchy;
Expand Down Expand Up @@ -252,7 +254,9 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
if (rnOptions.hasKey("enableNdk")) {
options.setEnableNdk(rnOptions.getBoolean("enableNdk"));
}

if (rnOptions.hasKey("_experiments")) {
options.getExperimental().setSessionReplay(getReplayOptions(rnOptions));
}
options.setBeforeSend((event, hint) -> {
// React native internally throws a JavascriptException
// Since we catch it before that, we don't want to send this one
Expand Down Expand Up @@ -293,6 +297,37 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
promise.resolve(true);
}

private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
@NotNull final SentryReplayOptions androidReplayOptions = new SentryReplayOptions();

@Nullable final ReadableMap rnExperimentsOptions = rnOptions.getMap("_experiments");
if (rnExperimentsOptions == null) {
return androidReplayOptions;
}

if (!(rnExperimentsOptions.hasKey("replaysSessionSampleRate") || rnExperimentsOptions.hasKey("replaysOnErrorSampleRate"))) {
return androidReplayOptions;
}

androidReplayOptions.setSessionSampleRate(rnExperimentsOptions.hasKey("replaysSessionSampleRate")
? rnExperimentsOptions.getDouble("replaysSessionSampleRate") : null);
androidReplayOptions.setErrorSampleRate(rnExperimentsOptions.hasKey("replaysOnErrorSampleRate")
? rnExperimentsOptions.getDouble("replaysOnErrorSampleRate") : null);

if (!rnOptions.hasKey("mobileReplayOptions")) {
return androidReplayOptions;
}
@Nullable final ReadableMap rnMobileReplayOptions = rnOptions.getMap("mobileReplayOptions");
if (rnMobileReplayOptions == null) {
return androidReplayOptions;
}

androidReplayOptions.setRedactAllText(!rnMobileReplayOptions.hasKey("maskAllText") || rnMobileReplayOptions.getBoolean("maskAllText"));
androidReplayOptions.setRedactAllImages(!rnMobileReplayOptions.hasKey("maskAllImages") || rnMobileReplayOptions.getBoolean("maskAllImages"));

return androidReplayOptions;
}

public void crash() {
throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)");
}
Expand Down Expand Up @@ -410,6 +445,24 @@ public void fetchNativeFrames(Promise promise) {
}
}

public void captureReplay(boolean isHardCrash, Promise promise) {
Sentry.getCurrentHub().getOptions().getReplayController().sendReplay(isHardCrash, null, null);
promise.resolve(getCurrentReplayId());
}

public @Nullable String getCurrentReplayId() {
final @Nullable IScope scope = InternalSentrySdk.getCurrentScope();
if (scope == null) {
return null;
}

final @NotNull SentryId id = scope.getReplayId();
if (id == SentryId.EMPTY_ID) {
return null;
}
return id.toString();
}

public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) {
byte[] bytes = Base64.decode(rawBytes, Base64.DEFAULT);

Expand Down
10 changes: 10 additions & 0 deletions android/src/newarch/java/io/sentry/react/RNSentryModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,14 @@ public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
// Not used on Android
return null;
}

@Override
public void captureReplay(boolean isHardCrash, Promise promise) {
this.impl.captureReplay(isHardCrash, promise);
}

@Override
public String getCurrentReplayId() {
return this.impl.getCurrentReplayId();
}
}
10 changes: 10 additions & 0 deletions android/src/oldarch/java/io/sentry/react/RNSentryModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,14 @@ public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
// Not used on Android
return null;
}

@ReactMethod
public void captureReplay(boolean isHardCrash, Promise promise) {
this.impl.captureReplay(isHardCrash, promise);
}

@ReactMethod(isBlockingSynchronousMethod = true)
public String getCurrentReplayId() {
return this.impl.getCurrentReplayId();
}
}
48 changes: 47 additions & 1 deletion ios/RNSentry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
// Because we sent it already before the app crashed.
if (nil != event.exceptions.firstObject.type &&
[event.exceptions.firstObject.type rangeOfString:@"Unhandled JS Exception"].location != NSNotFound) {
NSLog(@"Unhandled JS Exception");
return nil;
}

Expand All @@ -135,6 +134,28 @@ - (SentryOptions *_Nullable)createOptionsWithDictionary:(NSDictionary *_Nonnull)
[mutableOptions removeObjectForKey:@"tracesSampler"];
[mutableOptions removeObjectForKey:@"enableTracing"];

if ([mutableOptions valueForKey:@"_experiments"] != nil) {
NSDictionary *experiments = mutableOptions[@"_experiments"];
if (experiments[@"replaysSessionSampleRate"] != nil || experiments[@"replaysOnErrorSampleRate"] != nil) {
[mutableOptions setValue:@{
@"sessionReplay": @{
@"sessionSampleRate": experiments[@"replaysSessionSampleRate"] ?: [NSNull null],
@"errorSampleRate": experiments[@"replaysOnErrorSampleRate"] ?: [NSNull null],
@"redactAllImages": mutableOptions[@"mobileReplayOptions"] != nil &&
mutableOptions[@"mobileReplayOptions"][@"maskAllImages"] != nil
? mutableOptions[@"mobileReplayOptions"][@"maskAllImages"]
: [NSNull null],
@"redactAllText": mutableOptions[@"mobileReplayOptions"] != nil &&
mutableOptions[@"mobileReplayOptions"][@"maskAllText"] != nil
? mutableOptions[@"mobileReplayOptions"][@"maskAllText"]
: [NSNull null],
}
} forKey:@"experimental"];
[self addReplayRNRedactClasses: mutableOptions[@"mobileReplayOptions"]];
}
[mutableOptions removeObjectForKey:@"_experiments"];
}

SentryOptions *sentryOptions = [[SentryOptions alloc] initWithDict:mutableOptions didFailWithError:errorPointer];
if (*errorPointer != nil) {
return nil;
Expand Down Expand Up @@ -644,6 +665,31 @@ - (NSDictionary*) fetchNativeStackFramesBy: (NSArray<NSNumber*>*)instructionsAdd
// the 'tracesSampleRate' or 'tracesSampler' option.
}

RCT_EXPORT_METHOD(captureReplay: (BOOL)isHardCrash
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[PrivateSentrySDKOnly captureReplay];
resolve([PrivateSentrySDKOnly getReplayId]);
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId)
{
return [PrivateSentrySDKOnly getReplayId];
}

- (void) addReplayRNRedactClasses: (NSDictionary *_Nullable)replayOptions
{
NSMutableArray *_Nonnull classesToRedact = [[NSMutableArray alloc] init];
if ([replayOptions[@"maskAllImages"] boolValue] == YES) {
[classesToRedact addObject: NSClassFromString(@"RCTImageView")];
}
if ([replayOptions[@"maskAllText"] boolValue] == YES) {
[classesToRedact addObject: NSClassFromString(@"RCTTextView")];
}
[PrivateSentrySDKOnly addReplayRedactClasses: classesToRedact];
}

static NSString* const enabledProfilingMessage = @"Enable Hermes to use Sentry Profiling.";
static SentryId* nativeProfileTraceId = nil;
static uint64_t nativeProfileStartTime = 0;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@sentry/react-native",
"homepage": "https://github.com/getsentry/sentry-react-native",
"repository": "https://github.com/getsentry/sentry-react-native",
"version": "5.22.2",
"version": "5.23.0-alpha.1",
"description": "Official Sentry SDK for react-native",
"typings": "dist/js/index.d.ts",
"types": "dist/js/index.d.ts",
Expand Down
6 changes: 3 additions & 3 deletions samples/expo/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"slug": "sentry-react-native-expo-sample",
"jsEngine": "hermes",
"scheme": "sentry-expo-sample",
"version": "5.22.2",
"version": "5.23.0-alpha.1",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
Expand All @@ -19,15 +19,15 @@
"ios": {
"supportsTablet": true,
"bundleIdentifier": "io.sentry.expo.sample",
"buildNumber": "6"
"buildNumber": "7"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "io.sentry.expo.sample",
"versionCode": 6
"versionCode": 7
},
"web": {
"bundler": "metro",
Expand Down
2 changes: 2 additions & 0 deletions samples/expo/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ process.env.EXPO_SKIP_DURING_EXPORT !== 'true' && Sentry.init({
// dist: `1`,
_experiments: {
profilesSampleRate: 0,
// replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 1.0,
},
enableSpotlight: true,
});
Expand Down
2 changes: 1 addition & 1 deletion samples/expo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sentry-react-native-expo-sample",
"version": "5.22.2",
"version": "5.23.0-alpha.1",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
Expand Down
4 changes: 2 additions & 2 deletions samples/react-native/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ android {
applicationId "io.sentry.reactnative.sample"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 13
versionName "5.22.2"
versionCode 14
versionName "5.23.0-alpha.1"
}

signingConfigs {
Expand Down
4 changes: 2 additions & 2 deletions samples/react-native/ios/sentryreactnativesample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.22.2</string>
<string>5.23.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>13</string>
<string>14</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>NSAppTransportSecurity</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>5.22.2</string>
<string>5.23.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>13</string>
<string>14</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion samples/react-native/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sentry-react-native-sample",
"version": "5.22.2",
"version": "5.23.0-alpha.1",
"private": true,
"scripts": {
"postinstall": "patch-package",
Expand Down