Skip to content

Commit 376301c

Browse files
Merge ac72abc into 30e2d76
2 parents 30e2d76 + ac72abc commit 376301c

39 files changed

+1565
-31
lines changed

CHANGELOG.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
### Features
66

77
- 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))
8+
- Add replay breadcrumbs for touch & navigation events ([#3846](https://github.com/getsentry/sentry-react-native/pull/3846))
9+
- Add network data to Session Replays ([#3912](https://github.com/getsentry/sentry-react-native/pull/3912))
810

911
### Fixes
1012

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

120+
## 5.23.0-alpha.1
121+
122+
### Fixes
123+
124+
- Pass `replaysSessionSampleRate` option to Android ([#3714](https://github.com/getsentry/sentry-react-native/pull/3714))
125+
126+
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/)
127+
118128
## 5.22.3
119129

120130
### Fixes
@@ -148,6 +158,47 @@ This release does *not* build on iOS. Please use `5.23.1` or newer.
148158
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8250)
149159
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.24.0...8.25.0)
150160

161+
## 5.23.0-alpha.0
162+
163+
### Features
164+
165+
- Mobile Session Replay Alpha ([#3714](https://github.com/getsentry/sentry-react-native/pull/3714))
166+
167+
To enable Replay for React Native on mobile and web add the following options.
168+
169+
```js
170+
Sentry.init({
171+
_experiments: {
172+
replaysSessionSampleRate: 1.0,
173+
replaysOnErrorSampleRate: 1.0,
174+
},
175+
});
176+
```
177+
178+
To change the default Mobile Replay options add the `mobileReplayIntegration`.
179+
180+
```js
181+
Sentry.init({
182+
_experiments: {
183+
replaysSessionSampleRate: 1.0,
184+
replaysOnErrorSampleRate: 1.0,
185+
},
186+
integration: [
187+
Sentry.mobileReplayIntegration({
188+
maskAllText: true,
189+
maskAllImages: true,
190+
}),
191+
],
192+
});
193+
```
194+
195+
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/)
196+
197+
### Dependencies
198+
199+
- Bump Cocoa SDK to [8.25.0-alpha.0](https://github.com/getsentry/sentry-cocoa/releases/tag/8.25.0-alpha.0)
200+
- Bump Android SDK to [7.9.0-alpha.1](https://github.com/getsentry/sentry-java/releases/tag/7.9.0-alpha.1)
201+
151202
## 5.22.0
152203

153204
### Features
@@ -658,7 +709,7 @@ This release is compatible with `[email protected]` and newer.
658709
});
659710
```
660711

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

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

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

26982749
## v0.23.2
26992750

2700-
- Fixed #228 again ¯\\_(ツ)_
2751+
- Fixed #228 again ¯\\*(ツ)*
27012752

27022753
## v0.23.1
27032754

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ android {
5454

5555
dependencies {
5656
implementation 'com.facebook.react:react-native:+'
57-
api 'io.sentry:sentry-android:7.10.0'
57+
api 'io.sentry:sentry-android:7.11.0-alpha.2'
5858
}

android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import io.sentry.SentryExecutorService;
6262
import io.sentry.SentryLevel;
6363
import io.sentry.SentryOptions;
64+
import io.sentry.SentryReplayOptions;
6465
import io.sentry.UncaughtExceptionHandlerIntegration;
6566
import io.sentry.android.core.AndroidLogger;
6667
import io.sentry.android.core.AndroidProfiler;
@@ -79,6 +80,7 @@
7980
import io.sentry.android.core.performance.AppStartMetrics;
8081
import io.sentry.protocol.SdkVersion;
8182
import io.sentry.protocol.SentryException;
83+
import io.sentry.protocol.SentryId;
8284
import io.sentry.protocol.SentryPackage;
8385
import io.sentry.protocol.User;
8486
import io.sentry.protocol.ViewHierarchy;
@@ -186,7 +188,7 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
186188

187189
options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion());
188190
options.setNativeSdkName(NATIVE_SDK_NAME);
189-
options.setSdkVersion(sdkVersion);
191+
options.setSdkVersion(sdkVersion);
190192

191193
if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) {
192194
options.setDebug(true);
@@ -252,7 +254,10 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
252254
if (rnOptions.hasKey("enableNdk")) {
253255
options.setEnableNdk(rnOptions.getBoolean("enableNdk"));
254256
}
255-
257+
if (rnOptions.hasKey("_experiments")) {
258+
options.getExperimental().setSessionReplay(getReplayOptions(rnOptions));
259+
options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter());
260+
}
256261
options.setBeforeSend((event, hint) -> {
257262
// React native internally throws a JavascriptException
258263
// Since we catch it before that, we don't want to send this one
@@ -293,6 +298,37 @@ public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
293298
promise.resolve(true);
294299
}
295300

301+
private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) {
302+
@NotNull final SentryReplayOptions androidReplayOptions = new SentryReplayOptions();
303+
304+
@Nullable final ReadableMap rnExperimentsOptions = rnOptions.getMap("_experiments");
305+
if (rnExperimentsOptions == null) {
306+
return androidReplayOptions;
307+
}
308+
309+
if (!(rnExperimentsOptions.hasKey("replaysSessionSampleRate") || rnExperimentsOptions.hasKey("replaysOnErrorSampleRate"))) {
310+
return androidReplayOptions;
311+
}
312+
313+
androidReplayOptions.setSessionSampleRate(rnExperimentsOptions.hasKey("replaysSessionSampleRate")
314+
? rnExperimentsOptions.getDouble("replaysSessionSampleRate") : null);
315+
androidReplayOptions.setErrorSampleRate(rnExperimentsOptions.hasKey("replaysOnErrorSampleRate")
316+
? rnExperimentsOptions.getDouble("replaysOnErrorSampleRate") : null);
317+
318+
if (!rnOptions.hasKey("mobileReplayOptions")) {
319+
return androidReplayOptions;
320+
}
321+
@Nullable final ReadableMap rnMobileReplayOptions = rnOptions.getMap("mobileReplayOptions");
322+
if (rnMobileReplayOptions == null) {
323+
return androidReplayOptions;
324+
}
325+
326+
androidReplayOptions.setRedactAllText(!rnMobileReplayOptions.hasKey("maskAllText") || rnMobileReplayOptions.getBoolean("maskAllText"));
327+
androidReplayOptions.setRedactAllImages(!rnMobileReplayOptions.hasKey("maskAllImages") || rnMobileReplayOptions.getBoolean("maskAllImages"));
328+
329+
return androidReplayOptions;
330+
}
331+
296332
public void crash() {
297333
throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)");
298334
}
@@ -394,6 +430,24 @@ public void fetchNativeFrames(Promise promise) {
394430
}
395431
}
396432

433+
public void captureReplay(boolean isHardCrash, Promise promise) {
434+
Sentry.getCurrentHub().getOptions().getReplayController().sendReplay(isHardCrash, null, null);
435+
promise.resolve(getCurrentReplayId());
436+
}
437+
438+
public @Nullable String getCurrentReplayId() {
439+
final @Nullable IScope scope = InternalSentrySdk.getCurrentScope();
440+
if (scope == null) {
441+
return null;
442+
}
443+
444+
final @NotNull SentryId id = scope.getReplayId();
445+
if (id == SentryId.EMPTY_ID) {
446+
return null;
447+
}
448+
return id.toString();
449+
}
450+
397451
public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) {
398452
byte[] bytes = Base64.decode(rawBytes, Base64.DEFAULT);
399453

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package io.sentry.react;
2+
3+
import io.sentry.Breadcrumb;
4+
import io.sentry.android.replay.DefaultReplayBreadcrumbConverter;
5+
import io.sentry.rrweb.RRWebEvent;
6+
import io.sentry.rrweb.RRWebBreadcrumbEvent;
7+
import io.sentry.rrweb.RRWebSpanEvent;
8+
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.jetbrains.annotations.Nullable;
13+
import org.jetbrains.annotations.TestOnly;
14+
15+
import java.util.HashMap;
16+
17+
public final class RNSentryReplayBreadcrumbConverter extends DefaultReplayBreadcrumbConverter {
18+
public RNSentryReplayBreadcrumbConverter() {
19+
}
20+
21+
@Override
22+
public @Nullable RRWebEvent convert(final @NotNull Breadcrumb breadcrumb) {
23+
if (breadcrumb.getCategory() == null) {
24+
return null;
25+
}
26+
27+
if (breadcrumb.getCategory().equals("touch")) {
28+
return convertTouchBreadcrumb(breadcrumb);
29+
}
30+
if (breadcrumb.getCategory().equals("navigation")) {
31+
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();
32+
rrWebBreadcrumb.setCategory(breadcrumb.getCategory());
33+
rrWebBreadcrumb.setData(breadcrumb.getData());
34+
return rrWebBreadcrumb;
35+
}
36+
if (breadcrumb.getCategory().equals("xhr")) {
37+
return convertNetworkBreadcrumb(breadcrumb);
38+
}
39+
if (breadcrumb.getCategory().equals("http")) {
40+
// Drop native http breadcrumbs to avoid duplicates
41+
return null;
42+
}
43+
44+
RRWebEvent nativeBreadcrumb = super.convert(breadcrumb);
45+
46+
// ignore native navigation breadcrumbs
47+
if (nativeBreadcrumb instanceof RRWebBreadcrumbEvent) {
48+
final RRWebBreadcrumbEvent rrWebBreadcrumb = (RRWebBreadcrumbEvent) nativeBreadcrumb;
49+
if (rrWebBreadcrumb.getCategory() != null && rrWebBreadcrumb.getCategory().equals("navigation")) {
50+
return null;
51+
}
52+
}
53+
54+
return nativeBreadcrumb;
55+
}
56+
57+
@TestOnly
58+
public @NotNull RRWebEvent convertTouchBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
59+
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();
60+
61+
rrWebBreadcrumb.setCategory("ui.tap");
62+
ArrayList path = (ArrayList) breadcrumb.getData("path");
63+
if (path != null) {
64+
StringBuilder message = new StringBuilder();
65+
for (int i = Math.min(3, path.size()); i >= 0; i--) {
66+
HashMap item = (HashMap) path.get(i);
67+
message.append(item.get("name"));
68+
if (item.containsKey("element") || item.containsKey("file")) {
69+
message.append('(');
70+
if (item.containsKey("element")) {
71+
message.append(item.get("element"));
72+
if (item.containsKey("file")) {
73+
message.append(", ");
74+
message.append(item.get("file"));
75+
}
76+
} else if (item.containsKey("file")) {
77+
message.append(item.get("file"));
78+
}
79+
message.append(')');
80+
}
81+
if (i > 0) {
82+
message.append(" > ");
83+
}
84+
}
85+
rrWebBreadcrumb.setMessage(message.toString());
86+
}
87+
88+
rrWebBreadcrumb.setLevel(breadcrumb.getLevel());
89+
rrWebBreadcrumb.setData(breadcrumb.getData());
90+
rrWebBreadcrumb.setTimestamp(breadcrumb.getTimestamp().getTime());
91+
rrWebBreadcrumb.setBreadcrumbTimestamp(breadcrumb.getTimestamp().getTime() / 1000.0);
92+
rrWebBreadcrumb.setBreadcrumbType("default");
93+
return rrWebBreadcrumb;
94+
}
95+
96+
@TestOnly
97+
public @Nullable RRWebEvent convertNetworkBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
98+
final Double startTimestamp = breadcrumb.getData("start_timestamp") instanceof Number
99+
? (Double) breadcrumb.getData("start_timestamp") : null;
100+
final Double endTimestamp = breadcrumb.getData("end_timestamp") instanceof Number
101+
? (Double) breadcrumb.getData("end_timestamp") : null;
102+
final String url = breadcrumb.getData("url") instanceof String
103+
? (String) breadcrumb.getData("url") : null;
104+
105+
if (startTimestamp == null || endTimestamp == null || url == null) {
106+
return null;
107+
}
108+
109+
final HashMap<String, Object> data = new HashMap<>();
110+
if (breadcrumb.getData("method") instanceof String) {
111+
data.put("method", breadcrumb.getData("method"));
112+
}
113+
if (breadcrumb.getData("status_code") instanceof Double) {
114+
final Double statusCode = (Double) breadcrumb.getData("status_code");
115+
if (statusCode > 0) {
116+
data.put("statusCode", statusCode.intValue());
117+
}
118+
}
119+
if (breadcrumb.getData("request_body_size") instanceof Double) {
120+
data.put("requestBodySize", breadcrumb.getData("request_body_size"));
121+
}
122+
if (breadcrumb.getData("response_body_size") instanceof Double) {
123+
data.put("responseBodySize", breadcrumb.getData("response_body_size"));
124+
}
125+
126+
final RRWebSpanEvent rrWebSpanEvent = new RRWebSpanEvent();
127+
rrWebSpanEvent.setOp("resource.http");
128+
rrWebSpanEvent.setStartTimestamp(startTimestamp / 1000.0);
129+
rrWebSpanEvent.setEndTimestamp(endTimestamp / 1000.0);
130+
rrWebSpanEvent.setDescription(url);
131+
rrWebSpanEvent.setData(data);
132+
return rrWebSpanEvent;
133+
}
134+
}

android/src/newarch/java/io/sentry/react/RNSentryModule.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,14 @@ public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
158158
// Not used on Android
159159
return null;
160160
}
161+
162+
@Override
163+
public void captureReplay(boolean isHardCrash, Promise promise) {
164+
this.impl.captureReplay(isHardCrash, promise);
165+
}
166+
167+
@Override
168+
public String getCurrentReplayId() {
169+
return this.impl.getCurrentReplayId();
170+
}
161171
}

android/src/oldarch/java/io/sentry/react/RNSentryModule.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,14 @@ public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
158158
// Not used on Android
159159
return null;
160160
}
161+
162+
@ReactMethod
163+
public void captureReplay(boolean isHardCrash, Promise promise) {
164+
this.impl.captureReplay(isHardCrash, promise);
165+
}
166+
167+
@ReactMethod(isBlockingSynchronousMethod = true)
168+
public String getCurrentReplayId() {
169+
return this.impl.getCurrentReplayId();
170+
}
161171
}

0 commit comments

Comments
 (0)