Skip to content

(feat) Add Attachments #2463

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ef71954
(feat) Add Attachments
krystofwoldrich Sep 8, 2022
b7afc14
Add attachment header to ios
krystofwoldrich Sep 9, 2022
42c67eb
Add basic attachment support to ios, fix lint
krystofwoldrich Sep 9, 2022
ec39b54
Remove propagation of attachments to native sdks
krystofwoldrich Sep 12, 2022
c2b06e9
Add attachment processing for android
krystofwoldrich Sep 12, 2022
eb2ad99
Add new captureEnvelope bridge draft
krystofwoldrich Sep 12, 2022
15ee4b7
Add capture envelope native implementation
krystofwoldrich Sep 13, 2022
0ff0f1d
Remove getStringBytesLength from bridge
krystofwoldrich Sep 13, 2022
43b3d10
Fix tests
krystofwoldrich Sep 13, 2022
d98a589
Fix lint, add vendor buffer desc
krystofwoldrich Sep 13, 2022
5c317cc
Add utf8 tests
krystofwoldrich Sep 13, 2022
57de079
Fix changelog
krystofwoldrich Sep 13, 2022
20ce45c
Remove Buffer
krystofwoldrich Sep 13, 2022
681927a
Add first draft asset attachment example
krystofwoldrich Sep 13, 2022
27e7b92
Add ios attachments example, fix js sample dep on Buffer
krystofwoldrich Sep 14, 2022
ed285cc
Fix lint
krystofwoldrich Sep 14, 2022
cb91e25
Add simple byte size utf8 check
krystofwoldrich Sep 14, 2022
89893cf
Update CHANGELOG.md
krystofwoldrich Sep 14, 2022
d55170d
Refactor sample HomeScreen attachment example
krystofwoldrich Sep 14, 2022
9147cb5
Add buffer license
krystofwoldrich Sep 15, 2022
a0a7e1b
Remove options store ignored log
krystofwoldrich Sep 15, 2022
5906a26
Add missing envelope message test and fix breadcrumbs
krystofwoldrich Sep 15, 2022
c9fbe21
Merge remote-tracking branch 'origin/main' into krystofwoldrich/attac…
krystofwoldrich Sep 15, 2022
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add attachments support ([#2463](https://github.com/getsentry/sentry-react-native/pull/2463))

## 4.3.1

### Fixes
Expand Down
21 changes: 9 additions & 12 deletions android/src/main/java/io/sentry/react/RNSentryModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableMap;
Expand Down Expand Up @@ -292,7 +293,12 @@ public void fetchNativeFrames(Promise promise) {
}

@ReactMethod
public void captureEnvelope(String envelope, Promise promise) {
public void captureEnvelope(ReadableArray rawBytes, ReadableMap options, Promise promise) {
byte bytes[] = new byte[rawBytes.size()];
for (int i = 0; i < rawBytes.size(); i++) {
bytes[i] = (byte) rawBytes.getInt(i);
}

try {
final String outboxPath = HubAdapter.getInstance().getOptions().getOutboxPath();

Expand All @@ -302,24 +308,15 @@ public void captureEnvelope(String envelope, Promise promise) {
} else {
File installation = new File(outboxPath, UUID.randomUUID().toString());
try (FileOutputStream out = new FileOutputStream(installation)) {
out.write(envelope.getBytes(Charset.forName("UTF-8")));
out.write(bytes);
}
}
} catch (Throwable ignored) {
logger.severe("Error reading envelope");
logger.severe("Error while writing envelope to outbox.");
}
promise.resolve(true);
}

@ReactMethod
public void getStringBytesLength(String payload, Promise promise) {
try {
promise.resolve(payload.getBytes("UTF-8").length);
} catch (UnsupportedEncodingException e) {
promise.reject(e);
}
}

private static PackageInfo getPackageInfo(Context ctx) {
try {
return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0);
Expand Down
57 changes: 22 additions & 35 deletions ios/RNSentry.m
Original file line number Diff line number Diff line change
Expand Up @@ -270,47 +270,34 @@ - (void)setEventEnvironmentTag:(SentryEvent *)event
});
}

RCT_EXPORT_METHOD(captureEnvelope:(NSDictionary * _Nonnull)envelopeDict
RCT_EXPORT_METHOD(captureEnvelope:(NSArray * _Nonnull)bytes
options: (NSDictionary * _Nonnull)options
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
if ([NSJSONSerialization isValidJSONObject:envelopeDict]) {
SentrySdkInfo *sdkInfo = [[SentrySdkInfo alloc] initWithDict:envelopeDict[@"header"]];
SentryId *eventId = [[SentryId alloc] initWithUUIDString:envelopeDict[@"header"][@"event_id"]];
SentryTraceContext *traceContext = [[SentryTraceContext alloc] initWithDict:envelopeDict[@"header"][@"trace"]];
SentryEnvelopeHeader *envelopeHeader = [[SentryEnvelopeHeader alloc] initWithId:eventId sdkInfo:sdkInfo traceContext:traceContext];

NSError *error;
NSData *envelopeItemData = [NSJSONSerialization dataWithJSONObject:envelopeDict[@"payload"] options:0 error:&error];
if (nil != error) {
reject(@"SentryReactNative", @"Cannot serialize event", error);
}

NSString *itemType = envelopeDict[@"payload"][@"type"];
if (itemType == nil) {
// Default to event type.
itemType = @"event";
}

SentryEnvelopeItemHeader *envelopeItemHeader = [[SentryEnvelopeItemHeader alloc] initWithType:itemType length:envelopeItemData.length];
SentryEnvelopeItem *envelopeItem = [[SentryEnvelopeItem alloc] initWithHeader:envelopeItemHeader data:envelopeItemData];
NSMutableData *data = [[NSMutableData alloc] initWithCapacity: [bytes count]];
for(NSNumber *number in bytes) {
char byte = [number charValue];
[data appendBytes: &byte length: 1];
}

SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader singleItem:envelopeItem];
SentryEnvelope *envelope = [PrivateSentrySDKOnly envelopeWithData:data];
if (envelope == nil) {
reject(@"SentryReactNative",@"Failed to parse envelope from byte array.", nil);
return;
}

#if DEBUG
#if DEBUG
[PrivateSentrySDKOnly captureEnvelope:envelope];
#else
if (options[@'store']) {
// Storing to disk happens asynchronously with captureEnvelope
[PrivateSentrySDKOnly storeEnvelope:envelope];
} else {
[PrivateSentrySDKOnly captureEnvelope:envelope];
#else
if ([envelopeDict[@"payload"][@"level"] isEqualToString:@"fatal"]) {
// Storing to disk happens asynchronously with captureEnvelope
[PrivateSentrySDKOnly storeEnvelope:envelope];
} else {
[PrivateSentrySDKOnly captureEnvelope:envelope];
}
#endif
resolve(@YES);
} else {
reject(@"SentryReactNative", @"Cannot serialize event", nil);
}
}
#endif
resolve(@YES);
}

RCT_EXPORT_METHOD(setUser:(NSDictionary *)user
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.sentry.sample;
import android.content.Context;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;

import java.io.InputStream;

public class AssetsModule extends ReactContextBaseJavaModule {

AssetsModule(ReactApplicationContext context) {
super(context);
}

@Override
public String getName() {
return "AssetsModule";
}

@ReactMethod
public void getExampleAssetData(Promise promise) {
try {
InputStream stream = this.getReactApplicationContext().getResources().getAssets()
.open("logo_mini.png");
int size = stream.available();
byte[] buffer = new byte[size];
stream.read(buffer);
stream.close();
WritableArray array = new WritableNativeArray();
for (int i = 0; i < size; i++) {
array.pushInt(buffer[i]);
}
promise.resolve(array);
} catch (Exception e) {
promise.reject(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public boolean getUseDeveloperSupport() {
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new SamplePackage());

for (ReactPackage pkg : packages) {
if (pkg instanceof RNSentryPackage) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.sentry.sample;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SamplePackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new AssetsModule(reactContext));

return modules;
}

}
4 changes: 2 additions & 2 deletions sample/ios/sample/Images.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"data" : [
{
"compression-type" : "none",
"filename" : "logo_mini.png",
"idiom" : "universal",
"universal-type-identifier" : "image\/png"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions sample/ios/sample/RCTAssetsModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#import <React/RCTBridgeModule.h>
@interface RCTAssetsModule : NSObject <RCTBridgeModule>
@end
28 changes: 28 additions & 0 deletions sample/ios/sample/RCTAssetsModule.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import "RCTAssetsModule.h"

@implementation RCTAssetsModule

RCT_EXPORT_METHOD(getExampleAssetData: (RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSDataAsset *data = [[NSDataAsset alloc] initWithName:@"ExampleBinaryData"];
if (data == nil) {
reject(@"SampleSentryReactNative",@"Failed to load exmaple binary data asset.", nil);
}

NSMutableArray *array = [NSMutableArray arrayWithCapacity:data.data.length];

const char *bytes = [data.data bytes];

for (int i = 0; i < [data.data length]; i++)
{
[array addObject:[[NSNumber alloc] initWithChar:bytes[i]]];
}

resolve(array);
}

RCT_EXPORT_MODULE(AssetsModule);

@end

4 changes: 2 additions & 2 deletions sample/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ Sentry.init({
// Replace the example DSN below with your own DSN:
dsn: SENTRY_INTERNAL_DSN,
debug: true,
beforeSend: (e) => {
console.log('Event beforeSend:', e);
beforeSend: (e, hint) => {
console.log('Event beforeSend:', e, 'hint:', hint);
return e;
},
// This will be called with a boolean `didCallNativeInit` when the native SDK has been contacted.
Expand Down
35 changes: 34 additions & 1 deletion sample/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect} from 'react';
import {
Image,
ScrollView,
Expand All @@ -16,6 +16,10 @@ import * as Sentry from '@sentry/react-native';
import { getTestProps } from '../../utils/getTestProps';
import { SENTRY_INTERNAL_DSN } from '../dsn';
import { SeverityLevel } from '@sentry/types';
import { Scope } from '@sentry/react-native';
import { NativeModules } from 'react-native';

const {AssetsModule} = NativeModules;

interface Props {
navigation: StackNavigationProp<any, 'HomeScreen'>;
Expand Down Expand Up @@ -106,6 +110,12 @@ const HomeScreen = (props: Props) => {
console.log('Test scope properties were set.');
};

const [data, setData] = React.useState<Uint8Array>(null);
useEffect(() => {
AssetsModule.getExampleAssetData()
.then((asset: number[]) => setData(new Uint8Array(asset)));
}, []);

return (
<>
<StatusBar barStyle="dark-content" />
Expand Down Expand Up @@ -223,6 +233,29 @@ const HomeScreen = (props: Props) => {
</Text>
</TouchableOpacity>
</Sentry.ErrorBoundary>
<View style={styles.spacer} />
<TouchableOpacity
onPress={async () => {
Sentry.configureScope((scope: Scope) => {
scope.addAttachment({
data: 'Attachment content',
filename: 'attachment.txt',
});
scope.addAttachment({data: data, filename: 'logo.png'});
console.log('Sentry attachment added.');
});
}}>
<Text style={styles.buttonText}>Add attachment</Text>
</TouchableOpacity>
<View style={styles.spacer} />
<TouchableOpacity
onPress={async () => {
Sentry.configureScope((scope: Scope) => {
console.log(scope.getAttachments());
});
}}>
<Text style={styles.buttonText}>Get attachment</Text>
</TouchableOpacity>
</View>
<View style={styles.buttonArea}>
<TouchableOpacity
Expand Down
1 change: 1 addition & 0 deletions src/js/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
this._browserClient = new BrowserClient({
dsn: options.dsn,
transport: options.transport,
transportOptions: options.transportOptions,
stackParser: options.stackParser || defaultStackParser,
integrations: [],
});
Expand Down
11 changes: 4 additions & 7 deletions src/js/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@ export interface SentryNativeBridgeModule {

addBreadcrumb(breadcrumb: Breadcrumb): void;
captureEnvelope(
payload:
| string
| {
header: Record<string, unknown>;
payload: Record<string, unknown>;
}
bytes: number[],
options: {
store: boolean,
},
): PromiseLike<boolean>;
clearBreadcrumbs(): void;
crash(): void;
Expand All @@ -53,7 +51,6 @@ export interface SentryNativeBridgeModule {
fetchNativeDeviceContexts(): PromiseLike<NativeDeviceContextsResponse>;
fetchNativeAppStart(): PromiseLike<NativeAppStartResponse | null>;
fetchNativeFrames(): PromiseLike<NativeFramesResponse | null>;
getStringBytesLength(str: string): Promise<number>;
initNativeSdk(options: ReactNativeOptions): Promise<boolean>;
setUser(
defaultUserKeys: SerializedObject | null,
Expand Down
4 changes: 3 additions & 1 deletion src/js/integrations/reactnativeerrorhandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export class ReactNativeErrorHandlers implements Integration {

const currentHub = getCurrentHub();
const client = currentHub.getClient<ReactNativeClient>();
const scope = currentHub.getScope();

if (!client) {
logger.error(
Expand All @@ -201,7 +202,8 @@ export class ReactNativeErrorHandlers implements Integration {
const options = client.getOptions();

const event = await client.eventFromException(error, {
originalException: error
originalException: error,
attachments: scope?.getAttachments(),
});

if (isFatal) {
Expand Down
Loading