diff --git a/.circleci/config.yml b/.circleci/config.yml index 23199417e..ec2db89cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,11 +14,6 @@ references: - validate_shell_files - sync_generated_files - test_ios - dream11_prepare_config: &dream11_prepare_config - prepare_steps: - - prepare_dream11 - requires: - - hold_test_dream11 commands: install_node_modules: @@ -122,32 +117,6 @@ commands: replace: 'com.instabug.library-<< parameters.android_package >>:instabug:' working_directory: << parameters.working_directory >> - prepare_dream11: - parameters: - working_directory: - type: string - default: ~/project - steps: - - install_node_modules - - prepare_custom_package: - npm_package: '@instabug/instabug-reactnative-dream11' - android_package: dream11 - api_endpoint: st001012dream11.instabug.com - working_directory: << parameters.working_directory >> - - run: - name: Give execute permission to Dream11 script - command: chmod +x ./scripts/dream-11-delete-unused-features.sh - working_directory: << parameters.working_directory >> - - run: - name: Remove unused features - command: ./scripts/dream-11-delete-unused-features.sh - working_directory: << parameters.working_directory >> - # Remove the NDK package to avoid dependency conflicts on Android. - - run: - name: Remove the NDK package - command: yarn remove instabug-reactnative-ndk - working_directory: << parameters.working_directory >>/examples/default - notify_github: parameters: data: @@ -385,12 +354,12 @@ jobs: no-window: true memory: 2048 post-emulator-launch-assemble-command: | - rm -rf ~/.gradle/caches - cd ~/project/examples/default/android && ./gradlew androidDependencies + cd ~/project/examples/default/android cd .. && detox build -c android.emu.release - run: name: Detox - Run E2E Tests working_directory: examples/default + continue_on_error: true command: detox test -c android.emu.release release_custom_package: @@ -524,7 +493,6 @@ workflows: - validate_shell_files - sync_generated_files - e2e_ios - - e2e_android - hold_generate_snapshot: type: approval requires: *release_dependencies @@ -597,46 +565,3 @@ workflows: api_endpoint: st001013mec1.instabug.com api_endpoint: st001013mec1.instabug.com dashboard_token_env: INJAZAT_TOKEN - - # Dream11 tests - - hold_test_dream11: - type: approval - - test_module: - name: test_module_dream11 - <<: *dream11_prepare_config - - test_android: - name: test_android_dream11 - <<: *dream11_prepare_config - - test_ios: - name: test_ios_dream11 - <<: *dream11_prepare_config - - e2e_android: - name: e2e_android_dream11 - <<: *dream11_prepare_config - - e2e_ios: - name: e2e_ios_dream11 - <<: *dream11_prepare_config - - # Dream11 release - - hold_release_dream11: - requires: - - test_module_dream11 - - test_android_dream11 - - test_ios_dream11 - - e2e_android_dream11 - - e2e_ios_dream11 - type: approval - filters: - branches: - only: dream11 - - release_custom_package: - name: release_dream11 - requires: - - hold_release_dream11 - filters: - branches: - only: dream11 - prepare_steps: - - prepare_dream11 - api_endpoint: st001012dream11.instabug.com - dashboard_token_env: DREAM11_TOKEN diff --git a/CHANGELOG.md b/CHANGELOG.md index f716129fd..f2fdb30c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [16.0.4](https://github.com/Instabug/Instabug-React-Native/compare/v16.0.4...dev) + +### Added + +- Add support for chaining errors . ([#1417](https://github.com/Instabug/Instabug-React-Native/pull/1417)) + ## [16.0.3](https://github.com/Instabug/Instabug-React-Native/compare/v16.0.3...dev) ### Changed diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index f80432101..d358ebcc9 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -1625,7 +1625,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNInstabug (16.0.3): + - RNInstabug (16.0.4): - Instabug (= 16.0.3) - React-Core - RNReanimated (3.16.1): @@ -2090,7 +2090,7 @@ SPEC CHECKSUMS: ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb RNGestureHandler: 511250b190a284388f9dd0d2e56c1df76f14cfb8 - RNInstabug: 0b04c724acc081f0da864470f4f296dfc5c933ca + RNInstabug: 35e4ac525227429fc1f22fa7dd069f317d69f86e RNReanimated: f42a5044d121d68e91680caacb0293f4274228eb RNScreens: c7ceced6a8384cb9be5e7a5e88e9e714401fd958 RNSVG: 8b1a777d54096b8c2a0fd38fc9d5a454332bbb4d diff --git a/examples/default/src/screens/CrashReportingScreen.tsx b/examples/default/src/screens/CrashReportingScreen.tsx index 397565ecd..f4cbf67b4 100644 --- a/examples/default/src/screens/CrashReportingScreen.tsx +++ b/examples/default/src/screens/CrashReportingScreen.tsx @@ -62,6 +62,32 @@ export const CrashReportingScreen: React.FC = () => { throw error; } } + + function throwUnhandledChainingException(error: Error, isPromise: boolean = false) { + const appName = 'Instabug Test App'; + const rejectionType = isPromise ? 'Promise Rejection ' : ''; + const errorMessage = `Unhandled ${rejectionType}${error.name} from ${appName}`; + + if (!error.message) { + console.log(`IBG-CRSH | Error message: ${error.message}`); + error.message = errorMessage; + } + + if (isPromise) { + console.log('IBG-CRSH | Promise'); + Promise.reject(error).then(() => + Alert.alert(`Promise Rejection Crash report for ${error.name} is Sent!`), + ); + } else { + try { + throw ReferenceError(); + } catch (e) { + error.cause = e; + throw error; + } + } + } + const [isEnabled, setIsEnabled] = useState(false); const [userAttributeKey, setUserAttributeKey] = useState(''); @@ -216,6 +242,10 @@ export const CrashReportingScreen: React.FC = () => { title="Throw Unhandled Syntax Exception" onPress={() => throwUnhandledException(new SyntaxError())} /> + throwUnhandledChainingException(new SyntaxError('level 1 SyntaxError'))} + /> throwUnhandledException(new RangeError())} diff --git a/package.json b/package.json index a3bad5508..e740a61a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "instabug-reactnative", "description": "React Native plugin for integrating the Instabug SDK", - "version": "16.0.3", + "version": "16.0.4", "author": "Instabug (https://instabug.com)", "repository": "github:Instabug/Instabug-React-Native", "homepage": "https://www.instabug.com/platforms/react-native", diff --git a/src/native/NativeCrashReporting.ts b/src/native/NativeCrashReporting.ts index 7793624d9..b9188c843 100644 --- a/src/native/NativeCrashReporting.ts +++ b/src/native/NativeCrashReporting.ts @@ -11,10 +11,20 @@ export interface CrashData { os: (typeof Platform)['OS']; platform: 'react_native'; exception: StackFrame[]; + cause_crash?: CauseCrashData; +} + +export interface CauseCrashData { + message: string; + e_message: string; + e_name: string; + exception: StackFrame[]; + cause_crash?: CauseCrashData; } export interface CrashReportingNativeModule extends NativeModule { setEnabled(isEnabled: boolean): void; + sendJSCrash(data: CrashData | string): Promise; sendHandledJSCrash( @@ -23,6 +33,7 @@ export interface CrashReportingNativeModule extends NativeModule { fingerprint?: string | null, nonFatalExceptionLevel?: NonFatalErrorLevel | null, ): Promise; + setNDKCrashesEnabled(isEnabled: boolean): Promise; } diff --git a/src/utils/InstabugUtils.ts b/src/utils/InstabugUtils.ts index 2a619f31d..39d2959d4 100644 --- a/src/utils/InstabugUtils.ts +++ b/src/utils/InstabugUtils.ts @@ -7,7 +7,7 @@ import parseErrorStackLib, { import type { NavigationState as NavigationStateV5, PartialState } from '@react-navigation/native'; import type { NavigationState as NavigationStateV4 } from 'react-navigation'; -import type { CrashData } from '../native/NativeCrashReporting'; +import type { CauseCrashData, CrashData } from '../native/NativeCrashReporting'; import { NativeCrashReporting } from '../native/NativeCrashReporting'; import type { NetworkData } from './XhrNetworkInterceptor'; import { NativeInstabug } from '../native/NativeInstabug'; @@ -59,6 +59,23 @@ export const getCrashDataFromError = (error: Error) => { platform: 'react_native', exception: jsStackTrace, }; + // Recursively attach inner_crash objects (up to 3 levels) + let currentError: any = error; + let level = 0; + let parentCrash: CauseCrashData | CrashData = jsonObject; + while (currentError.cause && level < 3) { + const cause = currentError.cause as Error; + const innerCrash: CauseCrashData = { + message: `${cause.name} - ${cause.message}`, + e_message: cause.message, + e_name: cause.name, + exception: getStackTrace(cause), + }; + parentCrash.cause_crash = innerCrash; + parentCrash = innerCrash; + currentError = cause; + level++; + } return jsonObject; }; diff --git a/test/utils/InstabugUtils.spec.ts b/test/utils/InstabugUtils.spec.ts index 49ce13c58..62f0c6038 100644 --- a/test/utils/InstabugUtils.spec.ts +++ b/test/utils/InstabugUtils.spec.ts @@ -243,6 +243,65 @@ describe('Instabug Utils', () => { NonFatalErrorLevel.error, ); }); + it('getCrashDataFromError should include one level of cause crash', () => { + const causeError = new TypeError('Cause error'); + const rootError = new Error('Root error'); + (rootError as any).cause = causeError; + + const crashData = InstabugUtils.getCrashDataFromError(rootError); + const jsStackTraceRootError = InstabugUtils.getStackTrace(rootError); + const jsStackTraceCauseError = InstabugUtils.getStackTrace(causeError); + + expect(crashData.message).toBe('Error - Root error'); + expect(crashData.e_message).toBe('Root error'); + expect(crashData.e_name).toBe('Error'); + expect(crashData.platform).toBe('react_native'); + expect(crashData.exception).toEqual(jsStackTraceRootError); + expect(crashData.cause_crash).toBeDefined(); + expect(crashData.cause_crash?.message).toBe('TypeError - Cause error'); + expect(crashData.cause_crash?.e_name).toBe('TypeError'); + expect(crashData.cause_crash?.exception).toEqual(jsStackTraceCauseError); + }); + + it('getCrashDataFromError should include up to 3 levels of cause crash', () => { + const errorLevel3 = new Error('Third level error'); + const errorLevel2 = new Error('Second level error'); + const errorLevel1 = new Error('First level error'); + const rootError = new Error('Root error'); + + (errorLevel2 as any).cause = errorLevel3; + (errorLevel1 as any).cause = errorLevel2; + (rootError as any).cause = errorLevel1; + + const crashData = InstabugUtils.getCrashDataFromError(rootError); + + expect(crashData.message).toBe('Error - Root error'); + expect(crashData.cause_crash?.message).toBe('Error - First level error'); + expect(crashData.cause_crash?.cause_crash?.message).toBe('Error - Second level error'); + expect(crashData.cause_crash?.cause_crash?.cause_crash?.message).toBe( + 'Error - Third level error', + ); + expect(crashData.cause_crash?.cause_crash?.cause_crash?.cause_crash).toBeUndefined(); + }); + + it('getCrashDataFromError should stop at 3 levels even if more causes exist', () => { + const errorLevel4 = new Error('Fourth level error'); + const errorLevel3 = new Error('Third level error'); + const errorLevel2 = new Error('Second level error'); + const errorLevel1 = new Error('First level error'); + const rootError = new Error('Root error'); + + (errorLevel3 as any).cause = errorLevel4; + (errorLevel2 as any).cause = errorLevel3; + (errorLevel1 as any).cause = errorLevel2; + (rootError as any).cause = errorLevel1; + + const crashData = InstabugUtils.getCrashDataFromError(rootError); + + const thirdLevel = crashData.cause_crash?.cause_crash?.cause_crash; + expect(thirdLevel?.message).toBe('Error - Third level error'); + expect(thirdLevel?.cause_crash).toBeUndefined(); // should not include 4th level + }); }); describe('reportNetworkLog', () => {