Skip to content

Commit d1a10a1

Browse files
authored
Merge 2135c96 into 686b3bc
2 parents 686b3bc + 2135c96 commit d1a10a1

31 files changed

+3515
-1
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88
99
## Unreleased
1010

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()` or add the `FeedbackWidget` component.
16+
17+
```jsx
18+
import { FeedbackWidget } from "@sentry/react-native";
19+
...
20+
<FeedbackWidget/>
21+
```
22+
1123
### Fixes
1224

1325
- Various crashes and issues of Session Replay on Android. See the Android SDK version bump for more details. ([#4529](https://github.com/getsentry/sentry-react-native/pull/4529))

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;
@@ -72,6 +73,7 @@
7273
import io.sentry.vendor.Base64;
7374
import java.io.BufferedInputStream;
7475
import java.io.BufferedReader;
76+
import java.io.ByteArrayOutputStream;
7577
import java.io.File;
7678
import java.io.FileNotFoundException;
7779
import java.io.FileReader;
@@ -970,6 +972,39 @@ public String fetchNativePackageName() {
970972
return packageInfo.packageName;
971973
}
972974

975+
public void getDataFromUri(String uri, Promise promise) {
976+
try {
977+
Uri contentUri = Uri.parse(uri);
978+
try (InputStream is =
979+
getReactApplicationContext().getContentResolver().openInputStream(contentUri)) {
980+
if (is == null) {
981+
String msg = "File not found for uri: " + uri;
982+
logger.log(SentryLevel.ERROR, msg);
983+
promise.reject(new Exception(msg));
984+
return;
985+
}
986+
987+
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
988+
int bufferSize = 1024;
989+
byte[] buffer = new byte[bufferSize];
990+
int len;
991+
while ((len = is.read(buffer)) != -1) {
992+
byteBuffer.write(buffer, 0, len);
993+
}
994+
byte[] byteArray = byteBuffer.toByteArray();
995+
WritableArray jsArray = Arguments.createArray();
996+
for (byte b : byteArray) {
997+
jsArray.pushInt(b & 0xFF);
998+
}
999+
promise.resolve(jsArray);
1000+
}
1001+
} catch (IOException e) {
1002+
String msg = "Error reading uri: " + uri + ": " + e.getMessage();
1003+
logger.log(SentryLevel.ERROR, msg);
1004+
promise.reject(new Exception(msg));
1005+
}
1006+
}
1007+
9731008
public void crashedLastRun(Promise promise) {
9741009
promise.resolve(Sentry.isCrashedLastRun());
9751010
}

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
@@ -745,6 +745,35 @@ - (NSDictionary *)fetchNativeStackFramesBy:(NSArray<NSNumber *> *)instructionsAd
745745
#endif
746746
}
747747

748+
RCT_EXPORT_METHOD(getDataFromUri
749+
: (NSString *_Nonnull)uri resolve
750+
: (RCTPromiseResolveBlock)resolve rejecter
751+
: (RCTPromiseRejectBlock)reject)
752+
{
753+
#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST
754+
NSURL *fileURL = [NSURL URLWithString:uri];
755+
if (![fileURL isFileURL]) {
756+
reject(@"SentryReactNative", @"The provided URI is not a valid file:// URL", nil);
757+
return;
758+
}
759+
NSError *error = nil;
760+
NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:0 error:&error];
761+
if (error || !fileData) {
762+
reject(@"SentryReactNative", @"Failed to read file data", error);
763+
return;
764+
}
765+
NSMutableArray *byteArray = [NSMutableArray arrayWithCapacity:fileData.length];
766+
const unsigned char *bytes = (const unsigned char *)fileData.bytes;
767+
768+
for (NSUInteger i = 0; i < fileData.length; i++) {
769+
[byteArray addObject:@(bytes[i])];
770+
}
771+
resolve(byteArray);
772+
#else
773+
resolve(nil);
774+
#endif
775+
}
776+
748777
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId)
749778
{
750779
#if SENTRY_TARGET_REPLAY_SUPPORTED

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: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 FORGROUND_COLOR = '#2b2233';
7+
const BACKROUND_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: BACKROUND_COLOR,
15+
},
16+
title: {
17+
fontSize: 24,
18+
fontWeight: 'bold',
19+
marginBottom: 20,
20+
textAlign: 'left',
21+
flex: 1,
22+
color: FORGROUND_COLOR,
23+
},
24+
label: {
25+
marginBottom: 4,
26+
fontSize: 16,
27+
color: FORGROUND_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: FORGROUND_COLOR,
38+
},
39+
textArea: {
40+
height: 100,
41+
textAlignVertical: 'top',
42+
color: FORGROUND_COLOR,
43+
},
44+
screenshotButton: {
45+
backgroundColor: '#eee',
46+
padding: 15,
47+
borderRadius: 5,
48+
marginBottom: 20,
49+
alignItems: 'center',
50+
},
51+
screenshotText: {
52+
color: '#333',
53+
fontSize: 16,
54+
},
55+
submitButton: {
56+
backgroundColor: PURPLE,
57+
paddingVertical: 15,
58+
borderRadius: 5,
59+
alignItems: 'center',
60+
marginBottom: 10,
61+
},
62+
submitText: {
63+
color: BACKROUND_COLOR,
64+
fontSize: 18,
65+
},
66+
cancelButton: {
67+
paddingVertical: 15,
68+
alignItems: 'center',
69+
},
70+
cancelText: {
71+
color: FORGROUND_COLOR,
72+
fontSize: 16,
73+
},
74+
titleContainer: {
75+
flexDirection: 'row',
76+
width: '100%',
77+
},
78+
sentryLogo: {
79+
width: 40,
80+
height: 40,
81+
},
82+
};
83+
84+
export const modalWrapper: ViewStyle = {
85+
position: 'absolute',
86+
top: 0,
87+
left: 0,
88+
right: 0,
89+
bottom: 0,
90+
};
91+
92+
export const modalBackground: ViewStyle = {
93+
flex: 1,
94+
justifyContent: 'flex-end',
95+
};
96+
97+
export const modalSheetContainer: ViewStyle = {
98+
backgroundColor: '#ffffff',
99+
borderTopLeftRadius: 16,
100+
borderTopRightRadius: 16,
101+
overflow: 'hidden',
102+
alignSelf: 'stretch',
103+
height: '92%',
104+
shadowColor: '#000',
105+
shadowOffset: { width: 0, height: -3 },
106+
shadowOpacity: 0.1,
107+
shadowRadius: 4,
108+
elevation: 5,
109+
};
110+
111+
export default defaultStyles;

0 commit comments

Comments
 (0)