Skip to content

Commit 952dd05

Browse files
Merge branch 'main' into capture-app-start-errors
2 parents e935360 + 60ad48c commit 952dd05

34 files changed

+3633
-7
lines changed

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,41 @@
66
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
77
<!-- prettier-ignore-end -->
88
9+
## Unreleased
10+
11+
### Features
12+
13+
- User Feedback Widget Beta ([#4435](https://github.com/getsentry/sentry-react-native/pull/4435))
14+
15+
To collect user feedback from inside your application call `Sentry.showFeedbackWidget()`.
16+
17+
```js
18+
import Sentry from "@sentry/react-native";
19+
20+
Sentry.showFeedbackWidget();
21+
22+
Sentry.wrap(RootComponent);
23+
```
24+
25+
To change the default options add `Sentry.feedbackIntegration()`.
26+
27+
```js
28+
import Sentry from "@sentry/react-native";
29+
import * as ImagePicker from 'expo-image-picker';
30+
31+
Sentry.init({
32+
integrations: [
33+
Sentry.feedbackIntegration({
34+
imagePicker: ImagePicker,
35+
showName: true,
36+
showEmail: true,
37+
}),
38+
],
39+
});
40+
```
41+
42+
To learn more about the available configuration options visit [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/configuration).
43+
944
## 6.8.0
1045

1146
### Features

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import android.content.pm.PackageInfo;
1111
import android.content.pm.PackageManager;
1212
import android.content.res.AssetManager;
13+
import android.net.Uri;
1314
import android.util.SparseIntArray;
1415
import androidx.core.app.FrameMetricsAggregator;
1516
import androidx.fragment.app.FragmentActivity;
@@ -58,6 +59,7 @@
5859
import io.sentry.vendor.Base64;
5960
import java.io.BufferedInputStream;
6061
import java.io.BufferedReader;
62+
import java.io.ByteArrayOutputStream;
6163
import java.io.File;
6264
import java.io.FileNotFoundException;
6365
import java.io.FileReader;
@@ -750,6 +752,39 @@ public String fetchNativePackageName() {
750752
return packageInfo.packageName;
751753
}
752754

755+
public void getDataFromUri(String uri, Promise promise) {
756+
try {
757+
Uri contentUri = Uri.parse(uri);
758+
try (InputStream is =
759+
getReactApplicationContext().getContentResolver().openInputStream(contentUri)) {
760+
if (is == null) {
761+
String msg = "File not found for uri: " + uri;
762+
logger.log(SentryLevel.ERROR, msg);
763+
promise.reject(new Exception(msg));
764+
return;
765+
}
766+
767+
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
768+
int bufferSize = 1024;
769+
byte[] buffer = new byte[bufferSize];
770+
int len;
771+
while ((len = is.read(buffer)) != -1) {
772+
byteBuffer.write(buffer, 0, len);
773+
}
774+
byte[] byteArray = byteBuffer.toByteArray();
775+
WritableArray jsArray = Arguments.createArray();
776+
for (byte b : byteArray) {
777+
jsArray.pushInt(b & 0xFF);
778+
}
779+
promise.resolve(jsArray);
780+
}
781+
} catch (IOException e) {
782+
String msg = "Error reading uri: " + uri + ": " + e.getMessage();
783+
logger.log(SentryLevel.ERROR, msg);
784+
promise.reject(new Exception(msg));
785+
}
786+
}
787+
753788
public void crashedLastRun(Promise promise) {
754789
promise.resolve(Sentry.isCrashedLastRun());
755790
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,9 @@ public void crashedLastRun(Promise promise) {
177177
public void getNewScreenTimeToDisplay(Promise promise) {
178178
this.impl.getNewScreenTimeToDisplay(promise);
179179
}
180+
181+
@Override
182+
public void getDataFromUri(String uri, Promise promise) {
183+
this.impl.getDataFromUri(uri, promise);
184+
}
180185
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ public String fetchNativePackageName() {
152152
return this.impl.fetchNativePackageName();
153153
}
154154

155+
@ReactMethod
156+
public void getDataFromUri(String uri, Promise promise) {
157+
this.impl.getDataFromUri(uri, promise);
158+
}
159+
155160
@ReactMethod(isBlockingSynchronousMethod = true)
156161
public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
157162
// Not used on Android

packages/core/ios/RNSentry.mm

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,35 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys
602602
#endif
603603
}
604604

605+
RCT_EXPORT_METHOD(getDataFromUri
606+
: (NSString *_Nonnull)uri resolve
607+
: (RCTPromiseResolveBlock)resolve rejecter
608+
: (RCTPromiseRejectBlock)reject)
609+
{
610+
#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST
611+
NSURL *fileURL = [NSURL URLWithString:uri];
612+
if (![fileURL isFileURL]) {
613+
reject(@"SentryReactNative", @"The provided URI is not a valid file:// URL", nil);
614+
return;
615+
}
616+
NSError *error = nil;
617+
NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:0 error:&error];
618+
if (error || !fileData) {
619+
reject(@"SentryReactNative", @"Failed to read file data", error);
620+
return;
621+
}
622+
NSMutableArray *byteArray = [NSMutableArray arrayWithCapacity:fileData.length];
623+
const unsigned char *bytes = (const unsigned char *)fileData.bytes;
624+
625+
for (NSUInteger i = 0; i < fileData.length; i++) {
626+
[byteArray addObject:@(bytes[i])];
627+
}
628+
resolve(byteArray);
629+
#else
630+
resolve(nil);
631+
#endif
632+
}
633+
605634
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId)
606635
{
607636
#if SENTRY_TARGET_REPLAY_SUPPORTED

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"@sentry-internal/eslint-config-sdk": "8.54.0",
8282
"@sentry-internal/eslint-plugin-sdk": "8.54.0",
8383
"@sentry-internal/typescript": "8.54.0",
84-
"@sentry/wizard": "3.42.0",
84+
"@sentry/wizard": "3.42.1",
8585
"@testing-library/react-native": "^12.7.2",
8686
"@types/jest": "^29.5.13",
8787
"@types/node": "^20.9.3",

packages/core/src/js/NativeRNSentry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface Spec extends TurboModule {
4848
captureReplay(isHardCrash: boolean): Promise<string | undefined | null>;
4949
getCurrentReplayId(): string | undefined | null;
5050
crashedLastRun(): Promise<boolean | undefined | null>;
51+
getDataFromUri(uri: string): Promise<number[]>;
5152
}
5253

5354
export type NativeStackFrame = {
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import type { ViewStyle } from 'react-native';
2+
3+
import type { FeedbackWidgetStyles } from './FeedbackWidget.types';
4+
5+
const PURPLE = 'rgba(88, 74, 192, 1)';
6+
const FOREGROUND_COLOR = '#2b2233';
7+
const BACKGROUND_COLOR = '#ffffff';
8+
const BORDER_COLOR = 'rgba(41, 35, 47, 0.13)';
9+
10+
const defaultStyles: FeedbackWidgetStyles = {
11+
container: {
12+
flex: 1,
13+
padding: 20,
14+
backgroundColor: BACKGROUND_COLOR,
15+
},
16+
title: {
17+
fontSize: 24,
18+
fontWeight: 'bold',
19+
marginBottom: 20,
20+
textAlign: 'left',
21+
flex: 1,
22+
color: FOREGROUND_COLOR,
23+
},
24+
label: {
25+
marginBottom: 4,
26+
fontSize: 16,
27+
color: FOREGROUND_COLOR,
28+
},
29+
input: {
30+
height: 50,
31+
borderColor: BORDER_COLOR,
32+
borderWidth: 1,
33+
borderRadius: 5,
34+
paddingHorizontal: 10,
35+
marginBottom: 15,
36+
fontSize: 16,
37+
color: FOREGROUND_COLOR,
38+
},
39+
textArea: {
40+
height: 100,
41+
textAlignVertical: 'top',
42+
color: FOREGROUND_COLOR,
43+
},
44+
screenshotButton: {
45+
backgroundColor: BACKGROUND_COLOR,
46+
padding: 15,
47+
borderRadius: 5,
48+
alignItems: 'center',
49+
flex: 1,
50+
borderWidth: 1,
51+
borderColor: BORDER_COLOR,
52+
},
53+
screenshotContainer: {
54+
flexDirection: 'row',
55+
alignItems: 'center',
56+
width: '100%',
57+
marginBottom: 20,
58+
},
59+
screenshotThumbnail: {
60+
width: 50,
61+
height: 50,
62+
borderRadius: 5,
63+
marginRight: 10,
64+
},
65+
screenshotText: {
66+
color: FOREGROUND_COLOR,
67+
fontSize: 16,
68+
},
69+
submitButton: {
70+
backgroundColor: PURPLE,
71+
paddingVertical: 15,
72+
borderRadius: 5,
73+
alignItems: 'center',
74+
marginBottom: 10,
75+
},
76+
submitText: {
77+
color: BACKGROUND_COLOR,
78+
fontSize: 18,
79+
},
80+
cancelButton: {
81+
backgroundColor: BACKGROUND_COLOR,
82+
padding: 15,
83+
borderRadius: 5,
84+
alignItems: 'center',
85+
borderWidth: 1,
86+
borderColor: BORDER_COLOR,
87+
},
88+
cancelText: {
89+
color: FOREGROUND_COLOR,
90+
fontSize: 16,
91+
},
92+
titleContainer: {
93+
flexDirection: 'row',
94+
width: '100%',
95+
},
96+
sentryLogo: {
97+
width: 40,
98+
height: 40,
99+
},
100+
};
101+
102+
export const modalWrapper: ViewStyle = {
103+
position: 'absolute',
104+
top: 0,
105+
left: 0,
106+
right: 0,
107+
bottom: 0,
108+
};
109+
110+
export const modalSheetContainer: ViewStyle = {
111+
backgroundColor: '#ffffff',
112+
borderTopLeftRadius: 16,
113+
borderTopRightRadius: 16,
114+
overflow: 'hidden',
115+
alignSelf: 'stretch',
116+
shadowColor: '#000',
117+
shadowOffset: { width: 0, height: -3 },
118+
shadowOpacity: 0.1,
119+
shadowRadius: 4,
120+
elevation: 5,
121+
flex: 1,
122+
};
123+
124+
export const topSpacer: ViewStyle = {
125+
height: 64, // magic number
126+
};
127+
128+
export default defaultStyles;

0 commit comments

Comments
 (0)