Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fe3dc15
Disable bouncing
antonis Jan 30, 2025
72eef2d
Add modal ui appearance
antonis Jan 30, 2025
ccc808b
Update snapshot tests
antonis Jan 30, 2025
fd47fd2
Fix bottom margin
antonis Jan 30, 2025
dcc5d3b
Merge branch 'feedback-ui' into antonis/feedback-modal-ui
antonis Jan 31, 2025
9ecd8a2
Fix sheet height
antonis Jan 31, 2025
05f94f8
Remove extra modal border
antonis Jan 31, 2025
d88a599
Do not expose modal styles
antonis Jan 31, 2025
ce1de86
Animate background color
antonis Jan 31, 2025
a7a4e56
Avoid keyboard in modal
antonis Jan 31, 2025
8779886
Merge branch 'feedback-ui' into antonis/feedback-modal-ui
antonis Feb 3, 2025
ae80f7d
Merge branch 'feedback-ui' into antonis/feedback-modal-ui
antonis Feb 7, 2025
488658e
Merge branch 'feedback-ui' into antonis/feedback-modal-ui
antonis Feb 10, 2025
c4d502e
Use Image Picker interface matching `expo-image-picker` and `react-na…
antonis Feb 11, 2025
1b74b45
Update samples to pass the ImagePicker library implementation
antonis Feb 11, 2025
1d8a0db
Merge branch 'feedback-ui' into antonis/feedback-ui-imagepicker-integ…
antonis Feb 11, 2025
a0f4a77
Get image data from uri
antonis Feb 11, 2025
357dea8
Add early return and dev note
antonis Feb 11, 2025
192220b
Adds tests
antonis Feb 11, 2025
c3991ff
Adds sample expo plugin configuration
antonis Feb 11, 2025
91e96de
Merge branch 'feedback-ui' into antonis/feedback-ui-imagepicker-integ…
antonis Feb 11, 2025
6666cf6
Update media type for expo
antonis Feb 12, 2025
d1e5107
Update media type for rn
antonis Feb 12, 2025
d780fc1
Add native implementation for getDataFromUri
antonis Feb 13, 2025
85dff80
Bumped to the latest react-native-image-picker version 8
antonis Feb 13, 2025
e92fea6
Add missing null in return type
antonis Feb 13, 2025
becede1
Pull down to cancel on iOS
antonis Feb 13, 2025
529b703
Merge branch 'feedback-ui' into antonis/feedback-ios-pull-down-to-cancel
antonis Feb 14, 2025
3238fe0
On Android allow pulling down only from the top to avoid breaking nat…
antonis Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add native implementation for getDataFromUri
  • Loading branch information
antonis committed Feb 13, 2025
commit d780fc10fbcf24dc287de127b3215024f15942ba
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
import android.util.SparseIntArray;
import androidx.core.app.FrameMetricsAggregator;
import androidx.fragment.app.FragmentActivity;
Expand Down Expand Up @@ -72,6 +73,7 @@
import io.sentry.vendor.Base64;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
Expand Down Expand Up @@ -970,6 +972,39 @@ public String fetchNativePackageName() {
return packageInfo.packageName;
}

public void getDataFromUri(String uri, Promise promise) {
try {
Uri contentUri = Uri.parse(uri);
try (InputStream is =
getReactApplicationContext().getContentResolver().openInputStream(contentUri)) {
if (is == null) {
String msg = "File not found for uri: " + uri;
logger.log(SentryLevel.ERROR, msg);
promise.reject(new Exception(msg));
return;
}

ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len;
while ((len = is.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
byte[] byteArray = byteBuffer.toByteArray();
WritableArray jsArray = Arguments.createArray();
for (byte b : byteArray) {
jsArray.pushInt(b & 0xFF);
}
promise.resolve(jsArray);
}
} catch (IOException e) {
String msg = "Error reading uri: " + uri + ": " + e.getMessage();
logger.log(SentryLevel.ERROR, msg);
promise.reject(new Exception(msg));
}
}

public void crashedLastRun(Promise promise) {
promise.resolve(Sentry.isCrashedLastRun());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,9 @@ public void crashedLastRun(Promise promise) {
public void getNewScreenTimeToDisplay(Promise promise) {
this.impl.getNewScreenTimeToDisplay(promise);
}

@Override
public void getDataFromUri(String uri, Promise promise) {
this.impl.getDataFromUri(uri, promise);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ public String fetchNativePackageName() {
return this.impl.fetchNativePackageName();
}

@ReactMethod
public void getDataFromUri(String uri, Promise promise) {
this.impl.getDataFromUri(uri, promise);
}

@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
// Not used on Android
Expand Down
29 changes: 29 additions & 0 deletions packages/core/ios/RNSentry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,35 @@ - (NSDictionary *)fetchNativeStackFramesBy:(NSArray<NSNumber *> *)instructionsAd
#endif
}

RCT_EXPORT_METHOD(getDataFromUri
: (NSString *_Nonnull)uri resolve
: (RCTPromiseResolveBlock)resolve rejecter
: (RCTPromiseRejectBlock)reject)
{
#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST
NSURL *fileURL = [NSURL URLWithString:uri];
if (![fileURL isFileURL]) {
reject(@"SentryReactNative", @"The provided URI is not a valid file:// URL", nil);
return;
}
NSError *error = nil;
NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:0 error:&error];
if (error || !fileData) {
reject(@"SentryReactNative", @"Failed to read file data", error);
return;
}
NSMutableArray *byteArray = [NSMutableArray arrayWithCapacity:fileData.length];
const unsigned char *bytes = (const unsigned char *)fileData.bytes;

for (NSUInteger i = 0; i < fileData.length; i++) {
[byteArray addObject:@(bytes[i])];
}
resolve(byteArray);
#else
resolve(nil);
#endif
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId)
{
#if SENTRY_TARGET_REPLAY_SUPPORTED
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/js/NativeRNSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface Spec extends TurboModule {
captureReplay(isHardCrash: boolean): Promise<string | undefined | null>;
getCurrentReplayId(): string | undefined | null;
crashedLastRun(): Promise<boolean | undefined | null>;
getDataFromUri(uri: string): Promise<number[]>;
}

export type NativeStackFrame = {
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/js/feedback/FeedbackForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ import {
View
} from 'react-native';

import { NATIVE } from './../wrapper';
import { sentryLogo } from './branding';
import { defaultConfiguration } from './defaults';
import defaultStyles from './FeedbackForm.styles';
import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles, FeedbackGeneralConfiguration, FeedbackTextConfiguration, ImagePickerConfiguration } from './FeedbackForm.types';
import { getDataFromUri, isValidEmail } from './utils';
import { isValidEmail } from './utils';

/**
* @beta
Expand Down Expand Up @@ -126,11 +127,15 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
if (result.assets && result.assets.length > 0) {
const filename = result.assets[0].fileName;
const imageUri = result.assets[0].uri;
getDataFromUri(imageUri).then((data) => {
this.setState({ filename, attachment: data });
NATIVE.getDataFromUri(imageUri).then((data) => {
if (data != null) {
this.setState({ filename, attachment: data });
} else {
logger.error('Failed to read image data from uri:', imageUri);
}
})
.catch((error) => {
logger.error("Error:", error);
logger.error('Failed to read image data from uri:', imageUri, 'error: ', error);
});
}
} else {
Expand Down
23 changes: 0 additions & 23 deletions packages/core/src/js/feedback/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,3 @@ export const isValidEmail = (email: string): boolean => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(email);
};

/**
* Reads a file from a URI and returns a UInt8Array of its data.
* @param uri The file URI.
* @returns A Promise resolving to a UInt8Array.
*/
export async function getDataFromUri(uri: string): Promise<Uint8Array> {
const response = await fetch(uri);
const blob = await response.blob();

return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
if (reader.result instanceof ArrayBuffer) {
resolve(new Uint8Array(reader.result));
} else {
reject(new Error('Failed to read file as UInt8Array'));
}
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
}
15 changes: 15 additions & 0 deletions packages/core/src/js/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ interface SentryNativeWrapper {

crashedLastRun(): Promise<boolean | null>;
getNewScreenTimeToDisplay(): Promise<number | null | undefined>;

getDataFromUri(uri: string): Promise<Uint8Array>;
}

const EOL = utf8ToBytes('\n');
Expand Down Expand Up @@ -702,6 +704,19 @@ export const NATIVE: SentryNativeWrapper = {
return RNSentry.getNewScreenTimeToDisplay();
},

async getDataFromUri(uri: string): Promise<Uint8Array | null> {
if (!this.enableNative || !this._isModuleLoaded(RNSentry)) {
return null;
}
try {
const data: number[] = await RNSentry.getDataFromUri(uri);
return new Uint8Array(data);
} catch (error) {
logger.error('Error:', error);
return null;
}
},

/**
* Gets the event from envelopeItem and applies the level filter to the selected event.
* @param data An envelope item containing the event.
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/mockWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const NATIVE: MockInterface<NativeType> = {

crashedLastRun: jest.fn(),
getNewScreenTimeToDisplay: jest.fn().mockResolvedValue(42),
getDataFromUri: jest.fn(),
};

NATIVE.isNativeAvailable.mockReturnValue(true);
Expand Down
Loading