Add Sentry React Native SDK

parent fdbae6d8
{
"presets": ["react-native"],
"presets": ["react-native", "react-native-dotenv"],
"plugins": ["@babel/plugin-proposal-object-rest-spread", "@babel/transform-regenerator"]
}
\ No newline at end of file
......@@ -60,3 +60,5 @@ buck-out/
*.jsbundle
bundle.js
sentry.properties
.env
\ No newline at end of file
......@@ -3,6 +3,7 @@ import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import Snackbar from 'react-native-snackbar';
import { AsyncStorage } from 'react-native';
import { Sentry } from 'react-native-sentry';
import loginSaga, { DISPLAYNAMEKEY, PHOTOKEY, TOKENKEY, USERNAMEKEY } from '../../app/sagas/login';
import { apiRequest } from '../../app/utils/url';
......@@ -27,6 +28,13 @@ jest.mock('../../app/utils/url', () => ({
tokenSelector: () => 'token',
}));
jest.mock('react-native-sentry', () => ({
Sentry: {
setUserContext: () => {},
captureException: () => {},
},
}));
describe('login saga', () => {
const error = new Error('error');
......@@ -42,6 +50,7 @@ describe('login saga', () => {
it('should put the result data when the request succeeds', () => expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['token-auth'] }), { token: 'abc123' }],
[matchers.call.like({ fn: Sentry.setUserContext }), {}],
])
.put(loginActions.success('username', 'abc123'))
.put(loginActions.profile('abc123'))
......@@ -51,6 +60,7 @@ describe('login saga', () => {
it('should show a snackbar when the request succeeds', () => expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['token-auth'] }), { token: 'abc123' }],
[matchers.call.like({ fn: Sentry.setUserContext }), {}],
])
.dispatch(loginActions.login('username', 'password'))
.silentRun()
......@@ -64,6 +74,7 @@ describe('login saga', () => {
expectSaga(loginSaga)
.provide([
[matchers.call.like({ fn: apiRequest, args: ['token-auth'] }), { token: 'abc123' }],
[matchers.call.like({ fn: Sentry.setUserContext }), {}],
])
.dispatch(loginActions.login('username', 'password'))
.silentRun()
......
......@@ -78,6 +78,7 @@ project.ext.react = [
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-sentry/sentry.gradle"
/**
* Set this to true to create two separate APKs instead of one:
......@@ -145,6 +146,7 @@ android {
}
dependencies {
implementation project(':react-native-sentry')
implementation project(':react-native-locale-detector')
implementation project(':react-native-vector-icons')
implementation project(':react-native-snackbar')
......
......@@ -3,6 +3,7 @@ package com.thaliapp;
import android.app.Application;
import com.facebook.react.ReactApplication;
import io.sentry.RNSentryPackage;
import com.azendoo.reactnativesnackbar.SnackbarPackage;
import com.i18n.reactnativei18n.ReactNativeI18n;
import com.evollu.react.fcm.FIRMessagingPackage;
......@@ -28,6 +29,7 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNSentryPackage(),
new SnackbarPackage(),
new ReactNativeI18n(),
new FIRMessagingPackage(),
......
rootProject.name = 'ThaliApp'
include ':react-native-sentry'
project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android')
include ':react-native-snackbar'
project(':react-native-snackbar').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-snackbar/android')
include ':react-native-locale-detector'
......
import {
call, put, select, takeEvery,
} from 'redux-saga/effects';
import { call, put, select, takeEvery } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { apiRequest, tokenSelector } from '../utils/url';
import * as navigationActions from '../actions/navigation';
......@@ -30,10 +29,11 @@ const calendar = function* calendar() {
partner: true,
}));
} catch (error) {
// Swallow the error
Sentry.captureException(error);
}
yield put(calendarActions.success(events.concat(partnerEvents)));
} catch (error) {
Sentry.captureException(error);
yield put(calendarActions.failure());
}
};
......
import {
call, put, takeEvery, select,
} from 'redux-saga/effects';
import { call, put, takeEvery, select } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { apiRequest, tokenSelector } from '../utils/url';
import * as eventActions from '../actions/event';
......@@ -36,6 +35,7 @@ const event = function* event(action) {
eventRegistrations,
));
} catch (error) {
Sentry.captureException(error);
yield put(eventActions.failure());
}
};
......
import { call, takeEvery, put } from 'redux-saga/effects';
import { AsyncStorage } from 'react-native';
import Snackbar from 'react-native-snackbar';
import { Sentry } from 'react-native-sentry';
import { apiRequest } from '../utils/url';
import * as loginActions from '../actions/login';
......@@ -78,15 +79,22 @@ const profile = function* profile(action) {
]);
yield put(loginActions.profileSuccess(userProfile.display_name, userProfile.avatar.medium));
} catch (error) {
Sentry.captureException(error);
// Swallow error
}
};
function* success({ payload }) {
const { username } = payload;
yield call(Sentry.setUserContext, { username });
}
const loginSaga = function* loginSaga() {
yield takeEvery(loginActions.LOGIN, login);
yield takeEvery(loginActions.LOGOUT, logout);
yield takeEvery(loginActions.PROFILE, profile);
yield takeEvery(loginActions.TOKEN_INVALID, tokenInvalid);
yield takeEvery(loginActions.SUCCESS, success);
};
export default loginSaga;
import { Dimensions } from 'react-native';
import {
call, put, select, takeEvery,
} from 'redux-saga/effects';
import { call, put, select, takeEvery } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { TOTAL_BAR_HEIGHT } from '../ui/components/standardHeader/style/StandardHeader';
import { memberSize } from '../ui/screens/memberList/style/MemberList';
......@@ -38,6 +37,7 @@ const members = function* members(action) {
const response = yield call(apiRequest, 'members', data, params);
yield put(memberActions.success(response.results, response.next, keywords));
} catch (error) {
Sentry.captureException(error);
yield put(memberActions.failure());
}
};
......@@ -61,6 +61,7 @@ const more = function* more(action) {
const responseJson = yield fetch(url, data).then(response => response.json());
yield put(memberActions.moreSuccess(responseJson.results, responseJson.next));
} catch (error) {
Sentry.captureException(error);
yield put(memberActions.moreSuccess([], null));
}
};
......
import {
call, put, takeEvery, select,
} from 'redux-saga/effects';
import { call, put, takeEvery, select } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { apiRequest, tokenSelector } from '../utils/url';
import * as pizzaActions from '../actions/pizza';
......@@ -33,6 +32,7 @@ const retrievePizzaInfo = function* retrievePizzaInfo() {
if (error.response !== null && error.response.status === NOT_FOUND) {
yield put(pizzaActions.success(event, null, pizzaList));
} else {
Sentry.captureException(error);
yield put(pizzaActions.failure());
}
}
......@@ -40,6 +40,7 @@ const retrievePizzaInfo = function* retrievePizzaInfo() {
if (error.response !== null && error.response.status === NOT_FOUND) {
yield put(pizzaActions.success(null, null, []));
} else {
Sentry.captureException(error);
yield put(pizzaActions.failure());
}
}
......@@ -60,6 +61,7 @@ const cancel = function* cancel() {
yield call(apiRequest, 'pizzas/orders/me', data);
yield put(pizzaActions.cancelSuccess());
} catch (error) {
Sentry.captureException(error);
yield put(pizzaActions.failure());
}
};
......@@ -84,6 +86,7 @@ const order = function* order(action) {
const orderData = yield call(apiRequest, route, data);
yield put(pizzaActions.orderSuccess(orderData));
} catch (error) {
Sentry.captureException(error);
yield put(pizzaActions.failure());
}
};
......
import { call, put, takeEvery } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { apiRequest } from '../utils/url';
import * as profileActions from '../actions/profile';
......@@ -23,6 +24,7 @@ const profile = function* profile(action) {
const profileData = yield call(apiRequest, `members/${member}`, data);
yield put(profileActions.success(profileData));
} catch (error) {
Sentry.captureException(error);
yield put(profileActions.failure());
}
};
......
import { call, takeEvery, select } from 'redux-saga/effects';
import { Platform } from 'react-native';
import FCM from 'react-native-fcm';
import { Sentry } from 'react-native-sentry';
import { apiRequest, tokenSelector } from '../utils/url';
import * as pushNotificationsActions from '../actions/pushNotifications';
......@@ -43,6 +44,7 @@ const register = function* register() {
try {
yield call(apiRequest, 'devices', data);
} catch (err) {
Sentry.captureException(err);
// eat error, om nom nom
}
};
......
......@@ -3,6 +3,7 @@ import {
call, put, select, takeEvery,
} from 'redux-saga/effects';
import Snackbar from 'react-native-snackbar';
import { Sentry } from 'react-native-sentry';
import { apiRequest, tokenSelector } from '../utils/url';
......@@ -38,6 +39,7 @@ const register = function* register(action) {
}
Snackbar.show({ title: 'Registration successful!' });
} catch (error) {
Sentry.captureException(error);
yield put(eventActions.failure());
}
};
......@@ -71,6 +73,7 @@ const update = function* update(action) {
yield delay(50);
Snackbar.show({ title: 'Successfully updated registration' });
} catch (error) {
Sentry.captureException(error);
yield put(registrationActions.failure());
}
};
......@@ -95,6 +98,7 @@ const cancel = function* cancel(action) {
yield call(apiRequest, `registrations/${registration}`, data);
Snackbar.show({ title: 'Successfully cancelled registration' });
} catch (error) {
Sentry.captureException(error);
// Swallow error for now
}
......@@ -122,6 +126,7 @@ const fields = function* fields(action) {
yield put(registrationActions.showFields(registration, response.fields));
yield put(eventActions.done());
} catch (error) {
Sentry.captureException(error);
yield put(eventActions.failure());
}
};
......
import {
call, put, select, takeEvery,
} from 'redux-saga/effects';
import { call, put, select, takeEvery } from 'redux-saga/effects';
import { Sentry } from 'react-native-sentry';
import { apiRequest, tokenSelector } from '../utils/url';
import * as welcomeActions from '../actions/welcome';
......@@ -29,6 +28,7 @@ const welcome = function* welcome() {
if (error.name === 'TokenInvalidError') {
yield put(loginActions.tokenInvalid());
}
Sentry.captureException(error);
yield put(welcomeActions.failure());
}
};
......
import { AppRegistry } from 'react-native';
import { Sentry } from 'react-native-sentry';
import { SENTRY_DSN } from 'react-native-dotenv';
import App from './app/app';
Sentry.config(SENTRY_DSN).install();
AppRegistry.registerComponent('ThaliApp', () => App);
// Copyright (c) 2015 Doe Pics Hit, Inc. All rights reserved.
#import <Foundation/Foundation.h>
#import <UIKit/UIApplication.h>
typedef NSString*(^BBReturnNSStringCallback)(void);
typedef BOOL (^BBReturnBooleanCallback)(void);
typedef void (^BBCallback)(void);
@interface BuddyBuildSDK : NSObject
// Deprecated
+ (void)setup:(id<UIApplicationDelegate>)bbAppDelegate;
/**
* Initialize the SDK
*
* This should be called at (or near) the start of the appdelegate
*/
+ (void)setup;
/*
* Associate arbitrary key/value pairs with your crash reports and user feedback
* which will be visible from the buddybuild dashboard
*/
+ (void)setMetadataObject:(id)object forKey:(NSString*)key;
/*
* Programatically trigger the screenshot feedback UI without pressing the screenshot buttons
* If you have screenshot feedback disabled through the buddybuild setting,
* you can still trigger it by calling this method
*/
+ (void)takeScreenshotAndShowFeedbackScreen;
/*
* If you distribute a build to someone with their email address, buddybuild can
* figure out who they are and attach their info to feedback and crash reports.
*
* However, if you send out a build to a mailing list, or through TestFlight or
* the App Store we are unable to infer who they are. If you see 'Unknown User'
* this is likely the cause.
* Often you'll know the identity of your user, for example, after they've
* logged in. You can provide buddybuild a callback to identify the current user.
*/
+ (void)setUserDisplayNameCallback:(BBReturnNSStringCallback)bbCallback;
/*
* You might have API keys and other secrets that your app needs to consume.
* However, you may not want to check these secrets into the source code.
*
* You can provide your secrets to buddybuild. Buddybuild can then expose them
* to you at build time through environment variables. These secrets can also be
* configured to be included into built app. We obfuscate the device keys to
* prevent unauthorized access.
*/
+ (NSString*)valueForDeviceKey:(NSString*)bbKey;
/*
* To temporarily disable screenshot interception you can provide a callback
* here.
*
* When screenshotting is turned on through a buddybuild setting, and no
* callback is provided then screenshotting is by default on.
*
* If screenshotting is disabled through the buddybuild setting, then this
* callback has no effect
*
*/
+ (void)setScreenshotAllowedCallback:(BBReturnBooleanCallback)bbCallback;
/*
* Once a piece of feedback is sent this callback will be called
* so you can take additional actions if necessary
*/
+ (void)setScreenshotFeedbackSentCallback:(BBCallback)bbCallback;
/*
* Once a crash report is sent this callback will be called
* so you can take additional actions if necessary
*/
+ (void)setCrashReportSentCallback:(BBCallback)bbCallback;
/*
* Buddybuild Build Number
*/
+ (NSString*)buildNumber;
/*
* Scheme
*/
+ (NSString*)scheme;
/*
* App ID
*/
+ (NSString*)appID;
/*
* Build ID
*/
+ (NSString*)buildID;
/*
* Build Configuration
*/
+ (NSString*)buildConfiguration;
/*
* Branch name for this build
*/
+ (NSString*)branchName;
/* Manually invoke the screenshot tutorial
* If you don't want it to appear on app launch, disable it in the
* dashboard by going to settings -> buddybuildSDK -> Feature Settings and turning off the screenshot tutorial
* You will be able to show it at any time from anywhere in your app
*/
+ (void)showScreenshotTutorial;
+ (void)crash;
/*
* Logs to the console only while the debugger is attached (when running in Xcode)
* They can be downloaded in crash instances and feedbacks in the dashboard
*/
+ (void)log:(NSString *)message;
/*
* Starts recording video when running a UI test case.
* Should be called after each "[[[XCUIApplication alloc] init] launch];" in your UI tests codebase.
* Only run in buddybuild while the UI tests run. It will not run locally, on real iOS devices or on TestFlight and App Store installs.
*/
+ (void)startUITests;
/*
* Stops recording video at the end of a UI test case.
* Should be called before each "[super tearDown];" in your UI tests codebase.
* Only run in buddybuild while the UI tests run. It will not run locally, on real iOS devices or on TestFlight and App Store installs.
*/
+ (void)stopUITests;
/*
* Should be called in your app delegate in -[UIApplication application:didReceiveRemoteNotification:fetchCompletionHandler].
* Only run in buddybuild while the UI tests run. It will not run locally, on real iOS devices or on TestFlight and App Store installs.
*/
+ (void)uiTestsDidReceiveRemoteNotification:(NSDictionary *)userInfo;
/*
* DEPRECATED IN SDK 1.0.16+, use setMetadataObject:forKey:
*/
+ (void)setCrashMetadataObject:(id)object forKey:(NSString*)key __deprecated_msg("Use setMetadataObject:forKey: instead");
/*
* DEPRECATED IN SDK 1.0.17+
*/
+ (NSString*)userEmail __deprecated_msg("No longer available");
@end
@interface UIView (BuddyBuildSDK)
// Certain features of buddybuild involve capturing the screen (either through a static screenshot, or as a video for instant replays in crash reporting or video feedback.
// Your app may contain certain sensitive customer information that you do not want to be included in the video.
// If you set this property to be true, this view will be redacted from the screen capture and blacked out
@property (nonatomic, assign) BOOL buddybuildViewIsPrivate;
@end
module BuddyBuildSDK {
umbrella header "Headers/BuddyBuildSDK.h"
export *
module * { export * }
}
......@@ -8,10 +8,10 @@ PODS:
- FirebaseAnalytics (4.0.5):
- FirebaseCore (~> 4.0)
- FirebaseInstanceID (~> 2.0)
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- nanopb (~> 0.3)
- FirebaseCore (4.0.13):
- GoogleToolboxForMac/NSData+zlib (~> 2.1)
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- FirebaseInstanceID (2.0.8):
- FirebaseCore (~> 4.0)
- FirebaseMessaging (2.0.8):
......@@ -23,7 +23,7 @@ PODS:
- GoogleToolboxForMac/Defines (2.1.3)
- GoogleToolboxForMac/Logger (2.1.3):
- GoogleToolboxForMac/Defines (= 2.1.3)
- GoogleToolboxForMac/NSData+zlib (2.1.3):
- "GoogleToolboxForMac/NSData+zlib (2.1.3)":
- GoogleToolboxForMac/Defines (= 2.1.3)
- nanopb (0.3.8):
- nanopb/decode (= 0.3.8)
......@@ -36,6 +36,17 @@ DEPENDENCIES:
- Firebase/Core
- Firebase/Messaging
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Firebase
- FirebaseAnalytics
- FirebaseCore
- FirebaseInstanceID
- FirebaseMessaging
- GoogleToolboxForMac
- nanopb
- Protobuf
SPEC CHECKSUMS:
Firebase: 710decbbc6d9d48530e9a5dba3209740c3532e05
FirebaseAnalytics: 5b02a63ead2c3f0259cfc7f15e053e440587ecf8
......@@ -48,4 +59,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 81d414439f1f341b58c306e954d202374221992d
COCOAPODS: 1.3.1
COCOAPODS: 1.5.3
This diff is collapsed.
......@@ -54,7 +54,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
......@@ -74,7 +73,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
......@@ -9,17 +9,15 @@
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RNSentry.h>
#import <React/RCTLinkingManager.h>
#import "RNFIRMessaging.h"
#import <BuddyBuildSDK/BuddyBuildSDK.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[BuddyBuildSDK setup];
[FIRApp configure];
[[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
......@@ -42,12 +40,14 @@
// $ curl http://localhost:8081/index.ios.bundle -o main.jsbundle
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#endif
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"ThaliApp"
initialProperties:nil
launchOptions:launchOptions];
[RNSentry installWithRootView:rootView];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
......
This diff is collapsed.
Markdown is supported
0% or