From c9c0249d4fc7586fc260fbe4774dccbffb3121ba Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Thu, 1 Nov 2018 05:31:38 +0100 Subject: [PATCH] React native error handling (#350) * added react native error handler package * implement Mixpanel error tracking --- app/package-lock.json | 5 +++ app/package.json | 1 + app/src/index.js | 9 +++++ .../lbry/build/templates/build.tmpl.gradle | 8 ++++ .../lbry/build/templates/settings.tmpl.gradle | 2 + recipes/pyjnius/__init__.py | 5 +-- .../browser/reactmodules/MixpanelModule.java | 37 +++++++++++++++++-- 7 files changed, 60 insertions(+), 7 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index dac133e4..ddf5ec02 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -5272,6 +5272,11 @@ "react-native-drawer-layout": "1.3.2" } }, + "react-native-exception-handler": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-native-exception-handler/-/react-native-exception-handler-2.9.0.tgz", + "integrity": "sha512-XRHhGH5aM4lSenX4zZBa07JaszJGXeF8cv1KY314Q4qJWOihKWLpkdvwqwsBieZ2iy8DPhdAVioQzw8JLD/Okw==" + }, "react-native-fast-image": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.0.3.tgz", diff --git a/app/package.json b/app/package.json index 3c61ce67..3e0a86e2 100644 --- a/app/package.json +++ b/app/package.json @@ -13,6 +13,7 @@ "react": "16.2.0", "react-native": "0.55.3", "react-native-country-picker-modal": "^0.6.2", + "react-native-exception-handler": "2.9.0", "react-native-fast-image": "^5.0.3", "react-native-fetch-blob": "^0.10.8", "react-native-image-zoom-viewer": "^2.2.5", diff --git a/app/src/index.js b/app/src/index.js index 94012017..a01895ff 100644 --- a/app/src/index.js +++ b/app/src/index.js @@ -1,4 +1,5 @@ import React from 'react'; +import { setJSExceptionHandler } from 'react-native-exception-handler'; import { Provider, connect } from 'react-redux'; import { AppRegistry, @@ -31,6 +32,14 @@ import moment from 'moment'; import settingsReducer from './redux/reducers/settings'; import thunk from 'redux-thunk'; +const globalExceptionHandler = (error, isFatal) => { + if (error && NativeModules.Mixpanel) { + NativeModules.Mixpanel.logException(isFatal, error.message ? error.message : "No message", error); + } +}; +setJSExceptionHandler(globalExceptionHandler, true); + + function isFunction(object) { return typeof object === 'function'; } diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle index 5d8b782c..008bfcf3 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle @@ -72,6 +72,13 @@ android { } } +ext { + compileSdkVersion = {{ android_api }} + buildToolsVersion = '{{ build_tools_version }}' + minSdkVersion = {{ args.min_sdk_version }} + targetSdkVersion = {{ android_api }} +} + subprojects { afterEvaluate {project -> if (project.hasProperty("android")) { @@ -84,6 +91,7 @@ subprojects { } dependencies { + compile project(':react-native-exception-handler') compile project(':react-native-fast-image') compile project(':react-native-fetch-blob') compile project(':react-native-video') diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle b/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle index aeb014f7..4f1d2181 100644 --- a/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle +++ b/p4a/pythonforandroid/bootstraps/lbry/build/templates/settings.tmpl.gradle @@ -1,4 +1,6 @@ rootProject.name = 'browser' +include ':react-native-exception-handler' +project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android') include ':react-native-fast-image' project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-fast-image/android') include ':react-native-fetch-blob' diff --git a/recipes/pyjnius/__init__.py b/recipes/pyjnius/__init__.py index 90d63a86..331f4810 100644 --- a/recipes/pyjnius/__init__.py +++ b/recipes/pyjnius/__init__.py @@ -9,11 +9,10 @@ class PyjniusRecipe(CythonRecipe): version = '1.1.3' url = 'https://github.com/kivy/pyjnius/archive/{version}.zip' name = 'pyjnius' - depends = [('python2', 'python3crystax'), ('genericndkbuild', 'sdl2', 'sdl'), 'six'] + depends = [('python2', 'python3crystax'), 'genericndkbuild', 'six'] site_packages_name = 'jnius' - patches = [('sdl2_jnienv_getter.patch', will_build('sdl2')), - ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))] + patches = [('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))] def postbuild_arch(self, arch): super(PyjniusRecipe, self).postbuild_arch(arch) diff --git a/src/main/java/io/lbry/browser/reactmodules/MixpanelModule.java b/src/main/java/io/lbry/browser/reactmodules/MixpanelModule.java index 33d9d8e9..57874d14 100644 --- a/src/main/java/io/lbry/browser/reactmodules/MixpanelModule.java +++ b/src/main/java/io/lbry/browser/reactmodules/MixpanelModule.java @@ -1,6 +1,7 @@ package io.lbry.browser.reactmodules; import android.content.Context; +import android.widget.Toast; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -23,7 +24,7 @@ public class MixpanelModule extends ReactContextBaseJavaModule { "93b81fb957cb0ddcd3198c10853a6a95"; /* Production */ private Context context; - + private MixpanelAPI mixpanel; public MixpanelModule(ReactApplicationContext reactContext) { @@ -36,7 +37,7 @@ public class MixpanelModule extends ReactContextBaseJavaModule { public String getName() { return "Mixpanel"; } - + @ReactMethod public void track(String name, ReadableMap payload) { JSONObject props = new JSONObject(); @@ -48,11 +49,39 @@ public class MixpanelModule extends ReactContextBaseJavaModule { } } } catch (JSONException e) { - // Cannot use props. Stick with empty props. + // Cannot use props. Stick with empty props. } - + if (mixpanel != null) { mixpanel.track(name, props); } } + + @ReactMethod + public void logException(boolean fatal, String message, ReadableMap payload) { + JSONObject props = new JSONObject(); + try { + props.put("Message", message); + if (payload != null) { + HashMap payloadMap = payload.toHashMap(); + for (Map.Entry entry : payloadMap.entrySet()) { + props.put(entry.getKey(), entry.getValue()); + } + } + } catch (JSONException e) { + // Cannot use props. Stick with empty props. + } + + if (mixpanel != null) { + mixpanel.track(fatal ? "Exception" : "Warning", props); + } + + if (fatal) { + Toast.makeText(context, + "An application error occurred which has been automatically logged. " + + "If you keep seeing this message, please provide feedback to the LBRY " + + "team by emailing hello@lbry.io.", + Toast.LENGTH_LONG).show(); + } + } }