diff --git a/CHANGELOG.md b/CHANGELOG.md index 2287f6614..ec409f873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## [13.1.1](https://github.com/Instabug/Instabug-React-Native/compare/v13.0.4...dev) (JUN 6, 2024) + +### Fixed + +- Reading INSTABUG_APP_TOKEN from system environment when there is no default value ([#1232](https://github.com/Instabug/Instabug-React-Native/pull/1232)) + +### Changed + +- Bump Instabug iOS SDK to v13.1.0 ([#1227](https://github.com/Instabug/Instabug-React-Native/pull/1227)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/13.1.0). +- Bump Instabug android SDK to v13.1.1 ([#1228](https://github.com/Instabug/Instabug-React-Native/pull/1228)). [See release notes](https://github.com/Instabug/android/releases/tag/v13.1.0). + +### Added + +- Add support for passing a grouping fingerprint, error level, and user attributes to the `CrashReporting.reportError` non-fatals API ([#1194](https://github.com/Instabug/Instabug-React-Native/pull/1194)). + ## [13.0.5](https://github.com/Instabug/Instabug-React-Native/compare/v13.0.4...v13.0.5) (May 18, 2024) ### Changed @@ -26,7 +41,7 @@ - Bump Instabug iOS SDK to v13.0.0 ([#1189](https://github.com/Instabug/Instabug-React-Native/pull/1189)). [See release notes](https://github.com/instabug/instabug-ios/releases/tag/13.0.0). - Bump Instabug Android SDK to v13.0.0 ([#1188](https://github.com/Instabug/Instabug-React-Native/pull/1188)). [See release notes](https://github.com/Instabug/android/releases/tag/v13.0.0). -## [12.9.0](https://github.com/Instabug/Instabug-React-Native/compare/v12.8.0...v12.9.0) (April 2, 2024) +## [12.9.0](https://github.com/Instabug/Instabug-React-Native/compare/v12.8.0...12.9.0)(April 2, 2024) ### Added diff --git a/android/native.gradle b/android/native.gradle index 93cb37d50..4d5d47ed9 100644 --- a/android/native.gradle +++ b/android/native.gradle @@ -1,5 +1,5 @@ project.ext.instabug = [ - version: '13.0.3' + version: '13.1.1' ] dependencies { diff --git a/android/sourcemaps.gradle b/android/sourcemaps.gradle index f3fe5b59d..37875e070 100644 --- a/android/sourcemaps.gradle +++ b/android/sourcemaps.gradle @@ -91,7 +91,7 @@ String resolveVar(String name, String envKey, String defaultValue) { def env = System.getenv() def envValue = env.get(envKey) - if (envValue != null && envValue != defaultValue) { + if (envValue != null && defaultValue !=null && envValue != defaultValue) { project.logger.warn "Environment variable `${envKey}` might have incorrect value, " + "make sure this was intentional:\n" + " Environment Value: ${envValue}\n" + diff --git a/android/src/main/java/com/instabug/reactlibrary/ArgsRegistry.java b/android/src/main/java/com/instabug/reactlibrary/ArgsRegistry.java index 522bd0dd0..37f730cbe 100644 --- a/android/src/main/java/com/instabug/reactlibrary/ArgsRegistry.java +++ b/android/src/main/java/com/instabug/reactlibrary/ArgsRegistry.java @@ -4,6 +4,7 @@ import com.instabug.bug.BugReporting; import com.instabug.bug.invocation.Option; +import com.instabug.crash.models.IBGNonFatalException; import com.instabug.featuresrequest.ActionType; import com.instabug.library.InstabugColorTheme; import com.instabug.library.InstabugCustomTextPlaceHolder.Key; @@ -54,11 +55,19 @@ static Map getAll() { putAll(extendedBugReportStates); putAll(reproModes); putAll(sdkLogLevels); + putAll(nonFatalExceptionLevel); putAll(locales); putAll(placeholders); }}; } + public static ArgsMap nonFatalExceptionLevel = new ArgsMap() {{ + put("nonFatalErrorLevelCritical", IBGNonFatalException.Level.CRITICAL); + put("nonFatalErrorLevelError", IBGNonFatalException.Level.ERROR); + put("nonFatalErrorLevelWarning", IBGNonFatalException.Level.WARNING); + put("nonFatalErrorLevelInfo", IBGNonFatalException.Level.INFO); + }}; + static ArgsMap invocationEvents = new ArgsMap() {{ put("invocationEventNone", InstabugInvocationEvent.NONE); put("invocationEventShake", InstabugInvocationEvent.SHAKE); diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugCrashReportingModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugCrashReportingModule.java index 4bd84c0bd..080e6cf07 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugCrashReportingModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugCrashReportingModule.java @@ -2,11 +2,15 @@ import static com.instabug.reactlibrary.utils.InstabugUtil.getMethod; +import androidx.annotation.NonNull; + 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.ReadableMap; import com.instabug.crash.CrashReporting; +import com.instabug.crash.models.IBGNonFatalException; import com.instabug.library.Feature; import com.instabug.reactlibrary.utils.MainThreadHandler; @@ -14,6 +18,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -57,8 +63,8 @@ public void run() { * Send unhandled JS error object * * @param exceptionObject Exception object to be sent to Instabug's servers - * @param promise This makes sure that the RN side crashes the app only after the Android SDK - * finishes processing/handling the crash. + * @param promise This makes sure that the RN side crashes the app only after the Android SDK + * finishes processing/handling the crash. */ @ReactMethod public void sendJSCrash(final String exceptionObject, final Promise promise) { @@ -79,41 +85,64 @@ public void run() { * Send handled JS error object * * @param exceptionObject Exception object to be sent to Instabug's servers + * @param userAttributes (Optional) extra user attributes attached to the crash + * @param fingerprint (Optional) key used to customize how crashes are grouped together + * @param level different severity levels for errors */ @ReactMethod - public void sendHandledJSCrash(final String exceptionObject) { + public void sendHandledJSCrash(final String exceptionObject, @Nullable ReadableMap userAttributes, @Nullable String fingerprint, @Nullable String level) { try { JSONObject jsonObject = new JSONObject(exceptionObject); - sendJSCrashByReflection(jsonObject, true, null); - } catch (Exception e) { + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + Method method = getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class, boolean.class, + Map.class, JSONObject.class, IBGNonFatalException.Level.class); + if (method != null) { + IBGNonFatalException.Level nonFatalExceptionLevel = ArgsRegistry.nonFatalExceptionLevel.getOrDefault(level, IBGNonFatalException.Level.ERROR); + Map userAttributesMap = userAttributes == null ? null : userAttributes.toHashMap(); + JSONObject fingerprintObj = fingerprint == null ? null : CrashReporting.getFingerprintObject(fingerprint); + + method.invoke(null, jsonObject, true, userAttributesMap, fingerprintObj, nonFatalExceptionLevel); + + RNInstabugReactnativeModule.clearCurrentReport(); + } + } catch (ClassNotFoundException | IllegalAccessException | + InvocationTargetException e) { + e.printStackTrace(); + } + } + }); + } catch (Throwable e) { e.printStackTrace(); } } - private void sendJSCrashByReflection(final JSONObject exceptionObject, final boolean isHandled, @Nullable final Runnable onComplete) { - MainThreadHandler.runOnMainThread(new Runnable() { - @Override - public void run() { - try { - Method method = getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class, boolean.class); - if (method != null) { - method.invoke(null, exceptionObject, isHandled); - RNInstabugReactnativeModule.clearCurrentReport(); - } - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } finally { - if (onComplete != null) { - onComplete.run(); - } - } - } - }); - } + private void sendJSCrashByReflection(final JSONObject exceptionObject, final boolean isHandled, @Nullable final Runnable onComplete) { + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + Method method = getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class, boolean.class); + if (method != null) { + method.invoke(null, exceptionObject, isHandled); + RNInstabugReactnativeModule.clearCurrentReport(); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } finally { + if (onComplete != null) { + onComplete.run(); + } + } + } + }); + } /** * Enables and disables capturing native C++ NDK crash reporting. diff --git a/android/src/main/java/com/instabug/reactlibrary/utils/ReportUtil.java b/android/src/main/java/com/instabug/reactlibrary/utils/ReportUtil.java index a44da1882..d33387e7d 100644 --- a/android/src/main/java/com/instabug/reactlibrary/utils/ReportUtil.java +++ b/android/src/main/java/com/instabug/reactlibrary/utils/ReportUtil.java @@ -64,18 +64,4 @@ public static Report createReport(ReadableArray tags, ReadableArray consoleLogs, return report; } - public static WritableArray parseConsoleLogs(ArrayList consoleLogs) { - WritableArray writableArray = new WritableNativeArray(); - - for(int i = 0; i < consoleLogs.size(); i++) { - try { - writableArray.pushString(consoleLogs.get(i).toJson()); - } catch (Exception e) { - e.printStackTrace(); - } - - } - - return writableArray; - } } diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugCrashReportingModuleTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugCrashReportingModuleTest.java index fd2a9b58e..b5d97985b 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugCrashReportingModuleTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugCrashReportingModuleTest.java @@ -1,16 +1,25 @@ package com.instabug.reactlibrary; +import static com.instabug.crash.CrashReporting.getFingerprintObject; +import static com.instabug.reactlibrary.util.GlobalMocks.reflected; +import static org.mockito.AdditionalMatchers.cmpEq; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import android.os.Looper; import com.instabug.crash.CrashReporting; +import com.instabug.crash.models.IBGNonFatalException; import com.instabug.library.Feature; import com.instabug.reactlibrary.util.GlobalMocks; +import com.instabug.reactlibrary.util.MockReflected; import com.instabug.reactlibrary.utils.MainThreadHandler; +import org.json.JSONException; +import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -19,6 +28,9 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import java.util.HashMap; +import java.util.Map; + public class RNInstabugCrashReportingModuleTest { private final RNInstabugCrashReportingModule rnModule = new RNInstabugCrashReportingModule(null); @@ -38,6 +50,7 @@ public void mockMainThreadHandler() throws Exception { // Mock Looper class Looper mockMainThreadLooper = mock(Looper.class); Mockito.when(Looper.getMainLooper()).thenReturn(mockMainThreadLooper); + GlobalMocks.setUp(); // Override runOnMainThread @@ -58,6 +71,8 @@ public void tearDown() { mockLooper.close(); mockMainThreadHandler.close(); mockCrashReporting.close(); + GlobalMocks.close(); + } /********Crashes*********/ @@ -80,6 +95,18 @@ public void testSetNDKCrashesEnabledGivenFalse() { mockCrashReporting.verify(() -> CrashReporting.setNDKCrashesState(Feature.State.DISABLED)); } + @Test + public void testSendNonFatalError() { + String jsonCrash = "{}"; + boolean isHandled = true; + String fingerPrint = "test"; + String level = ArgsRegistry.nonFatalExceptionLevel.keySet().iterator().next(); + JSONObject expectedFingerprint = getFingerprintObject(fingerPrint); + IBGNonFatalException.Level expectedLevel = ArgsRegistry.nonFatalExceptionLevel.get(level); + rnModule.sendHandledJSCrash(jsonCrash, null, fingerPrint, level); + reflected.verify(() -> MockReflected.reportException(any(JSONObject.class), eq(isHandled), eq(null), eq(expectedFingerprint), eq(expectedLevel))); + } + @Test public void givenString$sendHandledJSCrash_whenQuery_thenShouldCallNativeApiWithArgs() throws Exception { // JSONObject json = mock(JSONObject.class); diff --git a/android/src/test/java/com/instabug/reactlibrary/util/GlobalMocks.java b/android/src/test/java/com/instabug/reactlibrary/util/GlobalMocks.java index e4810c405..5b88d6dbc 100644 --- a/android/src/test/java/com/instabug/reactlibrary/util/GlobalMocks.java +++ b/android/src/test/java/com/instabug/reactlibrary/util/GlobalMocks.java @@ -4,8 +4,10 @@ import android.util.Log; +import com.instabug.crash.models.IBGNonFatalException; import com.instabug.reactlibrary.utils.InstabugUtil; +import org.json.JSONObject; import org.mockito.MockedStatic; import java.lang.reflect.Method; @@ -37,6 +39,14 @@ public static void setUp() throws NoSuchMethodException { reflection .when(() -> InstabugUtil.getMethod(Class.forName("com.instabug.library.util.InstabugDeprecationLogger"), "setBaseUrl", String.class)) .thenReturn(mSetBaseUrl); + + // reportException mock + Method mCrashReportException = MockReflected.class.getDeclaredMethod("reportException", JSONObject.class, boolean.class, java.util.Map.class, JSONObject.class, IBGNonFatalException.Level.class); + mCrashReportException.setAccessible(true); + reflection + .when(() -> InstabugUtil.getMethod(Class.forName("com.instabug.crash.CrashReporting"), "reportException", JSONObject.class, + boolean.class, java.util.Map.class, JSONObject.class, IBGNonFatalException.Level.class)) + .thenReturn(mCrashReportException); } public static void close() { diff --git a/android/src/test/java/com/instabug/reactlibrary/util/MockReflected.java b/android/src/test/java/com/instabug/reactlibrary/util/MockReflected.java index bfe886b96..ba1c0386d 100644 --- a/android/src/test/java/com/instabug/reactlibrary/util/MockReflected.java +++ b/android/src/test/java/com/instabug/reactlibrary/util/MockReflected.java @@ -1,5 +1,11 @@ package com.instabug.reactlibrary.util; +import com.instabug.crash.models.IBGNonFatalException; + +import org.json.JSONObject; + +import java.util.Map; + /** * Includes fake implementations of methods called by reflection. * Used to verify whether or not a private methods was called. @@ -16,4 +22,9 @@ public static void setCurrentPlatform(int platform) {} * Instabug.util.InstabugDeprecationLogger.setBaseUrl */ public static void setBaseUrl(String baseUrl) {} + /** + * CrashReporting.reportException + */ + public static void reportException(JSONObject exception, boolean isHandled, Map userAttributes, JSONObject fingerPrint, IBGNonFatalException.Level level) {} + } diff --git a/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj b/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj index 25a646edd..afe4ffe60 100644 --- a/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj +++ b/examples/default/ios/InstabugExample.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 9A3D962AB03F97E25566779F /* Pods-InstabugExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample.debug.xcconfig"; path = "Target Support Files/Pods-InstabugExample/Pods-InstabugExample.debug.xcconfig"; sourceTree = ""; }; BAED0D0441A708AE2390E153 /* libPods-InstabugExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InstabugExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; BD54B44E2DF85672BB2D4DEE /* Pods-InstabugExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InstabugExample.release.xcconfig"; path = "Target Support Files/Pods-InstabugExample/Pods-InstabugExample.release.xcconfig"; sourceTree = ""; }; + BE3328762BDACE030078249A /* IBGCrashReporting+CP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IBGCrashReporting+CP.h"; sourceTree = ""; }; C3C8C24386310A3120006604 /* CrashReportingExampleModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CrashReportingExampleModule.m; sourceTree = ""; }; C3C8C784EADC037C5A752B94 /* CrashReportingExampleModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CrashReportingExampleModule.h; sourceTree = ""; }; CC3DF8852A1DFC99003E9914 /* InstabugCrashReportingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InstabugCrashReportingTests.m; sourceTree = ""; }; @@ -91,6 +92,7 @@ 00E356EF1AD99517003FC87E /* InstabugTests */ = { isa = PBXGroup; children = ( + BE3328752BDACE030078249A /* Util */, CC3DF8892A1DFC99003E9914 /* IBGConstants.h */, CC3DF88D2A1DFC9A003E9914 /* IBGConstants.m */, CC3DF88C2A1DFC99003E9914 /* InstabugAPMTests.m */, @@ -181,6 +183,14 @@ path = Pods; sourceTree = ""; }; + BE3328752BDACE030078249A /* Util */ = { + isa = PBXGroup; + children = ( + BE3328762BDACE030078249A /* IBGCrashReporting+CP.h */, + ); + path = Util; + sourceTree = ""; + }; C3C8C1DDCEA91410F27A3683 /* native */ = { isa = PBXGroup; children = ( @@ -412,7 +422,7 @@ name = "[CP-User] [instabug-reactnative] Upload Sourcemap"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/bin/sh\n\nmain() {\n # Read environment variables from ios/.xcode.env if it exists\n env_path=\"$PODS_ROOT/../.xcode.env\"\n if [ -f \"$env_path\" ]; then\n source \"$env_path\"\n fi\n\n # Read environment variables from ios/.xcode.env.local if it exists\n local_env_path=\"${ENV_PATH}.local\"\n if [ -f \"$local_env_path\" ]; then\n source \"$local_env_path\"\n fi\n\n if [[ \"$INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\" = true ]]; then\n echo \"[Info] \\`INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\\` was set to true, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ \"$CONFIGURATION\" = \"Debug\" ]]; then\n echo \"[Info] Building in debug mode, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ -z \"$INFOPLIST_FILE\" ]] || [[ -z \"$PROJECT_DIR\" ]]; then\n echo \"[Error] Instabug sourcemaps script must be invoked by Xcode\"\n exit 0\n fi\n\n local source_map_file=$(generate_sourcemaps | tail -n 1)\n\n local js_project_dir=\"$PROJECT_DIR/..\"\n local instabug_dir=$(dirname $(node -p \"require.resolve('instabug-reactnative/package.json')\"))\n local inferred_token=$(cd $js_project_dir && source $instabug_dir/scripts/find-token.sh)\n local app_token=$(resolve_var \"App Token\" \"INSTABUG_APP_TOKEN\" \"$inferred_token\" | tail -n 1)\n\n local inferred_name=$(/usr/libexec/PlistBuddy -c 'print CFBundleShortVersionString' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_name=$(resolve_var \"Version Name\" \"INSTABUG_APP_VERSION_NAME\" \"$inferred_name\" | tail -n 1)\n\n local inferred_code=$(/usr/libexec/PlistBuddy -c 'print CFBundleVersion' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_code=$(resolve_var \"Version Code\" \"INSTABUG_APP_VERSION_CODE\" \"$inferred_code\" | tail -n 1)\n\n node $instabug_dir/bin/index.js upload-sourcemaps \\\n --platform ios \\\n --file $source_map_file \\\n --token $app_token \\\n --name $version_name \\\n --code $version_code\n}\n\ngenerate_sourcemaps() {\n local react_native_dir=$(dirname $(node -p \"require.resolve('react-native/package.json')\"))\n\n # Fixes an issue with react-native prior to v0.67.0\n # For more info: https://github.com/facebook/react-native/issues/32168\n export RN_DIR=$react_native_dir \n\n # Used withing `react-native-xcode.sh` to generate sourcemap file\n export SOURCEMAP_FILE=\"$(pwd)/main.jsbundle.map\";\n\n source \"$react_native_dir/scripts/react-native-xcode.sh\"\n\n if [[ ! -f \"$SOURCEMAP_FILE\" ]]; then\n echo \"[Error] Unable to find source map file at: $SOURCEMAP_FILE\"\n exit 0\n fi\n\n echo $SOURCEMAP_FILE\n}\n\nresolve_var() {\n local name=$1\n local env_key=$2\n local default_value=$3\n\n local env_value=\"${!env_key}\"\n\n if [[ -n \"$env_value\" ]] && [[ \"$env_value\" != default_value ]]; then\n echo \"[Warning] Environment variable \\`$env_key\\` might have incorrect value, make sure this was intentional:\"\n echo \" Environment Value: $env_value\"\n echo \" Default Value: $default_value\"\n fi\n\n local value=\"${env_value:-$default_value}\"\n\n if [[ -z \"$value\" ]]; then\n echo \"[Error] Unable to find $name! Set the environment variable \\`$env_key\\` and try again.\"\n exit 0\n fi\n\n echo $value\n}\n\nmain \"$@\"; exit\n"; + shellScript = "#!/bin/sh\n\nmain() {\n # Read environment variables from ios/.xcode.env if it exists\n env_path=\"$PODS_ROOT/../.xcode.env\"\n if [ -f \"$env_path\" ]; then\n source \"$env_path\"\n fi\n\n # Read environment variables from ios/.xcode.env.local if it exists\n local_env_path=\"${ENV_PATH}.local\"\n if [ -f \"$local_env_path\" ]; then\n source \"$local_env_path\"\n fi\n\n if [[ \"$INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\" = true ]]; then\n echo \"[Info] \\`INSTABUG_SOURCEMAPS_UPLOAD_DISABLE\\` was set to true, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ \"$CONFIGURATION\" = \"Debug\" ]]; then\n echo \"[Info] Building in debug mode, skipping sourcemaps upload...\"\n exit 0\n fi\n\n if [[ -z \"$INFOPLIST_FILE\" ]] || [[ -z \"$PROJECT_DIR\" ]]; then\n echo \"[Error] Instabug sourcemaps script must be invoked by Xcode\"\n exit 0\n fi\n\n local source_map_file=$(generate_sourcemaps | tail -n 1)\n\n local js_project_dir=\"$PROJECT_DIR/..\"\n local instabug_dir=$(dirname $(node -p \"require.resolve('instabug-reactnative/package.json')\"))\n local inferred_token=$(cd $js_project_dir && source $instabug_dir/scripts/find-token.sh)\n local app_token=$(resolve_var \"App Token\" \"INSTABUG_APP_TOKEN\" \"$inferred_token\" | tail -n 1)\n\n local inferred_name=$(/usr/libexec/PlistBuddy -c 'print CFBundleShortVersionString' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_name=$(resolve_var \"Version Name\" \"INSTABUG_APP_VERSION_NAME\" \"$inferred_name\" | tail -n 1)\n\n local inferred_code=$(/usr/libexec/PlistBuddy -c 'print CFBundleVersion' \"$PROJECT_DIR/$INFOPLIST_FILE\")\n local version_code=$(resolve_var \"Version Code\" \"INSTABUG_APP_VERSION_CODE\" \"$inferred_code\" | tail -n 1)\n\n node $instabug_dir/bin/index.js upload-sourcemaps \\\n --platform ios \\\n --file $source_map_file \\\n --token $app_token \\\n --name $version_name \\\n --code $version_code\n}\n\ngenerate_sourcemaps() {\n local react_native_dir=$(dirname $(node -p \"require.resolve('react-native/package.json')\"))\n\n # Fixes an issue with react-native prior to v0.67.0\n # For more info: https://github.com/facebook/react-native/issues/32168\n export RN_DIR=$react_native_dir\n\n # Used withing `react-native-xcode.sh` to generate sourcemap file\n export SOURCEMAP_FILE=\"$(pwd)/main.jsbundle.map\";\n\n source \"$react_native_dir/scripts/react-native-xcode.sh\"\n\n if [[ ! -f \"$SOURCEMAP_FILE\" ]]; then\n echo \"[Error] Unable to find source map file at: $SOURCEMAP_FILE\"\n exit 0\n fi\n\n echo $SOURCEMAP_FILE\n}\n\nresolve_var() {\n local name=$1\n local env_key=$2\n local default_value=$3\n\n local env_value=\"${!env_key}\"\n\n if [[ -n \"$env_value\" ]] && [[ -n \"$default_value\" ]] && [[ \"$env_value\" != default_value ]]; then\n echo \"[Warning] Environment variable \\`$env_key\\` might have incorrect value, make sure this was intentional:\"\n echo \" Environment Value: $env_value\"\n echo \" Default Value: $default_value\"\n fi\n\n local value=\"${env_value:-$default_value}\"\n\n if [[ -z \"$value\" ]]; then\n echo \"[Error] Unable to find $name! Set the environment variable \\`$env_key\\` and try again.\"\n exit 0\n fi\n\n echo $value\n}\n\nmain \"$@\"; exit\n"; }; B77A7BA143DBD17E8AAFD0B4 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; diff --git a/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m b/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m index 83f682cd3..0d5ac7348 100644 --- a/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m +++ b/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m @@ -1,15 +1,21 @@ #import #import "Instabug/Instabug.h" #import "InstabugCrashReportingBridge.h" +#import "OCMock/OCMock.h" +#import "Util/IBGCrashReporting+CP.h" @interface InstabugCrashReportingTests : XCTestCase @property (nonatomic, retain) InstabugCrashReportingBridge *bridge; +@property (nonatomic, strong) id mCrashReporting; + @end @implementation InstabugCrashReportingTests - (void)setUp { self.bridge = [[InstabugCrashReportingBridge alloc] init]; + self.mCrashReporting = OCMClassMock([IBGCrashReporting class]); + } - (void)testSetEnabled { @@ -20,4 +26,22 @@ - (void)testSetEnabled { XCTAssertFalse(IBGCrashReporting.enabled); } +- (void)testSendNonFatalErrorJsonCrash { + NSDictionary *jsonCrash = @{}; + NSString *fingerPrint = @"fingerprint"; + RCTPromiseResolveBlock resolve = ^(id result) {}; + RCTPromiseRejectBlock reject = ^(NSString *code, NSString *message, NSError *error) {}; + NSDictionary *userAttributes = @{ @"key" : @"value", }; + IBGNonFatalLevel ibgNonFatalLevel = IBGNonFatalLevelInfo; + + + [self.bridge sendHandledJSCrash:jsonCrash userAttributes:userAttributes fingerprint:fingerPrint nonFatalExceptionLevel:ibgNonFatalLevel resolver:resolve rejecter:reject]; + + OCMVerify([self.mCrashReporting cp_reportNonFatalCrashWithStackTrace:jsonCrash + level:IBGNonFatalLevelInfo + groupingString:fingerPrint + userAttributes:userAttributes + ]); +} + @end diff --git a/examples/default/ios/InstabugTests/Util/IBGCrashReporting+CP.h b/examples/default/ios/InstabugTests/Util/IBGCrashReporting+CP.h new file mode 100644 index 000000000..4229dbcea --- /dev/null +++ b/examples/default/ios/InstabugTests/Util/IBGCrashReporting+CP.h @@ -0,0 +1,13 @@ +#import + + +@interface IBGCrashReporting (CP) + ++ (void)cp_reportFatalCrashWithStackTrace:(NSDictionary*)stackTrace; + ++ (void)cp_reportNonFatalCrashWithStackTrace:(NSDictionary*)stackTrace + level:(IBGNonFatalLevel)level + groupingString:(NSString *)groupingString + userAttributes:(NSDictionary *)userAttributes; +@end + diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index f89a967e5..30725b3a2 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -97,7 +97,7 @@ PODS: - hermes-engine (0.72.3): - hermes-engine/Pre-built (= 0.72.3) - hermes-engine/Pre-built (0.72.3) - - Instabug (13.0.5) + - Instabug (13.1.0) - instabug-reactnative-ndk (0.1.0): - RCT-Folly (= 2021.07.22.00) - React-Core @@ -531,8 +531,8 @@ PODS: - RNGestureHandler (2.13.4): - RCT-Folly (= 2021.07.22.00) - React-Core - - RNInstabug (13.0.5): - - Instabug (= 13.0.5) + - RNInstabug (13.1.1): + - Instabug (= 13.1.0) - React-Core - RNReanimated (3.5.4): - DoubleConversion @@ -798,7 +798,7 @@ SPEC CHECKSUMS: Google-Maps-iOS-Utils: f77eab4c4326d7e6a277f8e23a0232402731913a GoogleMaps: 032f676450ba0779bd8ce16840690915f84e57ac hermes-engine: 10fbd3f62405c41ea07e71973ea61e1878d07322 - Instabug: 54e81af4202faf8ed1e26e47f8816d6c42245d1e + Instabug: 3d55eff7ea55adf22df404908a2b954b8b585c29 instabug-reactnative-ndk: 960119a69380cf4cbe47ccd007c453f757927d17 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OCMock: 300b1b1b9155cb6378660b981c2557448830bdc6 @@ -841,7 +841,7 @@ SPEC CHECKSUMS: React-utils: bcb57da67eec2711f8b353f6e3d33bd8e4b2efa3 ReactCommon: 3ccb8fb14e6b3277e38c73b0ff5e4a1b8db017a9 RNGestureHandler: 6e46dde1f87e5f018a54fe5d40cd0e0b942b49ee - RNInstabug: 5284a7dc148edd3184a6510e9bae0f9f5ef8b254 + RNInstabug: 69850571f26b6c1f47171c2be64a3acc0cac3698 RNReanimated: ab2e96c6d5591c3dfbb38a464f54c8d17fb34a87 RNScreens: b21dc57dfa2b710c30ec600786a3fc223b1b92e7 RNSVG: 80584470ff1ffc7994923ea135a3e5ad825546b9 diff --git a/examples/default/src/screens/CrashReportingScreen.tsx b/examples/default/src/screens/CrashReportingScreen.tsx index 24eae5113..c5a53567f 100644 --- a/examples/default/src/screens/CrashReportingScreen.tsx +++ b/examples/default/src/screens/CrashReportingScreen.tsx @@ -1,13 +1,29 @@ -import React from 'react'; -import { Alert, Platform, ScrollView, Text, View } from 'react-native'; +import React, { useState } from 'react'; +import { Alert, Platform, ScrollView, StyleSheet, Text, View } from 'react-native'; -import { CrashReporting } from 'instabug-reactnative'; +import { CrashReporting, NonFatalErrorLevel } from 'instabug-reactnative'; import { ListTile } from '../components/ListTile'; import { Screen } from '../components/Screen'; import { Section } from '../components/Section'; import { PlatformListTile } from '../components/PlatformListTile'; import { NativeExampleCrashReporting } from '../native/NativeCrashReporting'; +import { VerticalListTile } from '../components/VerticalListTile'; +import { Button, VStack } from 'native-base'; +import { InputField } from '../components/InputField'; +import { Select } from '../components/Select'; + +const styles = StyleSheet.create({ + inputWrapper: { + padding: 4, + flex: 1, + }, + + formContainer: { + flexDirection: 'row', + alignItems: 'stretch', + }, +}); export const CrashReportingScreen: React.FC = () => { function throwHandledException(error: Error) { @@ -19,7 +35,7 @@ export const CrashReportingScreen: React.FC = () => { throw error; } catch (err) { if (err instanceof Error) { - CrashReporting.reportError(err).then(() => + CrashReporting.reportError(err, { level: NonFatalErrorLevel.critical }).then(() => Alert.alert(`Crash report for ${error.name} is Sent!`), ); } @@ -46,6 +62,41 @@ export const CrashReportingScreen: React.FC = () => { } } + const [userAttributeKey, setUserAttributeKey] = useState(''); + const [userAttributeValue, setUserAttributeValue] = useState(''); + const [crashNameValue, setCrashNameValue] = useState(''); + const [crashFingerprint, setCrashFingerprint] = useState(''); + const [crashLevelValue, setCrashLevelValue] = useState( + NonFatalErrorLevel.error, + ); + + function sendCrash() { + try { + const error = new Error(crashNameValue); + + throw error; + } catch (err) { + if (err instanceof Error) { + const attrMap: { [k: string]: string } = {}; + attrMap[userAttributeKey] = userAttributeValue; + + const userAttributes: Record = {}; + if (userAttributeKey && userAttributeValue) { + userAttributes[userAttributeKey] = userAttributeValue; + } + const fingerprint = crashFingerprint.length === 0 ? undefined : crashFingerprint; + + CrashReporting.reportError(err, { + userAttributes: userAttributes, + fingerprint: fingerprint, + level: crashLevelValue, + }).then(() => { + Alert.alert(`Crash report for ${crashNameValue} is Sent!`); + }); + } + } + } + return ( @@ -74,6 +125,71 @@ export const CrashReportingScreen: React.FC = () => { title="Throw Handled Native Exception" onPress={() => NativeExampleCrashReporting.sendNativeNonFatal()} /> + + + + setCrashNameValue(key)} + value={crashNameValue} + /> + + + + setUserAttributeKey(key)} + value={userAttributeKey} + /> + + + setUserAttributeValue(value)} + value={userAttributeValue} + /> + + + +