Skip to content

Commit

Permalink
Merge ac72abc into 30e2d76
Browse files Browse the repository at this point in the history
  • Loading branch information
krystofwoldrich authored Jun 27, 2024
2 parents 30e2d76 + ac72abc commit 376301c
Show file tree
Hide file tree
Showing 39 changed files with 1,565 additions and 31 deletions.
55 changes: 53 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Features

- Improve touch event component info if annotated with [`@sentry/babel-plugin-component-annotate`](https://www.npmjs.com/package/@sentry/babel-plugin-component-annotate) ([#3899](https://github.com/getsentry/sentry-react-native/pull/3899))
- Add replay breadcrumbs for touch & navigation events ([#3846](https://github.com/getsentry/sentry-react-native/pull/3846))
- Add network data to Session Replays ([#3912](https://github.com/getsentry/sentry-react-native/pull/3912))

### Fixes

Expand Down Expand Up @@ -115,6 +117,14 @@ This release does *not* build on iOS. Please use `5.23.1` or newer.
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8270)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.26.0...8.27.0)

## 5.23.0-alpha.1

### 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/)

## 5.22.3

### Fixes
Expand Down Expand Up @@ -148,6 +158,47 @@ This release does *not* build on iOS. Please use `5.23.1` or newer.
- [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 Expand Up @@ -658,7 +709,7 @@ This release is compatible with `[email protected]` and newer.
});
```

Read more at https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#7690
Read more at <https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#7690>

- Report current screen in `contexts.app.view_names` ([#3339](https://github.com/getsentry/sentry-react-native/pull/3339))

Expand Down Expand Up @@ -2697,7 +2748,7 @@ We are looking into ways making this more stable and plan to re-enable it again

## v0.23.2

- Fixed #228 again ¯\\_(ツ)_
- Fixed #228 again ¯\\*(ツ)*

## v0.23.1

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.10.0'
api 'io.sentry:sentry-android:7.11.0-alpha.2'
}
58 changes: 56 additions & 2 deletions 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 @@ -186,7 +188,7 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {

options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion());
options.setNativeSdkName(NATIVE_SDK_NAME);
options.setSdkVersion(sdkVersion);
options.setSdkVersion(sdkVersion);

if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) {
options.setDebug(true);
Expand Down Expand Up @@ -252,7 +254,10 @@ 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.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter());
}
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 +298,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 @@ -394,6 +430,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package io.sentry.react;

import io.sentry.Breadcrumb;
import io.sentry.android.replay.DefaultReplayBreadcrumbConverter;
import io.sentry.rrweb.RRWebEvent;
import io.sentry.rrweb.RRWebBreadcrumbEvent;
import io.sentry.rrweb.RRWebSpanEvent;

import java.util.ArrayList;
import java.util.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import java.util.HashMap;

public final class RNSentryReplayBreadcrumbConverter extends DefaultReplayBreadcrumbConverter {
public RNSentryReplayBreadcrumbConverter() {
}

@Override
public @Nullable RRWebEvent convert(final @NotNull Breadcrumb breadcrumb) {
if (breadcrumb.getCategory() == null) {
return null;
}

if (breadcrumb.getCategory().equals("touch")) {
return convertTouchBreadcrumb(breadcrumb);
}
if (breadcrumb.getCategory().equals("navigation")) {
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();
rrWebBreadcrumb.setCategory(breadcrumb.getCategory());
rrWebBreadcrumb.setData(breadcrumb.getData());
return rrWebBreadcrumb;
}
if (breadcrumb.getCategory().equals("xhr")) {
return convertNetworkBreadcrumb(breadcrumb);
}
if (breadcrumb.getCategory().equals("http")) {
// Drop native http breadcrumbs to avoid duplicates
return null;
}

RRWebEvent nativeBreadcrumb = super.convert(breadcrumb);

// ignore native navigation breadcrumbs
if (nativeBreadcrumb instanceof RRWebBreadcrumbEvent) {
final RRWebBreadcrumbEvent rrWebBreadcrumb = (RRWebBreadcrumbEvent) nativeBreadcrumb;
if (rrWebBreadcrumb.getCategory() != null && rrWebBreadcrumb.getCategory().equals("navigation")) {
return null;
}
}

return nativeBreadcrumb;
}

@TestOnly
public @NotNull RRWebEvent convertTouchBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();

rrWebBreadcrumb.setCategory("ui.tap");
ArrayList path = (ArrayList) breadcrumb.getData("path");
if (path != null) {
StringBuilder message = new StringBuilder();
for (int i = Math.min(3, path.size()); i >= 0; i--) {
HashMap item = (HashMap) path.get(i);
message.append(item.get("name"));
if (item.containsKey("element") || item.containsKey("file")) {
message.append('(');
if (item.containsKey("element")) {
message.append(item.get("element"));
if (item.containsKey("file")) {
message.append(", ");
message.append(item.get("file"));
}
} else if (item.containsKey("file")) {
message.append(item.get("file"));
}
message.append(')');
}
if (i > 0) {
message.append(" > ");
}
}
rrWebBreadcrumb.setMessage(message.toString());
}

rrWebBreadcrumb.setLevel(breadcrumb.getLevel());
rrWebBreadcrumb.setData(breadcrumb.getData());
rrWebBreadcrumb.setTimestamp(breadcrumb.getTimestamp().getTime());
rrWebBreadcrumb.setBreadcrumbTimestamp(breadcrumb.getTimestamp().getTime() / 1000.0);
rrWebBreadcrumb.setBreadcrumbType("default");
return rrWebBreadcrumb;
}

@TestOnly
public @Nullable RRWebEvent convertNetworkBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
final Double startTimestamp = breadcrumb.getData("start_timestamp") instanceof Number
? (Double) breadcrumb.getData("start_timestamp") : null;
final Double endTimestamp = breadcrumb.getData("end_timestamp") instanceof Number
? (Double) breadcrumb.getData("end_timestamp") : null;
final String url = breadcrumb.getData("url") instanceof String
? (String) breadcrumb.getData("url") : null;

if (startTimestamp == null || endTimestamp == null || url == null) {
return null;
}

final HashMap<String, Object> data = new HashMap<>();
if (breadcrumb.getData("method") instanceof String) {
data.put("method", breadcrumb.getData("method"));
}
if (breadcrumb.getData("status_code") instanceof Double) {
final Double statusCode = (Double) breadcrumb.getData("status_code");
if (statusCode > 0) {
data.put("statusCode", statusCode.intValue());
}
}
if (breadcrumb.getData("request_body_size") instanceof Double) {
data.put("requestBodySize", breadcrumb.getData("request_body_size"));
}
if (breadcrumb.getData("response_body_size") instanceof Double) {
data.put("responseBodySize", breadcrumb.getData("response_body_size"));
}

final RRWebSpanEvent rrWebSpanEvent = new RRWebSpanEvent();
rrWebSpanEvent.setOp("resource.http");
rrWebSpanEvent.setStartTimestamp(startTimestamp / 1000.0);
rrWebSpanEvent.setEndTimestamp(endTimestamp / 1000.0);
rrWebSpanEvent.setDescription(url);
rrWebSpanEvent.setData(data);
return rrWebSpanEvent;
}
}
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();
}
}
Loading

0 comments on commit 376301c

Please sign in to comment.