From 2b094ca90f714cb8058be7a6b1c8a17975f943ae Mon Sep 17 00:00:00 2001 From: ahmed alaa <154802748+ahmedAlaaInstabug@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:56:09 +0200 Subject: [PATCH 1/8] Release:v14.1.0 (#1338) * feat(example): add features and buttons implementation (#1280) Jira ID: RL-224 * fix: replace thrown errors with logs (#1220) * fix: Replace Thrown Errors with Logs sss chore(ios): bump sdk to v13.1.0 (#1227) * chore(ios): bump ios sdk v13.1.0 * chore(ios): bump ios sdk v13.1.0 * chore(ios): bump ios sdk v13.1.0 chore(android): bump sdk to v13.1.1 (#1228) * chore(android): bump android sdk v13.1.1 feat: enhance non-fatals support (#1194) * add non fatal api --------- Co-authored-by: Ahmed Mahmoud <68241710+a7medev@users.noreply.github.com> fix: read env variable in sourcemap (#1232) * fix sourcemap issue * fix sourcemap issue * fix sourcemap issue * Update typo in CHANGELOG.md --------- Co-authored-by: Andrew Amin <160974398+AndrewAminInstabug@users.noreply.github.com> Release:v13.1.1 (#1231) * release/v13.1.1 * Update CHANGELOG.md Update CHANGELOG.md chore(deps): bump @babel/traverse in /examples/default Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.8 to 7.24.6. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.24.6/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] chore(deps): bump follow-redirects from 1.15.2 to 1.15.6 Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] fix: cont work after logging fix(android): change parameters used in inner class to final (#1239) chore(android): bump sdk to v13.2.0 (#1245) * chore(android): bump sdk to v13.2.0 * chore: update changelog chore(ios): bump sdk to v13.2.0 (#1246) release: v13.2.0 (#1247) chore: resolve issues in changelog (#1249) feat(example): add apm screen (#1141) fix(android): resolve an OOM in network logs (#1244) fix(android): APM network logging(#1253) * fix(android): add W3C External Trace Attributes placeholder * chore: add CHANGLOG * chore: add CHANGLOG * fix: remove ios sub module feat: export upload utils (#1252) chore(example): remove flipper (#1259) fix(android): pass network start time in microseconds (#1260) * fix: network timestamp in android side * fix: PR comments Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> feat: support feature flags with variants (#1230) Jira ID: MOB-14684 --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> chore(android): bump android sdk to v13.3.0 (#1261) chore(ios): bump sdk to v13.3.0 (#1262) release: v13.3.0 (#1263) chore: remove duplicate app flows entries in changelog (#1264) chore: remove duplicate execution traces deprecation in changelog (#1265) feat: navigation tracking support with expo router (#1270) * feat: add screen tracker on screen change listener and tests * feat (example): add screen change listener chore: enhance expo router tracking support (#1272) ci: generalize enterprise releases (#1275) ci: run tests before enterprise releases (#1271) ci: publish snapshots to npm (#1274) fix: PR comments fix(ios): network log empty response body (#1273) fix: drop non-error objects when reporting errors (#1279) * Fix: omitted non-error objects when logging errors * ci: publish snapshots to npm (#1274) * Fix: omitted non-error objects when logging errors * fix: use warn instead of logs Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * fix: merge issues --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> feat: capture client error in the network interceptor (#1257) * feat/support-capture-client-error-in-xhr-requests --------- Co-authored-by: Abdelhamid Nasser <38096011+abdelhamid-f-nasser@users.noreply.github.com> Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> Co-authored-by: kholood fix: APM test cases * fix: APM test cases * fix test cases * fix: PR comments * fix: PR comments * fix: PR comments * refactor(example): upgrade to react native 0.75.4 (#1302) * chore: upgrade dependencies * refactor(example): upgrade to react native 0.75.4 * chore: integrate android sdk v14 snapshot * ci: install cocoapods 1.14 * ci: upgrade xcode to 15.4 * chore: remove .xcode.env.local * ci: install cocoapods into usr/local/bin * ci: fix empty jacoco report issue * Release: v14.0.0 (#1312) * Release : v14.0.0 * Release : v14.0.0 * Release : v14.0.0 * feat: add session sync callback (#1292) * feat(android): add session sync callback (#1281) * feat(android): add SRSyncCallback * feat: implement and test syncCallback CP side * feat(example): use SRSyncCallback in example app * ci: fix tests * fix: export session data type * fix(example): use session data type * fix(android):remove data modifier * fix(android): add property modifiers * fix(android): update test case * fix: enhance test case * fix: update session data type * fix: add more session metadata to setSyncCallback * fix: update syncCallback test * feat: add launchType to session metadata for setSyncCallback * fix: import type * fix: assert evaluate sync returns correct value * fix: import type * fix: cleanup * chore: update js doc * fix: typo * fix: follow interface naming convention * fix: update type * fix: refactor syncCallback * fix: default syncing session to true * fix: convert network logs to readable array * chore: add discriptive comment * chore: use readable map for session metadata * fix: setSyncCallback should sync in case of exception * fix: move SessionMetadata to models * fix: update SessionMetadata type import * fix: report bug e2e test --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * feat(ios): add session sync callback (#1282) * feat(android): add SRSyncCallback * feat: implement and test syncCallback CP side * feat(example): use SRSyncCallback in example app * ci: fix tests * fix: export session data type * fix(example): use session data type * fix(android):remove data modifier * fix(android): add property modifiers * fix(android): update test case * fix: enhance test case * fix(ios): update network log signature * chore(ios): integrate dynamic sampling snapshot * fix:update IOS network log unit test * fix: update session metadata * feat(ios): add setSyncCallback * fix: pod.lock file * fix: update session data type * fix: add more session metadata to setSyncCallback * fix: update syncCallback test * feat: add launchType to session metadata for setSyncCallback * fix: import type * fix: enhance test case * fix: add more session metadata to setSyncCallback * fix: update syncCallback test * feat: add launchType to session metadata for setSyncCallback * fix: import type * feat(ios): add launchType metadata to session syncCallback * fix: add unknown type to launch types * fix: assert evaluate sync returns correct value * fix: import type * fix: cleanup * chore: update js doc * fix: typo * fix: follow interface naming convention * fix: update type * fix: refactor syncCallback * fix: default syncing session to true * fix: convert network logs to readable array * chore: add discriptive comment * chore: use readable map for session metadata * fix: setSyncCallback should sync in case of exception * fix: move SessionMetadata to models * fix: update SessionMetadata type import * fix: report bug e2e test * chore (ios): update snapshot * chore (ios): refactor callback * fix: return network logs * chore: update podfile.lock * chore: fix formatting * chore: revert Podfile.lock * chore: fix ci * fix: launchType typo * fix: update class sessionEvaluationCompletion atomicity * chore: enhance syncCallback formatting * chore: update evaluateSync formatting * fix: fix test SetSyncCallback * fix: update getNetworkLogsArray return value --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * Revert "fix(ios): update network log signature" This reverts commit 8d9036e2020be91147670eb20a4cd98e4bda0a02. * chore(ios): update snapshot * fix: ios network logging test after reverting * fix: convert sendEvent arg from writable to readable map * chore(android): update snapshot * fix(android): refactor getSessionMetadataMap to tolerate null values * fix(ios): update fulfill exception wait time in test * fix(android): convert session metadat map to readable map * chore: update docs * fix: remove hot launch type * fix: increase timeout expectation in test case * Revert "fix: increase timeout expectation in test case" This reverts commit be32acdcebf18e2d8df20818bb167659dfa3a726. * feat(example): add features and buttons implementation (#1280) Jira ID: RL-224 * fix(android): add unknown launch type * chore: update documentation * feat: upgrade to 14.0.0 * feat: upgrade to 14.0.0 * feat: upgrade to 14.0.0 * merge dev * merge dev * merge dev * fix: test case --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> Co-authored-by: YoussefFouadd Co-authored-by: Ahmed alaa * master-on-dev (#1316) Co-authored-by: Mohamed Zakaria El-Zoghbi <5540492+mzelzoghbi@users.noreply.github.com> * chore: update release date (#1320) * feat: add w3c traceparent header injection (#1288) * feat(example): add apm screen (#1141) * fix(android): resolve an OOM in network logs (#1244) * fix(android): APM network logging(#1253) * fix(android): add W3C External Trace Attributes placeholder * chore: add CHANGLOG * chore: add CHANGLOG * fix: remove ios sub module * fix: use correct diff link for v13.0.0, v12.9.0 releases (#1198) * feat(ios): read env vars from .xcode.env in sourcemaps script (#1200) * feat(ios): read env vars from .xcode.env in sourcemaps script * chore: update xcode project * chore: update changelog * chore/update-podfile.lock * feat: add w3c header generator * ci:fix lint * ci:fix ios tests * feat:update header format * feat:update header format test case title * feat:Inject the W3C Header to Network Requests * ci:fix lint * feat:remove tracestate * feat: get feature flags from IOS * ci: fix ios test * fix: modify function naming * fix: update APM test cases * fix: update native test cases naming * feat(ios): w3c logs mapping * fix: export number partial id * fix: modify partial id generator function * fix: modify partial id generator test cases * feat(example): add network request generators buttons * ci: fix lint * ci(example): add missing import * feat(android): map apm network logs * feat(android): add W3C native modules & tests * feat: map w3c android native modules and test * feat: register w3c feature change listener * feat: add feature flags * feat: call updated feature flags * fix: update object assigning * fix: remove comment * fix: modify test cases naming * fix: generated header injection * fix: fix variable neames * fix: update test cases * fix(android): caught header null string * fix: update network log interface * fix (example): remove redundant button * feat (example): add Enable/Disable APM buttons * fix: add w3c Attributes to network logs tests * fix: fix imports * feat(android) : add w3c attributes to APM network Logs * chore: remove flipper * fix: adjust spacing * fix: update test case * feat: migrate-Feature-Flag-APM-method-to-Core * fix: js testcases * fix: js testcases * fix: js testcases * feat: add migrate APM into core in ios section * fix: js testcases * feat: add migrate APM into core in ios section * feat: add migrate APM into core in ios section * fix: Pr comments * fix: PR comment * fix: Pr comments * fix: added changelog item * fix: feature flag listener * fix: feature flag listener * feat: migrate w3c flags to APM core * feat(example): add apm screen (#1141) * fix(android): resolve an OOM in network logs (#1244) * fix(android): APM network logging(#1253) * fix(android): add W3C External Trace Attributes placeholder * chore: add CHANGLOG * chore: add CHANGLOG * fix: remove ios sub module * feat: export upload utils (#1252) * chore(example): remove flipper (#1259) * fix(android): pass network start time in microseconds (#1260) * fix: network timestamp in android side * fix: PR comments Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * feat: support feature flags with variants (#1230) Jira ID: MOB-14684 --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * chore(android): bump android sdk to v13.3.0 (#1261) * chore(ios): bump sdk to v13.3.0 (#1262) * release: v13.3.0 (#1263) * chore: remove duplicate app flows entries in changelog (#1264) * chore: remove duplicate execution traces deprecation in changelog (#1265) * feat: navigation tracking support with expo router (#1270) * feat: add screen tracker on screen change listener and tests * feat (example): add screen change listener * chore: enhance expo router tracking support (#1272) * ci: generalize enterprise releases (#1275) * ci: run tests before enterprise releases (#1271) * ci: publish snapshots to npm (#1274) * fix(ios): network log empty response body (#1273) * fix: drop non-error objects when reporting errors (#1279) * Fix: omitted non-error objects when logging errors * ci: publish snapshots to npm (#1274) * Fix: omitted non-error objects when logging errors * fix: use warn instead of logs Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * fix: merge issues --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * feat: capture client error in the network interceptor (#1257) * feat/support-capture-client-error-in-xhr-requests --------- Co-authored-by: Abdelhamid Nasser <38096011+abdelhamid-f-nasser@users.noreply.github.com> Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> Co-authored-by: kholood * fix: merge issues * fix: networkLogIOS test case * fix: merge issues * fix: merge issues * fix: merge issues * fix: merge issues * fix: merge issues * fix: remove logs * fix: refactore networkLogAndroid arguments * fix: merge issues * fix: merge issues * fix: move W3cExternalTraceAttributes to models * fix: return expected value type from bridge * fix: refactor method call * fix: refactor method name * fix: return expected value types of w3c flags * chore: refactor constant names * fix: pod file * fix(android): fix w3c caught header * fix (android): reporting network logs upon disabling w3c main feature flag * chore: add changelog --------- Co-authored-by: Abdelhamid Nasser <38096011+abdelhamid-f-nasser@users.noreply.github.com> Co-authored-by: kholood Co-authored-by: Ahmed alaa Co-authored-by: ahmed alaa <154802748+ahmedAlaaInstabug@users.noreply.github.com> * Adding buttons to the sample app (#1311) * feat(example): add webviews to the sample app (#1310) * Adding WebViews to sample app * Fixing Pods issue * Fixing CI * feat: exclude DEV server from network logs (#1307) * feat: exclude dev server * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * feat: exclude DEV server url from network logs * merge dev * fix: Adjust logging behavior based on the debugLogLevel. (#1319) * refactor(example): upgrade to react native 0.75.4 (#1302) * chore: upgrade dependencies * refactor(example): upgrade to react native 0.75.4 * chore: integrate android sdk v14 snapshot * ci: install cocoapods 1.14 * ci: upgrade xcode to 15.4 * chore: remove .xcode.env.local * ci: install cocoapods into usr/local/bin * ci: fix empty jacoco report issue * Release: v14.0.0 (#1312) * Release : v14.0.0 * Release : v14.0.0 * Release : v14.0.0 * feat: add session sync callback (#1292) * feat(android): add session sync callback (#1281) * feat(android): add SRSyncCallback * feat: implement and test syncCallback CP side * feat(example): use SRSyncCallback in example app * ci: fix tests * fix: export session data type * fix(example): use session data type * fix(android):remove data modifier * fix(android): add property modifiers * fix(android): update test case * fix: enhance test case * fix: update session data type * fix: add more session metadata to setSyncCallback * fix: update syncCallback test * feat: add launchType to session metadata for setSyncCallback * fix: import type * fix: assert evaluate sync returns correct value * fix: import type * fix: cleanup * chore: update js doc * fix: typo * fix: follow interface naming convention * fix: update type * fix: refactor syncCallback * fix: default syncing session to true * fix: convert network logs to readable array * chore: add discriptive comment * chore: use readable map for session metadata * fix: setSyncCallback should sync in case of exception * fix: move SessionMetadata to models * fix: update SessionMetadata type import * fix: report bug e2e test --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * feat(ios): add session sync callback (#1282) * feat(android): add SRSyncCallback * feat: implement and test syncCallback CP side * feat(example): use SRSyncCallback in example app * ci: fix tests * fix: export session data type * fix(example): use session data type * fix(android):remove data modifier * fix(android): add property modifiers * fix(android): update test case * fix: enhance test case * fix(ios): update network log signature * chore(ios): integrate dynamic sampling snapshot * fix:update IOS network log unit test * fix: update session metadata * feat(ios): add setSyncCallback * fix: pod.lock file * fix: update session data type * fix: add more session metadata to setSyncCallback * fix: update syncCallback test * feat: add launchType to session metadata for setSyncCallback * fix: import type * fix: enhance test case * fix: add more session metadata to setSyncCallback * fix: update syncCallback test * feat: add launchType to session metadata for setSyncCallback * fix: import type * feat(ios): add launchType metadata to session syncCallback * fix: add unknown type to launch types * fix: assert evaluate sync returns correct value * fix: import type * fix: cleanup * chore: update js doc * fix: typo * fix: follow interface naming convention * fix: update type * fix: refactor syncCallback * fix: default syncing session to true * fix: convert network logs to readable array * chore: add discriptive comment * chore: use readable map for session metadata * fix: setSyncCallback should sync in case of exception * fix: move SessionMetadata to models * fix: update SessionMetadata type import * fix: report bug e2e test * chore (ios): update snapshot * chore (ios): refactor callback * fix: return network logs * chore: update podfile.lock * chore: fix formatting * chore: revert Podfile.lock * chore: fix ci * fix: launchType typo * fix: update class sessionEvaluationCompletion atomicity * chore: enhance syncCallback formatting * chore: update evaluateSync formatting * fix: fix test SetSyncCallback * fix: update getNetworkLogsArray return value --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> * Revert "fix(ios): update network log signature" This reverts commit 8d9036e2020be91147670eb20a4cd98e4bda0a02. * chore(ios): update snapshot * fix: ios network logging test after reverting * fix: convert sendEvent arg from writable to readable map * chore(android): update snapshot * fix(android): refactor getSessionMetadataMap to tolerate null values * fix(ios): update fulfill exception wait time in test * fix(android): convert session metadat map to readable map * chore: update docs * fix: remove hot launch type * fix: increase timeout expectation in test case * Revert "fix: increase timeout expectation in test case" This reverts commit be32acdcebf18e2d8df20818bb167659dfa3a726. * feat(example): add features and buttons implementation (#1280) Jira ID: RL-224 * fix(android): add unknown launch type * chore: update documentation * feat: upgrade to 14.0.0 * feat: upgrade to 14.0.0 * feat: upgrade to 14.0.0 * merge dev * merge dev * merge dev * fix: test case --------- Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> Co-authored-by: YoussefFouadd Co-authored-by: Ahmed alaa * master-on-dev (#1316) Co-authored-by: Mohamed Zakaria El-Zoghbi <5540492+mzelzoghbi@users.noreply.github.com> * fix: adjust logging with debuglogLevel * chore: update release date (#1320) --------- Co-authored-by: Mohamed Zakaria El-Zoghbi <5540492+mzelzoghbi@users.noreply.github.com> Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> Co-authored-by: kholood Co-authored-by: YoussefFouadd --------- Co-authored-by: Mohamed Zakaria El-Zoghbi <5540492+mzelzoghbi@users.noreply.github.com> Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> Co-authored-by: kholood Co-authored-by: YoussefFouadd * Release:14.1.0 (#1335) * release: 14.1.0 * release: 14.1.0 * release: v14.1.0 * release: v14.1.0 * release: v14.1.0 --------- Co-authored-by: YoussefFouadd Co-authored-by: Ahmed Elrefaey <68241710+a7medev@users.noreply.github.com> Co-authored-by: kholood Co-authored-by: Mohamed Zakaria El-Zoghbi <5540492+mzelzoghbi@users.noreply.github.com> Co-authored-by: Abdelhamid Nasser <38096011+abdelhamid-f-nasser@users.noreply.github.com> --- CHANGELOG.md | 15 ++ android/build.gradle | 2 +- android/native.gradle | 2 +- .../com/instabug/reactlibrary/Constants.java | 3 + .../reactlibrary/RNInstabugAPMModule.java | 40 +++- .../RNInstabugReactnativeModule.java | 107 ++++++++++ .../reactlibrary/RNInstabugAPMModuleTest.java | 2 + .../RNInstabugReactnativeModuleTest.java | 27 +++ .../ios/InstabugTests/InstabugAPMTests.m | 3 + .../InstabugCrashReportingTests.m | 6 +- .../ios/InstabugTests/InstabugSampleTests.m | 86 +++++++- .../ios/InstabugTests/RNInstabugTests.m | 2 +- examples/default/ios/Podfile.lock | 35 ++- examples/default/package.json | 1 + examples/default/src/navigation/HomeStack.tsx | 21 ++ .../src/screens/BugReportingScreen.tsx | 64 +++++- .../default/src/screens/apm/APMScreen.tsx | 1 + .../default/src/screens/apm/NetworkScreen.tsx | 17 +- .../apm/webViews/FullWebViewsScreen.tsx | 15 ++ .../apm/webViews/PartialWebViewsScreen.tsx | 27 +++ .../screens/apm/webViews/WebViewsScreen.tsx | 16 ++ examples/default/yarn.lock | 10 +- ios/RNInstabug/InstabugAPMBridge.m | 4 + ios/RNInstabug/InstabugReactBridge.h | 7 +- ios/RNInstabug/InstabugReactBridge.m | 35 ++- ios/RNInstabug/Util/IBGAPM+PrivateAPIs.h | 15 ++ ios/RNInstabug/Util/IBGNetworkLogger+CP.h | 27 +++ ios/native.rb | 2 +- package.json | 2 +- src/models/W3cExternalTraceAttributes.ts | 22 ++ src/modules/CrashReporting.ts | 3 +- src/modules/Instabug.ts | 40 +++- src/modules/NetworkLogger.ts | 27 ++- src/native/NativeAPM.ts | 5 + src/native/NativeInstabug.ts | 13 ++ src/utils/FeatureFlags.ts | 28 +++ src/utils/InstabugConstants.ts | 1 + src/utils/InstabugUtils.ts | 54 +++++ src/utils/UnhandledRejectionTracking.ts | 3 +- src/utils/XhrNetworkInterceptor.ts | 92 +++++++- src/utils/config.ts | 7 + src/utils/logger.ts | 54 +++++ test/mocks/mockInstabug.ts | 4 + test/modules/Instabug.spec.ts | 19 +- test/modules/NetworkLogger.spec.ts | 18 +- test/utils/InstabugUtils.spec.ts | 19 ++ test/utils/XhrNetworkInterceptor.spec.ts | 202 +++++++++++++++++- 47 files changed, 1142 insertions(+), 63 deletions(-) create mode 100644 examples/default/src/screens/apm/webViews/FullWebViewsScreen.tsx create mode 100644 examples/default/src/screens/apm/webViews/PartialWebViewsScreen.tsx create mode 100644 examples/default/src/screens/apm/webViews/WebViewsScreen.tsx create mode 100644 ios/RNInstabug/Util/IBGAPM+PrivateAPIs.h create mode 100644 src/models/W3cExternalTraceAttributes.ts create mode 100644 src/utils/FeatureFlags.ts create mode 100644 src/utils/config.ts create mode 100644 src/utils/logger.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f53e242939..e1f28a933f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [14.1.0](https://github.com/Instabug/Instabug-React-Native/compare/v14.0.0...v14.1.0) (January 2, 2025) + +### Added + +- Add support for tracing network requests from Instabug to services like Datadog and New Relic ([#1288](https://github.com/Instabug/Instabug-React-Native/pull/1288)) + +### Changed + +- Bump Instabug iOS SDK to v14.1.0 ([#1335](https://github.com/Instabug/Instabug-React-Native/pull/1335)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/14.1.0). +- Bump Instabug Android SDK to v14.1.0 ([#1335](https://github.com/Instabug/Instabug-React-Native/pull/1335)). [See release notes](https://github.com/Instabug/Instabug-Android/releases/tag/v14.1.0). + ## [14.0.0](https://github.com/Instabug/Instabug-React-Native/compare/v13.4.0...14.0.0) (November 19, 2024) ### Added @@ -11,6 +22,10 @@ - Bump Instabug iOS SDK to v14.0.0 ([#1312](https://github.com/Instabug/Instabug-React-Native/pull/1312)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/14.0.0). - Bump Instabug Android SDK to v14.0.0 ([#1312](https://github.com/Instabug/Instabug-React-Native/pull/1312)). [See release notes](https://github.com/Instabug/Instabug-Android/releases/tag/v14.0.0). +### Added + +- Exclude DEV server from network logs ([#1307](https://github.com/Instabug/Instabug-React-Native/pull/1307)). + ### Fixed - Replace thrown errors with logs ([#1220](https://github.com/Instabug/Instabug-React-Native/pull/1220)) diff --git a/android/build.gradle b/android/build.gradle index 485acf84aa..8a312ccb37 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -57,7 +57,7 @@ android { minSdkVersion getExtOrDefault('minSdkVersion').toInteger() targetSdkVersion getExtOrDefault('targetSdkVersion').toInteger() versionCode 1 - versionName "13.4.0" + versionName "14.1.0" multiDexEnabled true ndk { abiFilters "armeabi-v7a", "x86" diff --git a/android/native.gradle b/android/native.gradle index ca10ca83b8..652733c4f8 100644 --- a/android/native.gradle +++ b/android/native.gradle @@ -1,5 +1,5 @@ project.ext.instabug = [ - version: '14.0.0' + version: '14.1.0' ] dependencies { diff --git a/android/src/main/java/com/instabug/reactlibrary/Constants.java b/android/src/main/java/com/instabug/reactlibrary/Constants.java index fcab683326..f6986200d3 100644 --- a/android/src/main/java/com/instabug/reactlibrary/Constants.java +++ b/android/src/main/java/com/instabug/reactlibrary/Constants.java @@ -9,6 +9,9 @@ final class Constants { final static String IBG_ON_NEW_MESSAGE_HANDLER = "IBGonNewMessageHandler"; final static String IBG_ON_NEW_REPLY_RECEIVED_CALLBACK = "IBGOnNewReplyReceivedCallback"; + + final static String IBG_ON_NEW_W3C_FLAGS_UPDATE_RECEIVED_CALLBACK = "IBGOnNewW3CFlagsUpdateReceivedCallback"; + final static String IBG_SESSION_REPLAY_ON_SYNC_CALLBACK_INVOCATION = "IBGSessionReplayOnSyncCallback"; } diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java index e2ff10b991..8b3c3206eb 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugAPMModule.java @@ -9,17 +9,16 @@ 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.apm.APM; import com.instabug.apm.model.ExecutionTrace; import com.instabug.apm.networking.APMNetworkLogger; import com.instabug.apm.networkinterception.cp.APMCPNetworkLog; +import com.instabug.reactlibrary.utils.EventEmitterModule; +import com.instabug.apm.networkinterception.cp.APMCPNetworkLog; import com.instabug.reactlibrary.utils.MainThreadHandler; -import org.json.JSONException; -import org.json.JSONObject; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; @@ -28,7 +27,7 @@ import static com.instabug.reactlibrary.utils.InstabugUtil.getMethod; -public class RNInstabugAPMModule extends ReactContextBaseJavaModule { +public class RNInstabugAPMModule extends EventEmitterModule { public RNInstabugAPMModule(ReactApplicationContext reactApplicationContext) { super(reactApplicationContext); @@ -330,14 +329,41 @@ private void networkLogAndroid(final double requestStartTime, final double statusCode, final String responseContentType, @Nullable final String errorDomain, + @Nullable final ReadableMap w3cAttributes, @Nullable final String gqlQueryName, - @Nullable final String serverErrorMessage) { + @Nullable final String serverErrorMessage + ) { try { APMNetworkLogger networkLogger = new APMNetworkLogger(); final boolean hasError = errorDomain != null && !errorDomain.isEmpty(); final String errorMessage = hasError ? errorDomain : null; + Boolean isW3cHeaderFound=false; + Long partialId=null; + Long networkStartTimeInSeconds=null; + + + try { + if (!w3cAttributes.isNull("isW3cHeaderFound")) { + isW3cHeaderFound = w3cAttributes.getBoolean("isW3cHeaderFound"); + } + if (!w3cAttributes.isNull("partialId")) { + partialId =(long) w3cAttributes.getDouble("partialId"); + networkStartTimeInSeconds = (long) w3cAttributes.getDouble("networkStartTimeInSeconds"); + } + + } catch (Exception e) { + e.printStackTrace(); + } + APMCPNetworkLog.W3CExternalTraceAttributes w3cExternalTraceAttributes = + new APMCPNetworkLog.W3CExternalTraceAttributes( + isW3cHeaderFound, + partialId, + networkStartTimeInSeconds, + w3cAttributes.getString("w3cGeneratedHeader"), + w3cAttributes.getString("w3cCaughtHeader") + ); try { Method method = getMethod(Class.forName("com.instabug.apm.networking.APMNetworkLogger"), "log", long.class, long.class, String.class, String.class, long.class, String.class, String.class, String.class, String.class, String.class, long.class, int.class, String.class, String.class, String.class, String.class, APMCPNetworkLog.W3CExternalTraceAttributes.class); if (method != null) { @@ -359,7 +385,7 @@ private void networkLogAndroid(final double requestStartTime, errorMessage, gqlQueryName, serverErrorMessage, - null + w3cExternalTraceAttributes ); } else { Log.e("IB-CP-Bridge", "APMNetworkLogger.log was not found by reflection"); diff --git a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java index f936eaa12e..9c901cb7a5 100644 --- a/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java +++ b/android/src/main/java/com/instabug/reactlibrary/RNInstabugReactnativeModule.java @@ -8,6 +8,7 @@ import android.util.Log; import android.view.View; +import androidx.annotation.NonNull; import androidx.annotation.UiThread; import com.facebook.react.bridge.Arguments; @@ -22,6 +23,8 @@ import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.uimanager.UIManagerModule; +import com.instabug.apm.InternalAPM; +import com.instabug.apm.configuration.cp.APMFeature; import com.instabug.library.Feature; import com.instabug.library.Instabug; import com.instabug.library.InstabugColorTheme; @@ -30,6 +33,11 @@ import com.instabug.library.LogLevel; import com.instabug.library.ReproConfigurations; import com.instabug.library.core.InstabugCore; +import com.instabug.library.internal.crossplatform.CoreFeature; +import com.instabug.library.internal.crossplatform.CoreFeaturesState; +import com.instabug.library.internal.crossplatform.FeaturesStateListener; +import com.instabug.library.internal.crossplatform.InternalCore; +import com.instabug.library.featuresflags.model.IBGFeatureFlag; import com.instabug.library.featuresflags.model.IBGFeatureFlag; import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.invocation.InstabugInvocationEvent; @@ -1148,6 +1156,105 @@ public void run() { } }); } + /** + * Register a listener for W3C flags value change + */ + @ReactMethod + public void registerW3CFlagsChangeListener(){ + + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + InternalCore.INSTANCE._setFeaturesStateListener(new FeaturesStateListener() { + @Override + public void invoke(@NonNull CoreFeaturesState featuresState) { + WritableMap params = Arguments.createMap(); + params.putBoolean("isW3ExternalTraceIDEnabled", featuresState.isW3CExternalTraceIdEnabled()); + params.putBoolean("isW3ExternalGeneratedHeaderEnabled", featuresState.isAttachingGeneratedHeaderEnabled()); + params.putBoolean("isW3CaughtHeaderEnabled", featuresState.isAttachingCapturedHeaderEnabled()); + + sendEvent(Constants.IBG_ON_NEW_W3C_FLAGS_UPDATE_RECEIVED_CALLBACK, params); + } + }); + } + catch (Exception e) { + e.printStackTrace(); + } + + } + + }); + } + + + /** + * Get first time Value of W3ExternalTraceID flag + */ + @ReactMethod + public void isW3ExternalTraceIDEnabled(Promise promise){ + + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_EXTERNAL_TRACE_ID)); + } + catch (Exception e) { + e.printStackTrace(); + promise.resolve(false); + } + + } + + }); + } + + + /** + * Get first time Value of W3ExternalGeneratedHeader flag + */ + @ReactMethod + public void isW3ExternalGeneratedHeaderEnabled(Promise promise){ + + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_GENERATED_HEADER)); + } + catch (Exception e) { + e.printStackTrace(); + promise.resolve(false); + } + + } + + }); + } + + /** + * Get first time Value of W3CaughtHeader flag + */ + @ReactMethod + public void isW3CaughtHeaderEnabled(Promise promise){ + + MainThreadHandler.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_CAPTURED_HEADER)); + } + catch (Exception e) { + e.printStackTrace(); + promise.resolve(false); + } + + } + + }); + } + /** * Map between the exported JS constant and the arg key in {@link ArgsRegistry}. diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java index 5045f929e6..85ca1384d1 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugAPMModuleTest.java @@ -204,4 +204,6 @@ public void testSetFlowAttribute() { verify(APM.class, times(1)); APM.endUITrace(); } + + } diff --git a/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java b/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java index b9bf2308cc..e8aad5b0c5 100644 --- a/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java +++ b/android/src/test/java/com/instabug/reactlibrary/RNInstabugReactnativeModuleTest.java @@ -18,6 +18,9 @@ import com.instabug.library.IssueType; import com.instabug.library.ReproConfigurations; import com.instabug.library.ReproMode; +import com.instabug.library.internal.crossplatform.CoreFeature; +import com.instabug.library.internal.crossplatform.InternalCore; +import com.instabug.library.featuresflags.model.IBGFeatureFlag; import com.instabug.library.featuresflags.model.IBGFeatureFlag; import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.ui.onboarding.WelcomeMessage; @@ -635,4 +638,28 @@ public void testWillRedirectToStore() { // then mockInstabug.verify(() -> Instabug.willRedirectToStore()); } + @Test + public void testW3CExternalTraceIDFlag(){ + Promise promise = mock(Promise.class); + InternalCore internalAPM = mock(InternalCore.class); + rnModule.isW3ExternalTraceIDEnabled(promise); + boolean expected=internalAPM._isFeatureEnabled(CoreFeature.W3C_EXTERNAL_TRACE_ID); + verify(promise).resolve(expected); + } + @Test + public void testW3CExternalGeneratedHeaderFlag(){ + Promise promise = mock(Promise.class); + InternalCore internalAPM = mock(InternalCore.class); + rnModule.isW3ExternalGeneratedHeaderEnabled(promise); + boolean expected=internalAPM._isFeatureEnabled(CoreFeature.W3C_ATTACHING_GENERATED_HEADER); + verify(promise).resolve(expected); + } + @Test + public void testW3CCaughtHeaderFlag(){ + Promise promise = mock(Promise.class); + InternalCore internalAPM = mock(InternalCore.class); + rnModule.isW3CaughtHeaderEnabled(promise); + boolean expected=internalAPM._isFeatureEnabled(CoreFeature.W3C_ATTACHING_CAPTURED_HEADER); + verify(promise).resolve(expected); + } } diff --git a/examples/default/ios/InstabugTests/InstabugAPMTests.m b/examples/default/ios/InstabugTests/InstabugAPMTests.m index dd96841dea..5945b2b791 100644 --- a/examples/default/ios/InstabugTests/InstabugAPMTests.m +++ b/examples/default/ios/InstabugTests/InstabugAPMTests.m @@ -13,6 +13,7 @@ #import #import "Instabug/Instabug.h" #import "IBGConstants.h" +#import "RNInstabug/IBGAPM+PrivateAPIs.h" @interface InstabugAPMTests : XCTestCase @property (nonatomic, retain) InstabugAPMBridge *instabugBridge; @@ -176,4 +177,6 @@ - (void) testEndUITrace { OCMVerify([mock endUITrace]); } + + @end diff --git a/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m b/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m index d42e47022a..d8ae7a0e54 100644 --- a/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m +++ b/examples/default/ios/InstabugTests/InstabugCrashReportingTests.m @@ -19,11 +19,13 @@ - (void)setUp { } - (void)testSetEnabled { + + [self.bridge setEnabled:NO]; + XCTAssertFalse(IBGCrashReporting.enabled); + [self.bridge setEnabled:YES]; XCTAssertTrue(IBGCrashReporting.enabled); - [self.bridge setEnabled:NO]; - XCTAssertFalse(IBGCrashReporting.enabled); } - (void)testSendJSCrash { diff --git a/examples/default/ios/InstabugTests/InstabugSampleTests.m b/examples/default/ios/InstabugTests/InstabugSampleTests.m index 51bbe182c2..8744ce4eb8 100644 --- a/examples/default/ios/InstabugTests/InstabugSampleTests.m +++ b/examples/default/ios/InstabugTests/InstabugSampleTests.m @@ -315,7 +315,7 @@ - (void)testSetWelcomeMessageMode { - (void)testNetworkLogIOS { id mIBGNetworkLogger = OCMClassMock([IBGNetworkLogger class]); - + NSString *url = @"https://api.instabug.com"; NSString *method = @"GET"; NSString *requestBody = @"requestBody"; @@ -332,7 +332,12 @@ - (void)testNetworkLogIOS { double duration = 150; NSString *gqlQueryName = nil; NSString *serverErrorMessage = nil; - + NSDictionary* w3cExternalTraceAttributes = nil; + NSNumber *isW3cCaughted = nil; + NSNumber *partialID = nil; + NSNumber *timestamp= nil; + NSString *generatedW3CTraceparent= nil; + NSString *caughtedW3CTraceparent= nil; [self.instabugBridge networkLogIOS:url method:method requestBody:requestBody @@ -348,8 +353,11 @@ - (void)testNetworkLogIOS { startTime:startTime duration:duration gqlQueryName:gqlQueryName - serverErrorMessage:serverErrorMessage]; - + serverErrorMessage:serverErrorMessage + w3cExternalTraceAttributes:w3cExternalTraceAttributes + + ]; + OCMVerify([mIBGNetworkLogger addNetworkLogWithUrl:url method:method requestBody:requestBody @@ -366,11 +374,12 @@ - (void)testNetworkLogIOS { duration:duration * 1000 gqlQueryName:gqlQueryName serverErrorMessage:serverErrorMessage - isW3cCaughted:nil - partialID:nil - timestamp:nil - generatedW3CTraceparent:nil - caughtedW3CTraceparent:nil]); + isW3cCaughted:isW3cCaughted + partialID:partialID + timestamp:timestamp + generatedW3CTraceparent:generatedW3CTraceparent + caughtedW3CTraceparent:caughtedW3CTraceparent + ]); } - (void)testSetFileAttachment { @@ -541,4 +550,63 @@ - (void)testRemoveAllFeatureFlags { OCMVerify([mock removeAllFeatureFlags]); } + +- (void) testIsW3ExternalTraceIDEnabled { + id mock = OCMClassMock([IBGNetworkLogger class]); + NSNumber *expectedValue = @(YES); + + OCMStub([mock w3ExternalTraceIDEnabled]).andReturn([expectedValue boolValue]); + + XCTestExpectation *expectation = [self expectationWithDescription:@"Call completion handler"]; + RCTPromiseResolveBlock resolve = ^(NSNumber *result) { + XCTAssertEqualObjects(result, expectedValue); + [expectation fulfill]; + }; + + [self.instabugBridge isW3ExternalTraceIDEnabled:resolve :nil]; + + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + + OCMVerify([mock w3ExternalTraceIDEnabled]); +} + +- (void) testIsW3ExternalGeneratedHeaderEnabled { + id mock = OCMClassMock([IBGNetworkLogger class]); + NSNumber *expectedValue = @(YES); + + OCMStub([mock w3ExternalGeneratedHeaderEnabled]).andReturn([expectedValue boolValue]); + + XCTestExpectation *expectation = [self expectationWithDescription:@"Call completion handler"]; + RCTPromiseResolveBlock resolve = ^(NSNumber *result) { + XCTAssertEqualObjects(result, expectedValue); + [expectation fulfill]; + }; + + [self.instabugBridge isW3ExternalGeneratedHeaderEnabled:resolve :nil]; + + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + + OCMVerify([mock w3ExternalGeneratedHeaderEnabled]); +} + +- (void) testIsW3CaughtHeaderEnabled { + id mock = OCMClassMock([IBGNetworkLogger class]); + NSNumber *expectedValue = @(YES); + + OCMStub([mock w3CaughtHeaderEnabled]).andReturn([expectedValue boolValue]); + + XCTestExpectation *expectation = [self expectationWithDescription:@"Call completion handler"]; + RCTPromiseResolveBlock resolve = ^(NSNumber *result) { + XCTAssertEqualObjects(result, expectedValue); + [expectation fulfill]; + }; + + [self.instabugBridge isW3CaughtHeaderEnabled:resolve :nil]; + + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + + OCMVerify([mock w3CaughtHeaderEnabled]); +} + + @end diff --git a/examples/default/ios/InstabugTests/RNInstabugTests.m b/examples/default/ios/InstabugTests/RNInstabugTests.m index cde248ad30..930da52ca5 100644 --- a/examples/default/ios/InstabugTests/RNInstabugTests.m +++ b/examples/default/ios/InstabugTests/RNInstabugTests.m @@ -69,7 +69,7 @@ - (void)testInitWithLogsLevel { - (void) testSetCodePushVersion { NSString *codePushVersion = @"1.0.0(1)"; [RNInstabug setCodePushVersion:codePushVersion]; - + OCMVerify([self.mInstabug setCodePushVersion:codePushVersion]); } diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index 81ba100a5a..6572db6072 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -31,7 +31,7 @@ PODS: - hermes-engine (0.75.4): - hermes-engine/Pre-built (= 0.75.4) - hermes-engine/Pre-built (0.75.4) - - Instabug (14.0.0) + - Instabug (14.1.0) - instabug-reactnative-ndk (0.1.0): - DoubleConversion - glog @@ -1319,6 +1319,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-webview (13.12.3): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - React-nativeconfig (0.75.4) - React-NativeModulesApple (0.75.4): - glog @@ -1602,8 +1623,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNInstabug (14.0.0): - - Instabug (= 14.0.0) + - RNInstabug (14.1.0): + - Instabug (= 14.1.0) - React-Core - RNReanimated (3.16.1): - DoubleConversion @@ -1786,6 +1807,7 @@ DEPENDENCIES: - react-native-maps (from `../node_modules/react-native-maps`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "react-native-slider (from `../node_modules/@react-native-community/slider`)" + - react-native-webview (from `../node_modules/react-native-webview`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -1915,6 +1937,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-safe-area-context" react-native-slider: :path: "../node_modules/@react-native-community/slider" + react-native-webview: + :path: "../node_modules/react-native-webview" React-nativeconfig: :path: "../node_modules/react-native/ReactCommon" React-NativeModulesApple: @@ -1993,7 +2017,7 @@ SPEC CHECKSUMS: Google-Maps-iOS-Utils: f77eab4c4326d7e6a277f8e23a0232402731913a GoogleMaps: 032f676450ba0779bd8ce16840690915f84e57ac hermes-engine: ea92f60f37dba025e293cbe4b4a548fd26b610a0 - Instabug: a0beffc01658773e2fac549845782f8937707dc4 + Instabug: 8cbca8974168c815658133e2813f5ac3a36f8e20 instabug-reactnative-ndk: d765ac289d56e8896398d02760d9abf2562fc641 OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74 RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740 @@ -2031,6 +2055,7 @@ SPEC CHECKSUMS: react-native-maps: 72a8a903f8a1b53e2c777ba79102078ab502e0bf react-native-safe-area-context: 142fade490cbebbe428640b8cbdb09daf17e8191 react-native-slider: 4a0f3386a38fc3d2d955efc515aef7096f7d1ee4 + react-native-webview: 926d2665cf3196e39c4449a72d136d0a53b9df8a React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794 React-NativeModulesApple: 9f7920224a3b0c7d04d77990067ded14cee3c614 React-perflogger: 59e1a3182dca2cee7b9f1f7aab204018d46d1914 @@ -2059,7 +2084,7 @@ SPEC CHECKSUMS: ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb RNGestureHandler: 511250b190a284388f9dd0d2e56c1df76f14cfb8 - RNInstabug: eaa8cde2bcd3c8e757c6dd5c0d33a20814f9658a + RNInstabug: 96e629f47c0af2e9455fbcf800d12049f980d873 RNReanimated: f42a5044d121d68e91680caacb0293f4274228eb RNScreens: c7ceced6a8384cb9be5e7a5e88e9e714401fd958 RNSVG: 8b1a777d54096b8c2a0fd38fc9d5a454332bbb4d diff --git a/examples/default/package.json b/examples/default/package.json index 617cdc17b8..26959bb0b9 100644 --- a/examples/default/package.json +++ b/examples/default/package.json @@ -32,6 +32,7 @@ "react-native-screens": "^3.35.0", "react-native-svg": "^15.8.0", "react-native-vector-icons": "^10.2.0", + "react-native-webview": "^13.12.3", "react-query": "^3.39.3" }, "devDependencies": { diff --git a/examples/default/src/navigation/HomeStack.tsx b/examples/default/src/navigation/HomeStack.tsx index 716ea05d9f..090aa65873 100644 --- a/examples/default/src/navigation/HomeStack.tsx +++ b/examples/default/src/navigation/HomeStack.tsx @@ -28,6 +28,9 @@ import { FlowsScreen } from '../screens/apm/FlowsScreen'; import { SessionReplayScreen } from '../screens/SessionReplayScreen'; import { LegacyModeScreen } from '../screens/LegacyModeScreen'; import { HttpScreen } from '../screens/apm/HttpScreen'; +import { WebViewsScreen } from '../screens/apm/webViews/WebViewsScreen'; +import { FullWebViewsScreen } from '../screens/apm/webViews/FullWebViewsScreen'; +import { PartialWebViewsScreen } from '../screens/apm/webViews/PartialWebViewsScreen'; export type HomeStackParamList = { Home: undefined; @@ -55,6 +58,9 @@ export type HomeStackParamList = { NetworkTraces: undefined; ExecutionTraces: undefined; AppFlows: undefined; + WebViews: undefined; + FullWebViews: undefined; + PartialWebViews: undefined; }; const HomeStack = createNativeStackNavigator(); @@ -142,6 +148,21 @@ export const HomeStackNavigator: React.FC = () => { options={{ title: 'LegacyMode' }} /> + + + ); }; diff --git a/examples/default/src/screens/BugReportingScreen.tsx b/examples/default/src/screens/BugReportingScreen.tsx index 95a8505699..a8afc14a8d 100644 --- a/examples/default/src/screens/BugReportingScreen.tsx +++ b/examples/default/src/screens/BugReportingScreen.tsx @@ -1,11 +1,20 @@ import React from 'react'; -import Instabug, { BugReporting, InvocationOption, ReportType } from 'instabug-reactnative'; +import Instabug, { + BugReporting, + InvocationOption, + ReportType, + ExtendedBugReportMode, + WelcomeMessageMode, +} from 'instabug-reactnative'; import { ListTile } from '../components/ListTile'; import { Screen } from '../components/Screen'; +import { useToast } from 'native-base'; +import { Section } from '../components/Section'; export const BugReportingScreen: React.FC = () => { + const toast = useToast(); return ( Instabug.show()} /> @@ -15,6 +24,59 @@ export const BugReportingScreen: React.FC = () => { onPress={() => BugReporting.show(ReportType.feedback, [InvocationOption.emailFieldHidden])} /> BugReporting.show(ReportType.question, [])} /> + + BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithRequiredFields) + } + /> + + BugReporting.setExtendedBugReportMode(ExtendedBugReportMode.enabledWithOptionalFields) + } + /> + Instabug.setSessionProfilerEnabled(true)} + /> + Instabug.showWelcomeMessage(WelcomeMessageMode.beta)} + /> + Instabug.showWelcomeMessage(WelcomeMessageMode.live)} + /> + +
+ + BugReporting.onInvokeHandler(function () { + Instabug.appendTags(['Invocation Handler tag1']); + }) + } + /> + + Instabug.onReportSubmitHandler(() => { + toast.show({ + description: 'Submission succeeded', + }); + }) + } + /> + + BugReporting.onSDKDismissedHandler(function () { + Instabug.setPrimaryColor('#FF0000'); + }) + } + /> +
); }; diff --git a/examples/default/src/screens/apm/APMScreen.tsx b/examples/default/src/screens/apm/APMScreen.tsx index 0b04e61919..d63ee65f4f 100644 --- a/examples/default/src/screens/apm/APMScreen.tsx +++ b/examples/default/src/screens/apm/APMScreen.tsx @@ -26,6 +26,7 @@ export const APMScreen: React.FC navigation.navigate('NetworkTraces')} /> navigation.navigate('ExecutionTraces')} /> navigation.navigate('AppFlows')} /> + navigation.navigate('WebViews')} /> ); }; diff --git a/examples/default/src/screens/apm/NetworkScreen.tsx b/examples/default/src/screens/apm/NetworkScreen.tsx index 8aa20f49f0..f9f057f612 100644 --- a/examples/default/src/screens/apm/NetworkScreen.tsx +++ b/examples/default/src/screens/apm/NetworkScreen.tsx @@ -94,6 +94,14 @@ export const NetworkScreen: React.FC< }; const { data, isError, isSuccess, isLoading, refetch } = useQuery('helloQuery', fetchGraphQlData); + const simulateNetworkRequest = () => { + axios.get('https://httpbin.org/anything', { + headers: { traceparent: 'Caught Header Example' }, + }); + }; + const simulateNetworkRequestWithoutHeader = () => { + axios.get('https://httpbin.org/anything'); + }; return ( @@ -111,7 +119,14 @@ export const NetworkScreen: React.FC< onPress={sendRequestToUrlUsingAxios} title="Send Request To Url Using Axios" /> - + simulateNetworkRequest()} + /> + simulateNetworkRequestWithoutHeader()} + /> refetch} title="Reload GraphQL" /> {isLoading && Loading...} diff --git a/examples/default/src/screens/apm/webViews/FullWebViewsScreen.tsx b/examples/default/src/screens/apm/webViews/FullWebViewsScreen.tsx new file mode 100644 index 0000000000..fbee5c028f --- /dev/null +++ b/examples/default/src/screens/apm/webViews/FullWebViewsScreen.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Screen } from '../../../components/Screen'; +import { WebView } from 'react-native-webview'; + +export const FullWebViewsScreen: React.FC = () => { + return ( + + + + ); +}; diff --git a/examples/default/src/screens/apm/webViews/PartialWebViewsScreen.tsx b/examples/default/src/screens/apm/webViews/PartialWebViewsScreen.tsx new file mode 100644 index 0000000000..6cb4a62639 --- /dev/null +++ b/examples/default/src/screens/apm/webViews/PartialWebViewsScreen.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Screen } from '../../../components/Screen'; +import { WebView } from 'react-native-webview'; +import { StyleSheet } from 'react-native'; + +export const PartialWebViewsScreen: React.FC = () => { + return ( + + + + + ); +}; +const styles = StyleSheet.create({ + webView: { + marginBottom: 20, + }, +}); diff --git a/examples/default/src/screens/apm/webViews/WebViewsScreen.tsx b/examples/default/src/screens/apm/webViews/WebViewsScreen.tsx new file mode 100644 index 0000000000..0c3309dfee --- /dev/null +++ b/examples/default/src/screens/apm/webViews/WebViewsScreen.tsx @@ -0,0 +1,16 @@ +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { HomeStackParamList } from '../../../navigation/HomeStack'; +import React from 'react'; +import { Screen } from '../../../components/Screen'; +import { ListTile } from '../../../components/ListTile'; + +export const WebViewsScreen: React.FC> = ({ + navigation, +}) => { + return ( + + navigation.navigate('FullWebViews')} /> + navigation.navigate('PartialWebViews')} /> + + ); +}; diff --git a/examples/default/yarn.lock b/examples/default/yarn.lock index 348eda2fa4..3e1f047c3f 100644 --- a/examples/default/yarn.lock +++ b/examples/default/yarn.lock @@ -4427,7 +4427,7 @@ intl-messageformat@^10.1.0: "@formatjs/icu-messageformat-parser" "2.9.1" tslib "2" -invariant@^2.2.4: +invariant@2.2.4, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -6329,6 +6329,14 @@ react-native-vector-icons@^10.2.0: prop-types "^15.7.2" yargs "^16.1.1" +react-native-webview@^13.12.3: + version "13.12.3" + resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-13.12.3.tgz#3aa9d2fc982ba2681e56d3e96e22b63a0d929270" + integrity sha512-Y1I5YyDYyE7NC96RHLhd2nxh7ymLYOYLTefgx5ixxw2OToQK0ow3OJ+o77QcI1Tuevj5PCxwqC/14ceS/7yPJQ== + dependencies: + escape-string-regexp "^4.0.0" + invariant "2.2.4" + react-native@0.75.4: version "0.75.4" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.75.4.tgz#40fc337b9c005521b5b7e039481bc4d444b009a9" diff --git a/ios/RNInstabug/InstabugAPMBridge.m b/ios/RNInstabug/InstabugAPMBridge.m index 0324be4a8b..daea8b4c1a 100644 --- a/ios/RNInstabug/InstabugAPMBridge.m +++ b/ios/RNInstabug/InstabugAPMBridge.m @@ -8,6 +8,7 @@ #import #import #import +#import "Util/IBGAPM+PrivateAPIs.h" @implementation InstabugAPMBridge @@ -110,6 +111,9 @@ - (id) init } + + + @synthesize description; @synthesize hash; diff --git a/ios/RNInstabug/InstabugReactBridge.h b/ios/RNInstabug/InstabugReactBridge.h index a3cfc21c13..bca04ddfd0 100644 --- a/ios/RNInstabug/InstabugReactBridge.h +++ b/ios/RNInstabug/InstabugReactBridge.h @@ -105,7 +105,9 @@ */ - (void)setNetworkLoggingEnabled:(BOOL)isEnabled; - +- (void)isW3ExternalTraceIDEnabled:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject; +- (void)isW3ExternalGeneratedHeaderEnabled:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject; +- (void)isW3CaughtHeaderEnabled:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject; - (void)networkLogIOS:(NSString * _Nonnull)url method:(NSString * _Nonnull)method requestBody:(NSString * _Nonnull)requestBody @@ -121,7 +123,8 @@ startTime:(double)startTime duration:(double)duration gqlQueryName:(NSString * _Nullable)gqlQueryName - serverErrorMessage:(NSString * _Nullable)serverErrorMessage; + serverErrorMessage:(NSString * _Nullable)serverErrorMessage +w3cExternalTraceAttributes:(NSDictionary * _Nullable)w3cExternalTraceAttributes; /* +------------------------------------------------------------------------+ diff --git a/ios/RNInstabug/InstabugReactBridge.m b/ios/RNInstabug/InstabugReactBridge.m index 534b849081..e7ca15600e 100644 --- a/ios/RNInstabug/InstabugReactBridge.m +++ b/ios/RNInstabug/InstabugReactBridge.m @@ -298,7 +298,14 @@ - (dispatch_queue_t)methodQueue { startTime:(double)startTime duration:(double)duration gqlQueryName:(NSString * _Nullable)gqlQueryName - serverErrorMessage:(NSString * _Nullable)serverErrorMessage) { + serverErrorMessage:(NSString * _Nullable)serverErrorMessage + w3cExternalTraceAttributes:(NSDictionary * _Nullable)w3cExternalTraceAttributes){ + NSNumber *isW3cCaught = (w3cExternalTraceAttributes[@"isW3cHeaderFound"] != [NSNull null]) ? w3cExternalTraceAttributes[@"isW3cHeaderFound"] : nil; + NSNumber * partialID = (w3cExternalTraceAttributes[@"partialId"] != [NSNull null]) ? w3cExternalTraceAttributes[@"partialId"] : nil; + NSNumber * timestamp = (w3cExternalTraceAttributes[@"networkStartTimeInSeconds"] != [NSNull null]) ? w3cExternalTraceAttributes[@"networkStartTimeInSeconds"] : nil; + NSString * generatedW3CTraceparent = (w3cExternalTraceAttributes[@"w3cGeneratedHeader"] != [NSNull null]) ? w3cExternalTraceAttributes[@"w3cGeneratedHeader"] : nil; + NSString * caughtW3CTraceparent = (w3cExternalTraceAttributes[@"w3cCaughtHeader"] != [NSNull null]) ? w3cExternalTraceAttributes[@"w3cCaughtHeader"] : nil; + [IBGNetworkLogger addNetworkLogWithUrl:url method:method requestBody:requestBody @@ -315,11 +322,12 @@ - (dispatch_queue_t)methodQueue { duration:duration * 1000 gqlQueryName:gqlQueryName serverErrorMessage:serverErrorMessage - isW3cCaughted:nil - partialID:nil - timestamp:nil - generatedW3CTraceparent:nil - caughtedW3CTraceparent:nil]; + isW3cCaughted:isW3cCaught + partialID:partialID + timestamp:timestamp + generatedW3CTraceparent:generatedW3CTraceparent + caughtedW3CTraceparent:caughtW3CTraceparent + ]; } RCT_EXPORT_METHOD(addPrivateView: (nonnull NSNumber *)reactTag) { @@ -369,7 +377,7 @@ - (dispatch_queue_t)methodQueue { [featureFlags addObject:[[IBGFeatureFlag alloc] initWithName:key variant:variant]]; } } - + [Instabug addFeatureFlags:featureFlags]; } @@ -378,7 +386,7 @@ - (dispatch_queue_t)methodQueue { for(id item in featureFlags){ [features addObject:[[IBGFeatureFlag alloc] initWithName:item]]; } - + @try { [Instabug removeFeatureFlags:features]; } @@ -395,6 +403,17 @@ - (dispatch_queue_t)methodQueue { [Instabug willRedirectToAppStore]; } +RCT_EXPORT_METHOD(isW3ExternalTraceIDEnabled:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject) { + resolve(@(IBGNetworkLogger.w3ExternalTraceIDEnabled)); +} +RCT_EXPORT_METHOD(isW3ExternalGeneratedHeaderEnabled:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject) { + resolve(@(IBGNetworkLogger.w3ExternalGeneratedHeaderEnabled)); +} +RCT_EXPORT_METHOD(isW3CaughtHeaderEnabled:(RCTPromiseResolveBlock)resolve :(RCTPromiseRejectBlock)reject) { + resolve(@(IBGNetworkLogger.w3CaughtHeaderEnabled)); +} + + - (NSDictionary *)constantsToExport { return ArgsRegistry.getAll; } diff --git a/ios/RNInstabug/Util/IBGAPM+PrivateAPIs.h b/ios/RNInstabug/Util/IBGAPM+PrivateAPIs.h new file mode 100644 index 0000000000..a451a0ad50 --- /dev/null +++ b/ios/RNInstabug/Util/IBGAPM+PrivateAPIs.h @@ -0,0 +1,15 @@ +// +// IBGAPM+PrivateAPIs.h +// Pods +// +// Created by Instabug on 02/06/2024. +// + +//#import "IBGAPM.h" + +@interface IBGAPM (PrivateAPIs) + +@property (class, atomic, assign) BOOL networkEnabled; + + +@end diff --git a/ios/RNInstabug/Util/IBGNetworkLogger+CP.h b/ios/RNInstabug/Util/IBGNetworkLogger+CP.h index 436553620e..805591d0ce 100644 --- a/ios/RNInstabug/Util/IBGNetworkLogger+CP.h +++ b/ios/RNInstabug/Util/IBGNetworkLogger+CP.h @@ -4,6 +4,11 @@ NS_ASSUME_NONNULL_BEGIN @interface IBGNetworkLogger (CP) +@property (class, atomic, assign) BOOL w3ExternalTraceIDEnabled; +@property (class, atomic, assign) BOOL w3ExternalGeneratedHeaderEnabled; +@property (class, atomic, assign) BOOL w3CaughtHeaderEnabled; + + + (void)disableAutomaticCapturingOfNetworkLogs; + (void)addNetworkLogWithUrl:(NSString *_Nonnull)url method:(NSString *_Nonnull)method @@ -27,6 +32,28 @@ NS_ASSUME_NONNULL_BEGIN generatedW3CTraceparent:(NSString * _Nullable)generatedW3CTraceparent caughtedW3CTraceparent:(NSString * _Nullable)caughtedW3CTraceparent; ++ (void)addNetworkLogWithUrl:(NSString *)url + method:(NSString *)method + requestBody:(NSString *)request + requestBodySize:(int64_t)requestBodySize + responseBody:(NSString *)response + responseBodySize:(int64_t)responseBodySize + responseCode:(int32_t)code + requestHeaders:(NSDictionary *)requestHeaders + responseHeaders:(NSDictionary *)responseHeaders + contentType:(NSString *)contentType + errorDomain:(NSString *)errorDomain + errorCode:(int32_t)errorCode + startTime:(int64_t)startTime + duration:(int64_t) duration + gqlQueryName:(NSString * _Nullable)gqlQueryName + serverErrorMessage:(NSString * _Nullable)serverErrorMessage + isW3cCaughted:(NSNumber * _Nullable)isW3cCaughted + partialID:(NSNumber * _Nullable)partialID + timestamp:(NSNumber * _Nullable)timestamp + generatedW3CTraceparent:(NSString * _Nullable)generatedW3CTraceparent + caughtedW3CTraceparent:(NSString * _Nullable)caughtedW3CTraceparent; + @end NS_ASSUME_NONNULL_END diff --git a/ios/native.rb b/ios/native.rb index 6970521416..4fc710cbe5 100644 --- a/ios/native.rb +++ b/ios/native.rb @@ -1,4 +1,4 @@ -$instabug = { :version => '14.0.0' } +$instabug = { :version => '14.1.0' } def use_instabug! (spec = nil) version = $instabug[:version] diff --git a/package.json b/package.json index 75005c28bc..e065cb993d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "instabug-reactnative", "description": "React Native plugin for integrating the Instabug SDK", - "version": "14.0.0", + "version": "14.1.0", "author": "Instabug (https://instabug.com)", "repository": "github:Instabug/Instabug-React-Native", "homepage": "https://www.instabug.com/platforms/react-native", diff --git a/src/models/W3cExternalTraceAttributes.ts b/src/models/W3cExternalTraceAttributes.ts new file mode 100644 index 0000000000..f4e7ab6a45 --- /dev/null +++ b/src/models/W3cExternalTraceAttributes.ts @@ -0,0 +1,22 @@ +export type W3cExternalTraceAttributes = { + /** + * A key that determines if the traceparent header was found + */ + isW3cHeaderFound: boolean | null; + /** + * A unique identifier for the trace generated by the SDK in case of no cought header found + */ + partialId: number | null; + /** + * The start time of the network request + */ + networkStartTimeInSeconds: number | null; + /** + * The traceparent header generated by the SDK + */ + w3cGeneratedHeader: string | null; + /** + * The traceparent header received by the server + */ + w3cCaughtHeader: string | null; +}; diff --git a/src/modules/CrashReporting.ts b/src/modules/CrashReporting.ts index c374c7d772..858e035a32 100644 --- a/src/modules/CrashReporting.ts +++ b/src/modules/CrashReporting.ts @@ -5,6 +5,7 @@ import InstabugUtils from '../utils/InstabugUtils'; import { Platform } from 'react-native'; import type { NonFatalOptions } from '../models/NonFatalOptions'; import { NonFatalErrorLevel } from '../utils/Enums'; +import { Logger } from '../utils/logger'; /** * Enables and disables everything related to crash reporting including intercepting @@ -35,7 +36,7 @@ export const reportError = (error: ExtendedError, nonFatalOptions: NonFatalOptio ), ); } else { - console.warn( + Logger.warn( `IBG-RN: The error ${error} has been omitted because only error type is supported.`, ); return; diff --git a/src/modules/Instabug.ts b/src/modules/Instabug.ts index 1d528fba94..91f6c5c127 100644 --- a/src/modules/Instabug.ts +++ b/src/modules/Instabug.ts @@ -10,7 +10,8 @@ import type { NavigationAction, NavigationState as NavigationStateV4 } from 'rea import type { InstabugConfig } from '../models/InstabugConfig'; import Report from '../models/Report'; -import { NativeEvents, NativeInstabug, emitter } from '../native/NativeInstabug'; +import { emitter, NativeEvents, NativeInstabug } from '../native/NativeInstabug'; +import { registerW3CFlagsListener } from '../utils/FeatureFlags'; import { ColorTheme, Locale, @@ -26,6 +27,8 @@ import { captureUnhandledRejections } from '../utils/UnhandledRejectionTracking' import type { ReproConfig } from '../models/ReproConfig'; import type { FeatureFlag } from '../models/FeatureFlag'; import InstabugConstants from '../utils/InstabugConstants'; +import { InstabugRNConfig } from '../utils/config'; +import { Logger } from '../utils/logger'; let _currentScreen: string | null = null; let _lastScreen: string | null = null; @@ -68,6 +71,10 @@ export const init = (config: InstabugConfig) => { InstabugUtils.captureJsErrors(); captureUnhandledRejections(); + if (Platform.OS === 'android') { + registerW3CFlagsListener(); + } + // Default networkInterceptionMode to JavaScript if (config.networkInterceptionMode == null) { config.networkInterceptionMode = NetworkInterceptionMode.javascript; @@ -88,6 +95,8 @@ export const init = (config: InstabugConfig) => { _isFirstScreen = true; _currentScreen = firstScreen; + InstabugRNConfig.debugLogsLevel = config.debugLogsLevel ?? LogLevel.error; + reportCurrentViewForAndroid(firstScreen); setTimeout(() => { if (_currentScreen === firstScreen) { @@ -382,9 +391,10 @@ export const setReproStepsConfig = (config: ReproConfig) => { */ export const setUserAttribute = (key: string, value: string) => { if (!key || typeof key !== 'string' || typeof value !== 'string') { - console.error(InstabugConstants.SET_USER_ATTRIBUTES_ERROR_TYPE_MESSAGE); + Logger.error(InstabugConstants.SET_USER_ATTRIBUTES_ERROR_TYPE_MESSAGE); return; } + NativeInstabug.setUserAttribute(key, value); }; @@ -406,7 +416,7 @@ export const getUserAttribute = async (key: string): Promise => { */ export const removeUserAttribute = (key: string) => { if (!key || typeof key !== 'string') { - console.error(InstabugConstants.REMOVE_USER_ATTRIBUTES_ERROR_TYPE_MESSAGE); + Logger.error(InstabugConstants.REMOVE_USER_ATTRIBUTES_ERROR_TYPE_MESSAGE); return; } @@ -633,6 +643,13 @@ export const willRedirectToStore = () => { NativeInstabug.willRedirectToStore(); }; +/** + * This API has be called when changing the default Metro server port (8081) to exclude the DEV URL from network logging. + */ +export const setMetroDevServerPort = (port: number) => { + InstabugRNConfig.metroDevServerPort = port.toString(); +}; + export const componentDidAppearListener = (event: ComponentDidAppearEvent) => { if (_isFirstScreen) { _lastScreen = event.componentName; @@ -644,3 +661,20 @@ export const componentDidAppearListener = (event: ComponentDidAppearEvent) => { _lastScreen = event.componentName; } }; + +/** + * Sets listener to W3ExternalTraceID flag changes + * @param handler A callback that gets the update value of the flag + */ +export const _registerW3CFlagsChangeListener = ( + handler: (payload: { + isW3ExternalTraceIDEnabled: boolean; + isW3ExternalGeneratedHeaderEnabled: boolean; + isW3CaughtHeaderEnabled: boolean; + }) => void, +) => { + emitter.addListener(NativeEvents.ON_W3C_FLAGS_CHANGE, (payload) => { + handler(payload); + }); + NativeInstabug.registerW3CFlagsChangeListener(); +}; diff --git a/src/modules/NetworkLogger.ts b/src/modules/NetworkLogger.ts index 67f3a54ccf..1e40b9fa9b 100644 --- a/src/modules/NetworkLogger.ts +++ b/src/modules/NetworkLogger.ts @@ -2,7 +2,9 @@ import type { RequestHandler } from '@apollo/client'; import InstabugConstants from '../utils/InstabugConstants'; import xhr, { NetworkData, ProgressCallback } from '../utils/XhrNetworkInterceptor'; -import { reportNetworkLog, isContentTypeNotAllowed } from '../utils/InstabugUtils'; +import { isContentTypeNotAllowed, reportNetworkLog } from '../utils/InstabugUtils'; +import { InstabugRNConfig } from '../utils/config'; +import { Logger } from '../utils/logger'; export type { NetworkData }; @@ -10,6 +12,11 @@ export type NetworkDataObfuscationHandler = (data: NetworkData) => Promise { network = await _networkDataObfuscationHandler(network); } + if (__DEV__) { + const urlPort = getPortFromUrl(network.url); + if (urlPort === InstabugRNConfig.metroDevServerPort) { + return; + } + } if (network.requestBodySize > InstabugConstants.MAX_NETWORK_BODY_SIZE_IN_BYTES) { network.requestBody = InstabugConstants.MAX_REQUEST_BODY_SIZE_EXCEEDED_MESSAGE; - console.warn('IBG-RN:', InstabugConstants.MAX_REQUEST_BODY_SIZE_EXCEEDED_MESSAGE); + Logger.warn('IBG-RN:', InstabugConstants.MAX_REQUEST_BODY_SIZE_EXCEEDED_MESSAGE); } if (network.responseBodySize > InstabugConstants.MAX_NETWORK_BODY_SIZE_IN_BYTES) { network.responseBody = InstabugConstants.MAX_RESPONSE_BODY_SIZE_EXCEEDED_MESSAGE; - console.warn('IBG-RN:', InstabugConstants.MAX_RESPONSE_BODY_SIZE_EXCEEDED_MESSAGE); + Logger.warn('IBG-RN:', InstabugConstants.MAX_RESPONSE_BODY_SIZE_EXCEEDED_MESSAGE); } if (network.requestBody && isContentTypeNotAllowed(network.requestContentType)) { network.requestBody = `Body is omitted because content type ${network.requestContentType} isn't supported`; - console.warn( + Logger.warn( `IBG-RN: The request body for the network request with URL ${network.url} has been omitted because the content type ${network.requestContentType} isn't supported.`, ); } if (network.responseBody && isContentTypeNotAllowed(network.contentType)) { network.responseBody = `Body is omitted because content type ${network.contentType} isn't supported`; - console.warn( + Logger.warn( `IBG-RN: The response body for the network request with URL ${network.url} has been omitted because the content type ${network.contentType} isn't supported.`, ); } reportNetworkLog(network); } catch (e) { - console.error(e); + Logger.error(e); } } }); @@ -96,7 +109,7 @@ export const apolloLinkRequestHandler: RequestHandler = (operation, forward) => return { headers: newHeaders }; }); } catch (e) { - console.error(e); + Logger.error(e); } return forward(operation); diff --git a/src/native/NativeAPM.ts b/src/native/NativeAPM.ts index b1981cfe37..9fa30b702c 100644 --- a/src/native/NativeAPM.ts +++ b/src/native/NativeAPM.ts @@ -1,5 +1,7 @@ import type { NativeModule } from 'react-native'; +import { NativeEventEmitter } from 'react-native'; +import type { W3cExternalTraceAttributes } from '../models/W3cExternalTraceAttributes'; import { NativeModules } from './NativePackage'; export interface ApmNativeModule extends NativeModule { @@ -22,6 +24,7 @@ export interface ApmNativeModule extends NativeModule { statusCode: number, responseContentType: string, errorDomain: string, + w3cExternalTraceAttributes: W3cExternalTraceAttributes, gqlQueryName?: string, serverErrorMessage?: string, ): void; @@ -48,3 +51,5 @@ export interface ApmNativeModule extends NativeModule { } export const NativeAPM = NativeModules.IBGAPM; + +export const emitter = new NativeEventEmitter(NativeAPM); diff --git a/src/native/NativeInstabug.ts b/src/native/NativeInstabug.ts index 3b72f5951f..5f0628ef71 100644 --- a/src/native/NativeInstabug.ts +++ b/src/native/NativeInstabug.ts @@ -11,6 +11,7 @@ import type { WelcomeMessageMode, } from '../utils/Enums'; import type { NativeConstants } from './NativeConstants'; +import type { W3cExternalTraceAttributes } from '../models/W3cExternalTraceAttributes'; import { NativeModules } from './NativePackage'; export interface InstabugNativeModule extends NativeModule { @@ -67,6 +68,7 @@ export interface InstabugNativeModule extends NativeModule { duration: number, gqlQueryName: string | undefined, serverErrorMessage: string | undefined, + W3cExternalTraceAttributes: W3cExternalTraceAttributes, ): void; setNetworkLoggingEnabled(isEnabled: boolean): void; @@ -140,12 +142,23 @@ export interface InstabugNativeModule extends NativeModule { addFileAttachmentWithURLToReport(url: string, filename?: string): void; addFileAttachmentWithDataToReport(data: string, filename?: string): void; willRedirectToStore(): void; + + // W3C Feature Flags + isW3ExternalTraceIDEnabled(): Promise; + + isW3ExternalGeneratedHeaderEnabled(): Promise; + + isW3CaughtHeaderEnabled(): Promise; + + // W3C Feature Flags Listener for Android + registerW3CFlagsChangeListener(): void; } export const NativeInstabug = NativeModules.Instabug; export enum NativeEvents { PRESENDING_HANDLER = 'IBGpreSendingHandler', + ON_W3C_FLAGS_CHANGE = 'IBGOnNewW3CFlagsUpdateReceivedCallback', } export const emitter = new NativeEventEmitter(NativeInstabug); diff --git a/src/utils/FeatureFlags.ts b/src/utils/FeatureFlags.ts new file mode 100644 index 0000000000..479ab7ba47 --- /dev/null +++ b/src/utils/FeatureFlags.ts @@ -0,0 +1,28 @@ +import { NativeInstabug } from '../native/NativeInstabug'; +import { _registerW3CFlagsChangeListener } from '../modules/Instabug'; + +export const FeatureFlags = { + isW3ExternalTraceID: () => NativeInstabug.isW3ExternalTraceIDEnabled(), + isW3ExternalGeneratedHeader: () => NativeInstabug.isW3ExternalGeneratedHeaderEnabled(), + isW3CaughtHeader: () => NativeInstabug.isW3CaughtHeaderEnabled(), +}; + +export const registerW3CFlagsListener = () => { + _registerW3CFlagsChangeListener( + (res: { + isW3ExternalTraceIDEnabled: boolean; + isW3ExternalGeneratedHeaderEnabled: boolean; + isW3CaughtHeaderEnabled: boolean; + }) => { + FeatureFlags.isW3ExternalTraceID = async () => { + return res.isW3ExternalTraceIDEnabled; + }; + FeatureFlags.isW3ExternalGeneratedHeader = async () => { + return res.isW3ExternalGeneratedHeaderEnabled; + }; + FeatureFlags.isW3CaughtHeader = async () => { + return res.isW3CaughtHeaderEnabled; + }; + }, + ); +}; diff --git a/src/utils/InstabugConstants.ts b/src/utils/InstabugConstants.ts index 6d117d9871..aedc840701 100644 --- a/src/utils/InstabugConstants.ts +++ b/src/utils/InstabugConstants.ts @@ -11,6 +11,7 @@ const InstabugConstants = { 'IBG-RN: Expected key and value passed to setUserAttribute to be of type string', REMOVE_USER_ATTRIBUTES_ERROR_TYPE_MESSAGE: 'IBG-RN: Expected key and value passed to removeUserAttribute to be of type string', + DEFAULT_METRO_PORT: '8081', }; export default InstabugConstants; diff --git a/src/utils/InstabugUtils.ts b/src/utils/InstabugUtils.ts index d4238f14f0..df19f0d42a 100644 --- a/src/utils/InstabugUtils.ts +++ b/src/utils/InstabugUtils.ts @@ -126,6 +126,44 @@ export async function sendCrashReport( return remoteSenderCallback(jsonObject); } +/** + * Generate random 32 bit unsigned integer Hexadecimal (8 chars) lower case letters + * Should not return all zeros + */ +export const generateTracePartialId = () => { + let randomNumber: number; + let hexString: string; + + do { + randomNumber = Math.floor(Math.random() * 0xffffffff); + hexString = randomNumber.toString(16).padStart(8, '0'); + } while (hexString === '00000000'); + + return { numberPartilId: randomNumber, hexStringPartialId: hexString.toLowerCase() }; +}; +/** + * Generate W3C header in the format of {version}-{trace-id}-{parent-id}-{trace-flag} + * @param networkStartTime + * @returns w3c header + */ +export const generateW3CHeader = (networkStartTime: number) => { + const { hexStringPartialId, numberPartilId } = generateTracePartialId(); + + const TRACESTATE = '4942472d'; + const VERSION = '00'; + const TRACE_FLAG = '01'; + + const timestampInSeconds = Math.floor(networkStartTime.valueOf() / 1000); + const hexaDigitsTimestamp = timestampInSeconds.toString(16).toLowerCase(); + const traceId = `${hexaDigitsTimestamp}${hexStringPartialId}${hexaDigitsTimestamp}${hexStringPartialId}`; + const parentId = `${TRACESTATE}${hexStringPartialId}`; + + return { + timestampInSeconds, + partialId: numberPartilId, + w3cHeader: `${VERSION}-${traceId}-${parentId}-${TRACE_FLAG}`, + }; +}; export function isContentTypeNotAllowed(contentType: string) { const allowed = [ @@ -171,6 +209,13 @@ export function reportNetworkLog(network: NetworkData) { network.responseCode, network.contentType, network.errorDomain, + { + isW3cHeaderFound: network.isW3cHeaderFound, + partialId: network.partialId, + networkStartTimeInSeconds: network.networkStartTimeInSeconds, + w3cGeneratedHeader: network.w3cGeneratedHeader, + w3cCaughtHeader: network.w3cCaughtHeader, + }, network.gqlQueryName, network.serverErrorMessage, ); @@ -192,6 +237,13 @@ export function reportNetworkLog(network: NetworkData) { network.duration, network.gqlQueryName, network.serverErrorMessage, + { + isW3cHeaderFound: network.isW3cHeaderFound, + partialId: network.partialId, + networkStartTimeInSeconds: network.networkStartTimeInSeconds, + w3cGeneratedHeader: network.w3cGeneratedHeader, + w3cCaughtHeader: network.w3cCaughtHeader, + }, ); } } @@ -204,4 +256,6 @@ export default { getStackTrace, stringifyIfNotString, sendCrashReport, + generateTracePartialId, + generateW3CHeader, }; diff --git a/src/utils/UnhandledRejectionTracking.ts b/src/utils/UnhandledRejectionTracking.ts index d8049a9b09..9cbe0dc91a 100644 --- a/src/utils/UnhandledRejectionTracking.ts +++ b/src/utils/UnhandledRejectionTracking.ts @@ -2,6 +2,7 @@ import tracking, { RejectionTrackingOptions } from 'promise/setimmediate/rejecti import { sendCrashReport } from './InstabugUtils'; import { NativeCrashReporting } from '../native/NativeCrashReporting'; import { NonFatalErrorLevel } from './Enums'; +import { Logger } from './logger'; export interface HermesInternalType { enablePromiseRejectionTracker?: (options?: RejectionTrackingOptions) => void; @@ -113,5 +114,5 @@ function _originalOnUnhandled(id: number, rejection: unknown = {}) { `Possible Unhandled Promise Rejection (id: ${id}):\n` + `${message ?? ''}\n` + (stack == null ? '' : stack); - console.warn(warning); + Logger.warn(warning); } diff --git a/src/utils/XhrNetworkInterceptor.ts b/src/utils/XhrNetworkInterceptor.ts index 98c5ef9cc5..4443940362 100644 --- a/src/utils/XhrNetworkInterceptor.ts +++ b/src/utils/XhrNetworkInterceptor.ts @@ -1,5 +1,7 @@ import InstabugConstants from './InstabugConstants'; -import { stringifyIfNotString } from './InstabugUtils'; +import { stringifyIfNotString, generateW3CHeader } from './InstabugUtils'; + +import { FeatureFlags } from '../utils/FeatureFlags'; export type ProgressCallback = (totalBytesSent: number, totalBytesExpectedToSend: number) => void; export type NetworkDataCallback = (data: NetworkData) => void; @@ -22,6 +24,11 @@ export interface NetworkData { gqlQueryName?: string; serverErrorMessage: string; requestContentType: string; + isW3cHeaderFound: boolean | null; + partialId: number | null; + networkStartTimeInSeconds: number | null; + w3cGeneratedHeader: string | null; + w3cCaughtHeader: string | null; } const XMLHttpRequest = global.XMLHttpRequest; @@ -53,8 +60,85 @@ const _reset = () => { gqlQueryName: '', serverErrorMessage: '', requestContentType: '', + isW3cHeaderFound: null, + partialId: null, + networkStartTimeInSeconds: null, + w3cGeneratedHeader: null, + w3cCaughtHeader: null, }; }; +const getTraceparentHeader = async (networkData: NetworkData) => { + const [ + isW3cExternalTraceIDEnabled, + isW3cExternalGeneratedHeaderEnabled, + isW3cCaughtHeaderEnabled, + ] = await Promise.all([ + FeatureFlags.isW3ExternalTraceID(), + FeatureFlags.isW3ExternalGeneratedHeader(), + FeatureFlags.isW3CaughtHeader(), + ]); + + return injectHeaders(networkData, { + isW3cExternalTraceIDEnabled, + isW3cExternalGeneratedHeaderEnabled, + isW3cCaughtHeaderEnabled, + }); +}; + +export const injectHeaders = async ( + networkData: NetworkData, + featureFlags: { + isW3cExternalTraceIDEnabled: boolean; + isW3cExternalGeneratedHeaderEnabled: boolean; + isW3cCaughtHeaderEnabled: boolean; + }, +) => { + const { + isW3cExternalTraceIDEnabled, + isW3cExternalGeneratedHeaderEnabled, + isW3cCaughtHeaderEnabled, + } = featureFlags; + + if (!isW3cExternalTraceIDEnabled) { + return; + } + + const isHeaderFound = networkData.requestHeaders.traceparent != null; + + networkData.isW3cHeaderFound = isHeaderFound; + + const injectionMethodology = isHeaderFound + ? identifyCaughtHeader(networkData, isW3cCaughtHeaderEnabled) + : injectGeneratedData(networkData, isW3cExternalGeneratedHeaderEnabled); + return injectionMethodology; +}; + +const identifyCaughtHeader = async ( + networkData: NetworkData, + isW3cCaughtHeaderEnabled: boolean, +) => { + if (isW3cCaughtHeaderEnabled) { + networkData.w3cCaughtHeader = networkData.requestHeaders.traceparent; + return networkData.requestHeaders.traceparent; + } + return; +}; + +const injectGeneratedData = ( + networkData: NetworkData, + isW3cExternalGeneratedHeaderEnabled: boolean, +) => { + const { timestampInSeconds, partialId, w3cHeader } = generateW3CHeader(networkData.startTime); + networkData.partialId = partialId; + networkData.networkStartTimeInSeconds = timestampInSeconds; + + if (isW3cExternalGeneratedHeaderEnabled) { + networkData.w3cGeneratedHeader = w3cHeader; + return w3cHeader; + } + + return; +}; export default { setOnDoneCallback(callback: NetworkDataCallback) { @@ -91,7 +175,7 @@ export default { originalXHRSetRequestHeader.apply(this, [header, value]); }; - XMLHttpRequest.prototype.send = function (data) { + XMLHttpRequest.prototype.send = async function (data) { const cloneNetwork = JSON.parse(JSON.stringify(network)); cloneNetwork.requestBody = data ? data : ''; @@ -226,6 +310,10 @@ export default { } cloneNetwork.startTime = Date.now(); + const traceparent = await getTraceparentHeader(cloneNetwork); + if (traceparent) { + this.setRequestHeader('Traceparent', traceparent); + } originalXHRSend.apply(this, [data]); }; isInterceptorEnabled = true; diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 0000000000..de7073d931 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,7 @@ +import InstabugConstants from './InstabugConstants'; +import { LogLevel } from './Enums'; + +export const InstabugRNConfig = { + metroDevServerPort: InstabugConstants.DEFAULT_METRO_PORT, + debugLogsLevel: LogLevel.error, +}; diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000000..e43d740d03 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,54 @@ +import { InstabugRNConfig } from './config'; +import { LogLevel } from './Enums'; + +export class Logger { + private static shouldLog(level: LogLevel): boolean { + const currentLevel = InstabugRNConfig.debugLogsLevel; + + // Return true if the current log level is equal to or more verbose than the requested level + const logLevelHierarchy: Record = { + [LogLevel.verbose]: 3, + [LogLevel.debug]: 2, + [LogLevel.error]: 1, + [LogLevel.none]: 0, + }; + + return logLevelHierarchy[currentLevel] >= logLevelHierarchy[level]; + } + + // General logging method that takes a logging function as an argument + private static logMessage( + level: LogLevel, + logMethod: (...args: any[]) => void, + message?: any, + ...optionalParams: any[] + ): void { + if (this.shouldLog(level)) { + logMethod(message, ...optionalParams); + } + } + + static error(message?: any, ...optionalParams: any[]) { + this.logMessage(LogLevel.error, console.error, message, ...optionalParams); // Pass console.error for errors + } + + static info(message?: any, ...optionalParams: any[]) { + this.logMessage(LogLevel.verbose, console.info, message, ...optionalParams); // Pass console.info for info + } + + static log(message?: any, ...optionalParams: any[]) { + this.logMessage(LogLevel.verbose, console.log, message, ...optionalParams); // Default log method + } + + static warn(message?: any, ...optionalParams: any[]) { + this.logMessage(LogLevel.debug, console.warn, message, ...optionalParams); // Use console.warn for debug + } + + static trace(message?: any, ...optionalParams: any[]) { + this.logMessage(LogLevel.debug, console.trace, message, ...optionalParams); // Use console.trace for debugging + } + + static debug(message?: any, ...optionalParams: any[]) { + this.logMessage(LogLevel.debug, console.debug, message, ...optionalParams); // Use console.debug for debug logs + } +} diff --git a/test/mocks/mockInstabug.ts b/test/mocks/mockInstabug.ts index 5139afcde3..7b3cf2e695 100644 --- a/test/mocks/mockInstabug.ts +++ b/test/mocks/mockInstabug.ts @@ -69,6 +69,10 @@ const mockInstabug: InstabugNativeModule = { addFileAttachmentWithDataToReport: jest.fn(), setNetworkLoggingEnabled: jest.fn(), willRedirectToStore: jest.fn(), + isW3ExternalTraceIDEnabled: jest.fn(), + isW3ExternalGeneratedHeaderEnabled: jest.fn(), + isW3CaughtHeaderEnabled: jest.fn(), + registerW3CFlagsChangeListener: jest.fn(), }; export default mockInstabug; diff --git a/test/modules/Instabug.spec.ts b/test/modules/Instabug.spec.ts index 46b4b208e1..d1bca25c90 100644 --- a/test/modules/Instabug.spec.ts +++ b/test/modules/Instabug.spec.ts @@ -24,6 +24,7 @@ import { import InstabugUtils from '../../src/utils/InstabugUtils'; import type { FeatureFlag } from '../../src/models/FeatureFlag'; import InstabugConstants from '../../src/utils/InstabugConstants'; +import { Logger } from '../../src/utils/logger'; describe('Instabug Module', () => { beforeEach(() => { @@ -641,7 +642,7 @@ describe('Instabug Module', () => { [{}, 'value'], ['key', []], ])("should fail if key and value aren't strings when calling setUserAttribute", (key, value) => { - const logSpy = jest.spyOn(console, 'error'); + const logSpy = jest.spyOn(Logger, 'error'); // @ts-ignore Instabug.setUserAttribute(key, value); @@ -870,4 +871,20 @@ describe('Instabug Module', () => { Instabug.willRedirectToStore(); expect(NativeInstabug.willRedirectToStore).toBeCalledTimes(1); }); + + it('should register W3C flag listener', async () => { + const callback = jest.fn(); + Instabug._registerW3CFlagsChangeListener(callback); + + expect(NativeInstabug.registerW3CFlagsChangeListener).toBeCalledTimes(1); + }); + + it('should invoke callback on emitting the event IBGOnNewW3CFlagsUpdateReceivedCallback', () => { + const callback = jest.fn(); + Instabug._registerW3CFlagsChangeListener(callback); + emitter.emit(NativeEvents.ON_W3C_FLAGS_CHANGE); + + expect(emitter.listenerCount(NativeEvents.ON_W3C_FLAGS_CHANGE)).toBe(1); + expect(callback).toHaveBeenCalled(); + }); }); diff --git a/test/modules/NetworkLogger.spec.ts b/test/modules/NetworkLogger.spec.ts index 71dd2dd778..dbf35eddb9 100644 --- a/test/modules/NetworkLogger.spec.ts +++ b/test/modules/NetworkLogger.spec.ts @@ -7,6 +7,7 @@ import * as NetworkLogger from '../../src/modules/NetworkLogger'; import Interceptor from '../../src/utils/XhrNetworkInterceptor'; import { isContentTypeNotAllowed, reportNetworkLog } from '../../src/utils/InstabugUtils'; import InstabugConstants from '../../src/utils/InstabugConstants'; +import { Logger } from '../../src/utils/logger'; const clone = (obj: T): T => { return JSON.parse(JSON.stringify(obj)); @@ -30,6 +31,11 @@ describe('NetworkLogger Module', () => { startTime: 0, serverErrorMessage: '', requestContentType: 'application/json', + isW3cHeaderFound: null, + partialId: null, + networkStartTimeInSeconds: null, + w3cGeneratedHeader: null, + w3cCaughtHeader: null, }; beforeEach(() => { @@ -89,7 +95,7 @@ describe('NetworkLogger Module', () => { it('should not break if network data obfuscation fails', async () => { // Avoid the console.error to clutter the test log - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = jest.spyOn(Logger, 'error').mockImplementation(() => {}); // Make a circular object, this should make JSON.stringify fail const handler = jest.fn(() => { @@ -133,7 +139,7 @@ describe('NetworkLogger Module', () => { }); it('should not break if apollo handler throws an error', async () => { - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const consoleSpy = jest.spyOn(Logger, 'error').mockImplementation(() => {}); const operation = { setContext: jest.fn(() => { @@ -150,7 +156,7 @@ describe('NetworkLogger Module', () => { }); it('should omit request body if its content type is not allowed', () => { - const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarn = jest.spyOn(Logger, 'warn').mockImplementation(); jest.mocked(isContentTypeNotAllowed).mockReturnValueOnce(true); const networkData = { @@ -175,7 +181,7 @@ describe('NetworkLogger Module', () => { }); it('should omit response body if its content type is not allowed', () => { - const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarn = jest.spyOn(Logger, 'warn').mockImplementation(); jest.mocked(isContentTypeNotAllowed).mockReturnValueOnce(true); const networkData = { @@ -200,7 +206,7 @@ describe('NetworkLogger Module', () => { }); it('should omit request body if its size exceeds the maximum allowed size', () => { - const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarn = jest.spyOn(Logger, 'warn').mockImplementation(); const networkData = { ...network, @@ -239,7 +245,7 @@ describe('NetworkLogger Module', () => { }); it('should omit response body if its size exceeds the maximum allowed size', () => { - const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarn = jest.spyOn(Logger, 'warn').mockImplementation(); const networkData = { ...network, diff --git a/test/utils/InstabugUtils.spec.ts b/test/utils/InstabugUtils.spec.ts index becfccc0e9..fd389d7f0b 100644 --- a/test/utils/InstabugUtils.spec.ts +++ b/test/utils/InstabugUtils.spec.ts @@ -258,6 +258,11 @@ describe('reportNetworkLog', () => { errorDomain: 'errorDomain', serverErrorMessage: 'serverErrorMessage', requestContentType: 'requestContentType', + isW3cHeaderFound: null, + partialId: null, + networkStartTimeInSeconds: null, + w3cGeneratedHeader: null, + w3cCaughtHeader: null, }; it('reportNetworkLog should send network logs to native with the correct parameters on Android', () => { @@ -296,6 +301,13 @@ describe('reportNetworkLog', () => { network.responseCode, network.contentType, network.errorDomain, + { + isW3cHeaderFound: null, + partialId: null, + networkStartTimeInSeconds: null, + w3cGeneratedHeader: null, + w3cCaughtHeader: null, + }, network.gqlQueryName, network.serverErrorMessage, ); @@ -324,6 +336,13 @@ describe('reportNetworkLog', () => { network.duration, network.gqlQueryName, network.serverErrorMessage, + { + isW3cHeaderFound: null, + partialId: null, + networkStartTimeInSeconds: null, + w3cGeneratedHeader: null, + w3cCaughtHeader: null, + }, ); }); }); diff --git a/test/utils/XhrNetworkInterceptor.spec.ts b/test/utils/XhrNetworkInterceptor.spec.ts index 10a8f1abb8..dfb9e7f43b 100644 --- a/test/utils/XhrNetworkInterceptor.spec.ts +++ b/test/utils/XhrNetworkInterceptor.spec.ts @@ -4,7 +4,7 @@ import nock from 'nock'; import waitForExpect from 'wait-for-expect'; import InstabugConstants from '../../src/utils/InstabugConstants'; -import Interceptor from '../../src/utils/XhrNetworkInterceptor'; +import Interceptor, { injectHeaders } from '../../src/utils/XhrNetworkInterceptor'; const url = 'http://api.instabug.com'; const method = 'GET'; @@ -293,3 +293,203 @@ describe('Network Interceptor', () => { FakeRequest.send(); }); }); + +describe('Network Interceptor W3C Headers', () => { + beforeEach(() => { + nock.cleanAll(); + }); + + it('should attach generated header if all flags are enabled on no header found', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: true, + isW3cExternalGeneratedHeaderEnabled: true, + isW3cCaughtHeaderEnabled: true, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(false); + expect(network.partialId).not.toBe(null); + expect(network.networkStartTimeInSeconds).toEqual(Math.floor(network.startTime / 1000)); + expect(network.w3cGeneratedHeader).toHaveLength(55); + expect(network.w3cCaughtHeader).toBe(null); + }); + done(); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + it('should attach generated header if key flag & generated header flags are enabled on no header found', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: true, + isW3cExternalGeneratedHeaderEnabled: true, + isW3cCaughtHeaderEnabled: false, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(false); + expect(network.partialId).not.toBe(null); + expect(network.networkStartTimeInSeconds).toEqual(Math.floor(network.startTime / 1000)); + expect(network.w3cGeneratedHeader).toHaveLength(55); + expect(network.w3cCaughtHeader).toBe(null); + }); + done(); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + + it('should not attach headers when key flag is disabled & generated, caught header flags are enabled', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: false, + isW3cExternalGeneratedHeaderEnabled: true, + isW3cCaughtHeaderEnabled: true, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(null); + expect(network.partialId).toBe(null); + expect(network.networkStartTimeInSeconds).toBe(null); + expect(network.w3cGeneratedHeader).toBe(null); + expect(network.w3cCaughtHeader).toBe(null); + expect(network.requestHeaders).not.toHaveProperty('traceparent'); + + done(); + }); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + it('should not attach headers when all feature flags are disabled', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: false, + isW3cExternalGeneratedHeaderEnabled: false, + isW3cCaughtHeaderEnabled: false, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(null); + expect(network.partialId).toBe(null); + expect(network.networkStartTimeInSeconds).toBe(null); + expect(network.w3cGeneratedHeader).toBe(null); + expect(network.w3cCaughtHeader).toBe(null); + expect(network.requestHeaders).not.toHaveProperty('traceparent'); + + done(); + }); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + it('should not attach headers when key & caught header flags are disabled and generated header flag is enabled', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: false, + isW3cExternalGeneratedHeaderEnabled: true, + isW3cCaughtHeaderEnabled: false, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(null); + expect(network.partialId).toBe(null); + expect(network.networkStartTimeInSeconds).toBe(null); + expect(network.w3cGeneratedHeader).toBe(null); + expect(network.w3cCaughtHeader).toBe(null); + expect(network.requestHeaders).not.toHaveProperty('traceparent'); + done(); + }); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + it('should not attach headers when key & generated header flags are disabled and caught header flag is enabled', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: false, + isW3cExternalGeneratedHeaderEnabled: false, + isW3cCaughtHeaderEnabled: true, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(null); + expect(network.partialId).toBe(null); + expect(network.networkStartTimeInSeconds).toBe(null); + expect(network.w3cGeneratedHeader).toBe(null); + expect(network.w3cCaughtHeader).toBe(null); + expect(network.requestHeaders).not.toHaveProperty('traceparent'); + done(); + }); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + it('should not attach headers when key flag is enabled & generated, caught header flags are disabled on header found', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: true, + isW3cExternalGeneratedHeaderEnabled: false, + isW3cCaughtHeaderEnabled: false, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + network.requestHeaders.traceparent = 'caught traceparent header'; + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toEqual(true); + expect(network.partialId).toBe(null); + expect(network.networkStartTimeInSeconds).toBe(null); + expect(network.w3cGeneratedHeader).toBe(null); + expect(network.w3cCaughtHeader).toBe(null); + done(); + }); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + + it('should attach caught header if all flags are enabled ', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: true, + isW3cExternalGeneratedHeaderEnabled: true, + isW3cCaughtHeaderEnabled: true, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + network.requestHeaders.traceparent = 'caught traceparent header'; + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(true); + expect(network.partialId).toBe(null); + expect(network.networkStartTimeInSeconds).toBe(null); + expect(network.w3cGeneratedHeader).toBe(null); + expect(network.w3cCaughtHeader).toBe('caught traceparent header'); + expect(network.requestHeaders).toHaveProperty('traceparent'); + done(); + }); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); + it('should attach caught header if key & caught header flags are enabled and generated header flag is disabled', (done) => { + const featureFlags = { + isW3cExternalTraceIDEnabled: true, + isW3cExternalGeneratedHeaderEnabled: false, + isW3cCaughtHeaderEnabled: true, + }; + Interceptor.enableInterception(); + Interceptor.setOnDoneCallback((network) => { + network.requestHeaders.traceparent = 'caught traceparent header'; + injectHeaders(network, featureFlags); + expect(network.isW3cHeaderFound).toBe(true); + expect(network.partialId).toBe(null); + expect(network.networkStartTimeInSeconds).toBe(null); + expect(network.w3cGeneratedHeader).toBe(null); + expect(network.w3cCaughtHeader).toBe('caught traceparent header'); + expect(network.requestHeaders).toHaveProperty('traceparent'); + done(); + }); + FakeRequest.mockResponse(request); + FakeRequest.open(method, url); + FakeRequest.send(); + }); +}); From e9125b84bc974282fee6dbae2540a4cd5bcc64fd Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Thu, 20 Mar 2025 23:37:13 +0200 Subject: [PATCH 2/8] fix: prevent not sending the unSent xhrRequest --- src/utils/XhrNetworkInterceptor.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/utils/XhrNetworkInterceptor.ts b/src/utils/XhrNetworkInterceptor.ts index 4443940362..615bf4f522 100644 --- a/src/utils/XhrNetworkInterceptor.ts +++ b/src/utils/XhrNetworkInterceptor.ts @@ -85,7 +85,7 @@ const getTraceparentHeader = async (networkData: NetworkData) => { }); }; -export const injectHeaders = async ( +export const injectHeaders = ( networkData: NetworkData, featureFlags: { isW3cExternalTraceIDEnabled: boolean; @@ -113,10 +113,7 @@ export const injectHeaders = async ( return injectionMethodology; }; -const identifyCaughtHeader = async ( - networkData: NetworkData, - isW3cCaughtHeaderEnabled: boolean, -) => { +const identifyCaughtHeader = (networkData: NetworkData, isW3cCaughtHeaderEnabled: boolean) => { if (isW3cCaughtHeaderEnabled) { networkData.w3cCaughtHeader = networkData.requestHeaders.traceparent; return networkData.requestHeaders.traceparent; @@ -314,7 +311,11 @@ export default { if (traceparent) { this.setRequestHeader('Traceparent', traceparent); } - originalXHRSend.apply(this, [data]); + if (this.readyState === this.UNSENT) { + return; // Prevent sending the request if not opened + } + + originalXHRSend.apply(this, [data]); // This line is essential }; isInterceptorEnabled = true; }, From d8059650913ccd28f5d0cd58cec4e262df82f303 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Thu, 20 Mar 2025 23:43:06 +0200 Subject: [PATCH 3/8] fix: prevent not sending the unSent xhrRequest --- src/utils/XhrNetworkInterceptor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/XhrNetworkInterceptor.ts b/src/utils/XhrNetworkInterceptor.ts index 615bf4f522..4eee6dd90c 100644 --- a/src/utils/XhrNetworkInterceptor.ts +++ b/src/utils/XhrNetworkInterceptor.ts @@ -315,7 +315,7 @@ export default { return; // Prevent sending the request if not opened } - originalXHRSend.apply(this, [data]); // This line is essential + originalXHRSend.apply(this, [data]); }; isInterceptorEnabled = true; }, From a467f2e3bc46b35905f87d9d78e09478256da18c Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Thu, 15 May 2025 14:11:11 +0300 Subject: [PATCH 4/8] feat: add netinfo check in network screen --- CHANGELOG.md | 7 +++++++ examples/default/package.json | 1 + examples/default/src/screens/apm/NetworkScreen.tsx | 5 +++++ examples/default/yarn.lock | 5 +++++ 4 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1f28a933f..20316e54c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [Unreleased](https://github.com/Instabug/Instabug-React-Native/compare/v14.1.0...dev) + +## Fixed + +- Intercept incomplete network Requests ([#1365](https://github.com/Instabug/Instabug-React-Native/pull/1365)) + + ## [14.1.0](https://github.com/Instabug/Instabug-React-Native/compare/v14.0.0...v14.1.0) (January 2, 2025) ### Added diff --git a/examples/default/package.json b/examples/default/package.json index 26959bb0b9..c890d86b48 100644 --- a/examples/default/package.json +++ b/examples/default/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@react-native-clipboard/clipboard": "^1.14.3", + "@react-native-community/netinfo": "^11.4.1", "@react-native-community/slider": "^4.5.5", "@react-navigation/bottom-tabs": "^6.5.7", "@react-navigation/native": "^6.1.6", diff --git a/examples/default/src/screens/apm/NetworkScreen.tsx b/examples/default/src/screens/apm/NetworkScreen.tsx index f9f057f612..7df8af7f49 100644 --- a/examples/default/src/screens/apm/NetworkScreen.tsx +++ b/examples/default/src/screens/apm/NetworkScreen.tsx @@ -11,12 +11,16 @@ import axios from 'axios'; import type { HomeStackParamList } from '../../navigation/HomeStack'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { ListTile } from '../../components/ListTile'; +import { useNetInfo } from '@react-native-community/netinfo'; export const NetworkScreen: React.FC< NativeStackScreenProps > = ({ navigation }) => { const [endpointUrl, setEndpointUrl] = useState(''); const { width, height } = useWindowDimensions(); + + const { isConnected } = useNetInfo(); + const defaultRequestUrl = 'https://jsonplaceholder.typicode.com/posts/1'; const imageUrls = [ 'https://fastly.picsum.photos/id/57/200/300.jpg?hmac=l908G1qVr4r7dP947-tak2mY8Vvic_vEYzCXUCKKskY', @@ -129,6 +133,7 @@ export const NetworkScreen: React.FC< /> refetch} title="Reload GraphQL" /> + {isConnected ? 'Network is Connected' : 'Network is not connected'} {isLoading && Loading...} {isSuccess && GraphQL Data: {data.country.emoji}} {isError && Error!} diff --git a/examples/default/yarn.lock b/examples/default/yarn.lock index 3e1f047c3f..39819f8fd1 100644 --- a/examples/default/yarn.lock +++ b/examples/default/yarn.lock @@ -2041,6 +2041,11 @@ prompts "^2.4.2" semver "^7.5.2" +"@react-native-community/netinfo@^11.4.1": + version "11.4.1" + resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-11.4.1.tgz#a3c247aceab35f75dd0aa4bfa85d2be5a4508688" + integrity sha512-B0BYAkghz3Q2V09BF88RA601XursIEA111tnc2JOaN7axJWmNefmfjZqw/KdSxKZp7CZUuPpjBmz/WCR9uaHYg== + "@react-native-community/slider@^4.5.5": version "4.5.5" resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-4.5.5.tgz#d70fc5870477760033769bbd6625d57e7d7678b2" From db4bcc2e9214fc2e305d9cdac069f21e5f30ff74 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Tue, 20 May 2025 11:56:25 +0300 Subject: [PATCH 5/8] feat: add change log --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b22edda545..837ada0bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ # Changelog -## [Unreleased] +## [Unreleased](https://github.com/Instabug/Instabug-React-Native/compare/v14.3.0...dev) ### Added - Add support for BugReporting user consents. ([#1383](https://github.com/Instabug/Instabug-React-Native/pull/1383)) +### Fixed + +- Not sending the inComplete xhrRequest. ([#1365](https://github.com/Instabug/Instabug-React-Native/pull/1365)) + ## [14.3.0](https://github.com/Instabug/Instabug-React-Native/compare/v14.1.0...14.3.0) ### Added From 9696d6573914ca49b435904d6f9ffeafaeee31f2 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Tue, 20 May 2025 14:09:15 +0300 Subject: [PATCH 6/8] feat: add change log --- examples/default/ios/Podfile.lock | 6 ++++++ examples/default/yarn.lock | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index 2062dad75a..849180b496 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -1296,6 +1296,8 @@ PODS: - React-Core - react-native-maps (1.10.3): - React-Core + - react-native-netinfo (11.4.1): + - React-Core - react-native-safe-area-context (4.12.0): - React-Core - react-native-slider (4.5.5): @@ -1805,6 +1807,7 @@ DEPENDENCIES: - react-native-config (from `../node_modules/react-native-config`) - react-native-google-maps (from `../node_modules/react-native-maps`) - react-native-maps (from `../node_modules/react-native-maps`) + - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "react-native-slider (from `../node_modules/@react-native-community/slider`)" - react-native-webview (from `../node_modules/react-native-webview`) @@ -1933,6 +1936,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-maps" react-native-maps: :path: "../node_modules/react-native-maps" + react-native-netinfo: + :path: "../node_modules/@react-native-community/netinfo" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" react-native-slider: @@ -2053,6 +2058,7 @@ SPEC CHECKSUMS: react-native-config: 8f7283449bbb048902f4e764affbbf24504454af react-native-google-maps: 1bcc1f9f13f798fcf230db7fe476f3566d0bc0a3 react-native-maps: 72a8a903f8a1b53e2c777ba79102078ab502e0bf + react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 react-native-safe-area-context: 142fade490cbebbe428640b8cbdb09daf17e8191 react-native-slider: 4a0f3386a38fc3d2d955efc515aef7096f7d1ee4 react-native-webview: c0b91a4598bd54e9fbc70353aebf1e9bab2e5bb9 diff --git a/examples/default/yarn.lock b/examples/default/yarn.lock index c484688ce0..012fe261d7 100644 --- a/examples/default/yarn.lock +++ b/examples/default/yarn.lock @@ -2041,6 +2041,11 @@ prompts "^2.4.2" semver "^7.5.2" +"@react-native-community/netinfo@^11.4.1": + version "11.4.1" + resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-11.4.1.tgz#a3c247aceab35f75dd0aa4bfa85d2be5a4508688" + integrity sha512-B0BYAkghz3Q2V09BF88RA601XursIEA111tnc2JOaN7axJWmNefmfjZqw/KdSxKZp7CZUuPpjBmz/WCR9uaHYg== + "@react-native-community/slider@^4.5.5": version "4.5.5" resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-4.5.5.tgz#d70fc5870477760033769bbd6625d57e7d7678b2" From 2fbf30545825e68185698d050a166094f9c62713 Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Tue, 20 May 2025 14:16:20 +0300 Subject: [PATCH 7/8] ci: fix ci running --- examples/default/ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/default/ios/Podfile.lock b/examples/default/ios/Podfile.lock index 7fbcf13562..55b2b299a6 100644 --- a/examples/default/ios/Podfile.lock +++ b/examples/default/ios/Podfile.lock @@ -2060,7 +2060,7 @@ SPEC CHECKSUMS: react-native-config: 8f7283449bbb048902f4e764affbbf24504454af react-native-google-maps: 1bcc1f9f13f798fcf230db7fe476f3566d0bc0a3 react-native-maps: 72a8a903f8a1b53e2c777ba79102078ab502e0bf - react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 + react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac react-native-safe-area-context: 142fade490cbebbe428640b8cbdb09daf17e8191 react-native-slider: 4a0f3386a38fc3d2d955efc515aef7096f7d1ee4 react-native-webview: c0b91a4598bd54e9fbc70353aebf1e9bab2e5bb9 From 381d2b97fe97cd813f7353bd2233c9545ec1093a Mon Sep 17 00:00:00 2001 From: Ahmed alaa Date: Tue, 20 May 2025 14:24:03 +0300 Subject: [PATCH 8/8] ci: fix ci running --- android/native.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/native.gradle b/android/native.gradle index 3241dc5f24..7177b8c279 100644 --- a/android/native.gradle +++ b/android/native.gradle @@ -1,5 +1,5 @@ project.ext.instabug = [ - version: '14.3.0' + version: '14.3.0.6752106-SNAPSHOT' ] dependencies {