diff --git a/.circleci/config.yml b/.circleci/config.yml index b4f66035d..e209a3b62 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ commands: setup_flutter: steps: - flutter/install_sdk_and_pub: - flutter_version: 3.3.0 + flutter_version: 3.10.5 - run: name: Generate Pigeons command: sh ./scripts/pigeon.sh @@ -33,7 +33,7 @@ commands: steps: - run: name: Install Appium - command: npm install -g appium@next + command: npm install -g appium - when: condition: equal: @@ -42,7 +42,7 @@ commands: steps: - run: name: Install XCUITest Driver - command: appium driver install xcuitest + command: appium driver install xcuitest@4.35.0 - when: condition: equal: @@ -51,7 +51,7 @@ commands: steps: - run: name: Install UIAutomator2 Driver - command: appium driver install uiautomator2 + command: appium driver install uiautomator2@2.29.5 - run: name: Launch Appium # Enable --relaxed-security for `mobile: shell` command that Captain uses internally. diff --git a/CHANGELOG.md b/CHANGELOG.md index fadd7516c..bfdfad359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [11.14.0](https://github.com/Instabug/Instabug-Flutter/compare/v11.13.0...v11.14.0) (September 13, 2023) + +### Added + +- Add network logs obfuscation support using the new `NetworkLogger.obfuscateLog` API ([#380](https://github.com/Instabug/Instabug-Flutter/pull/380)). +- Add network logs omission support using the new `NetworkLogger.omitLog` API ([#382](https://github.com/Instabug/Instabug-Flutter/pull/382)). +- Add the new repro steps configuration API `Instabug.setReproStepsConfig` ([#388](https://github.com/Instabug/Instabug-Flutter/pull/388)). + +### Changed + +- Bump Instabug Android SDK to v11.14.0 ([#384](https://github.com/Instabug/Instabug-Flutter/pull/384)). [See release notes](https://github.com/Instabug/Instabug-Android/releases/tag/v11.14.0). +- Bump Instabug iOS SDK to v11.14.0 ([#383](https://github.com/Instabug/Instabug-Flutter/pull/383)). [See release notes](https://github.com/Instabug/Instabug-iOS/releases/tag/11.14.0). + +### Deprecated + +- Deprecate `Instabug.setReproStepsMode` in favor of the new `Instabug.setReproStepsConfig` ([#388](https://github.com/Instabug/Instabug-Flutter/pull/388)). + ## [11.13.0](https://github.com/Instabug/Instabug-Flutter/compare/v11.12.0...v11.13.0) (July 10, 2023) ### Changed diff --git a/Gemfile b/Gemfile index 09a8c1e13..19b2c91d4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1 @@ source "https://rubygems.org" - -gem 'danger', '~> 8.6', '>= 8.6.1' diff --git a/Gemfile.lock b/Gemfile.lock index 982ec2834..3c10ae234 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,84 +1,11 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - claide (1.1.0) - claide-plugins (0.9.2) - cork - nap - open4 (~> 1.3) - colored2 (3.1.2) - cork (0.3.0) - colored2 (~> 3.1) - danger (8.6.1) - claide (~> 1.0) - claide-plugins (>= 0.9.2) - colored2 (~> 3.1) - cork (~> 0.1) - faraday (>= 0.9.0, < 2.0) - faraday-http-cache (~> 2.0) - git (~> 1.7) - kramdown (~> 2.3) - kramdown-parser-gfm (~> 1.0) - no_proxy_fix - octokit (~> 4.7) - terminal-table (>= 1, < 4) - faraday (1.10.2) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-http-cache (2.4.1) - faraday (>= 0.8) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - git (1.12.0) - addressable (~> 2.8) - rchardet (~> 1.8) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - multipart-post (2.2.3) - nap (1.1.0) - no_proxy_fix (0.1.2) - octokit (4.25.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) - open4 (1.3.4) - public_suffix (5.0.0) - rchardet (1.8.0) - rexml (3.2.5) - ruby2_keywords (0.0.5) - sawyer (0.9.2) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.3.0) PLATFORMS ruby DEPENDENCIES - danger (~> 8.6, >= 8.6.1) BUNDLED WITH 1.17.2 diff --git a/README.md b/README.md index 16804193a..88080febd 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ A Flutter plugin for [Instabug](https://instabug.com/). ### Installation - 1. Add Instabug to your `pubspec.yaml` file. ```yaml @@ -65,18 +64,23 @@ Instabug automatically captures every crash of your app and sends relevant detai ```dart void main() { - WidgetsFlutterBinding.ensureInitialized(); - - Instabug.init( - token: 'APP_TOKEN', - invocationEvents: [InvocationEvent.floatingButton], + runZonedGuarded( + () { + WidgetsFlutterBinding.ensureInitialized(); + + Instabug.init( + token: 'APP_TOKEN', + invocationEvents: [InvocationEvent.shake], + ); + + FlutterError.onError = (FlutterErrorDetails details) { + Zone.current.handleUncaughtError(details.exception, details.stack!); + }; + + runApp(MyApp()); + }, + CrashReporting.reportCrash, ); - - FlutterError.onError = (FlutterErrorDetails details) { - Zone.current.handleUncaughtError(details.exception, details.stack!); - }; - - runZonedGuarded(() => runApp(MyApp()), CrashReporting.reportCrash); } ``` @@ -90,7 +94,6 @@ Repro Steps list all of the actions an app user took before reporting a bug or c )); ``` - ## Network Logging You can choose to attach all your network requests to the reports being sent to the dashboard. To enable the feature when using the `dart:io` package `HttpClient`, please refer to the [Instabug Dart IO Http Client](https://github.com/Instabug/instabug-dart-io-http-client) repository. diff --git a/android/build.gradle b/android/build.gradle index f74a857e4..f85e76f11 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ group 'com.instabug.flutter' -version '11.13.0' +version '11.14.0' buildscript { repositories { @@ -41,7 +41,7 @@ android { } dependencies { - api 'com.instabug.library:instabug:11.13.0' + api 'com.instabug.library:instabug:11.14.0' testImplementation 'junit:junit:4.13.2' testImplementation "org.mockito:mockito-inline:3.12.1" diff --git a/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java b/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java index a9bc50346..c5292b9c4 100644 --- a/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java +++ b/android/src/main/java/com/instabug/flutter/modules/InstabugApi.java @@ -19,7 +19,9 @@ import com.instabug.library.Instabug; import com.instabug.library.InstabugColorTheme; import com.instabug.library.InstabugCustomTextPlaceHolder; +import com.instabug.library.IssueType; import com.instabug.library.Platform; +import com.instabug.library.ReproConfigurations; import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.invocation.InstabugInvocationEvent; import com.instabug.library.model.NetworkLog; @@ -283,7 +285,9 @@ public void setSdkDebugLogsLevel(@NonNull String level) { // iOS Only } + @SuppressWarnings("deprecation") @Override + @Deprecated() public void setReproStepsMode(@NonNull String mode) { try { final State resolvedMode = ArgsRegistry.reproStates.get(mode); @@ -293,6 +297,29 @@ public void setReproStepsMode(@NonNull String mode) { } } + @Override + public void setReproStepsConfig(@Nullable String bugMode, @Nullable String crashMode) { + try { + final ReproConfigurations.Builder builder = new ReproConfigurations.Builder(); + + if (bugMode != null) { + final Integer resolvedBugMode = ArgsRegistry.reproModes.get(bugMode); + builder.setIssueMode(IssueType.Bug, resolvedBugMode); + } + + if (crashMode != null) { + final Integer resolvedCrashMode = ArgsRegistry.reproModes.get(crashMode); + builder.setIssueMode(IssueType.Crash, resolvedCrashMode); + } + + final ReproConfigurations config = builder.build(); + + Instabug.setReproConfigurations(config); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Override public void reportScreenChange(@NonNull String screenName) { try { diff --git a/android/src/main/java/com/instabug/flutter/util/ArgsRegistry.java b/android/src/main/java/com/instabug/flutter/util/ArgsRegistry.java index b63341bf3..6beba4d52 100644 --- a/android/src/main/java/com/instabug/flutter/util/ArgsRegistry.java +++ b/android/src/main/java/com/instabug/flutter/util/ArgsRegistry.java @@ -9,6 +9,7 @@ import com.instabug.library.InstabugColorTheme; import com.instabug.library.InstabugCustomTextPlaceHolder.Key; import com.instabug.library.OnSdkDismissCallback.DismissType; +import com.instabug.library.ReproMode; import com.instabug.library.extendedbugreport.ExtendedBugReport; import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.invocation.InstabugInvocationEvent; @@ -111,12 +112,19 @@ public T get(Object key) { put("ExtendedBugReportMode.disabled", ExtendedBugReport.State.DISABLED); }}; + @Deprecated() public static final ArgsMap reproStates = new ArgsMap() {{ put("ReproStepsMode.enabledWithNoScreenshots", State.ENABLED_WITH_NO_SCREENSHOTS); put("ReproStepsMode.enabled", State.ENABLED); put("ReproStepsMode.disabled", State.DISABLED); }}; + public static final ArgsMap reproModes = new ArgsMap() {{ + put("ReproStepsMode.enabledWithNoScreenshots", ReproMode.EnableWithNoScreenshots); + put("ReproStepsMode.enabled", ReproMode.EnableWithScreenshots); + put("ReproStepsMode.disabled", ReproMode.Disable); + }}; + public static final ArgsMap locales = new ArgsMap() {{ put("IBGLocale.arabic", InstabugLocale.ARABIC); put("IBGLocale.azerbaijani", InstabugLocale.AZERBAIJANI); @@ -214,4 +222,4 @@ public T get(Object key) { put("CustomTextPlaceHolderKey.messagesNotificationAndOthers", Key.CHATS_MULTIPLE_MESSAGE_NOTIFICATION); put("CustomTextPlaceHolderKey.insufficientContentMessage", Key.COMMENT_FIELD_INSUFFICIENT_CONTENT); }}; -} \ No newline at end of file +} diff --git a/android/src/test/java/com/instabug/flutter/ArgsRegistryTest.java b/android/src/test/java/com/instabug/flutter/ArgsRegistryTest.java index d4e59fb00..ef1b9c21c 100644 --- a/android/src/test/java/com/instabug/flutter/ArgsRegistryTest.java +++ b/android/src/test/java/com/instabug/flutter/ArgsRegistryTest.java @@ -10,6 +10,7 @@ import com.instabug.library.InstabugColorTheme; import com.instabug.library.InstabugCustomTextPlaceHolder.Key; import com.instabug.library.OnSdkDismissCallback.DismissType; +import com.instabug.library.ReproMode; import com.instabug.library.extendedbugreport.ExtendedBugReport; import com.instabug.library.internal.module.InstabugLocale; import com.instabug.library.invocation.InstabugInvocationEvent; @@ -183,6 +184,7 @@ public void testExtendedBugReportStates() { } + @SuppressWarnings("deprecation") @Test public void testReproStates() { State[] values = { @@ -196,6 +198,19 @@ public void testReproStates() { } } + @Test + public void testReproModes() { + Integer[] values = { + ReproMode.Disable, + ReproMode.EnableWithScreenshots, + ReproMode.EnableWithNoScreenshots, + }; + + for (Integer value : values) { + assertTrue(ArgsRegistry.reproModes.containsValue(value)); + } + } + @Test public void testLocales() { diff --git a/android/src/test/java/com/instabug/flutter/InstabugApiTest.java b/android/src/test/java/com/instabug/flutter/InstabugApiTest.java index 35d871406..14ce73325 100644 --- a/android/src/test/java/com/instabug/flutter/InstabugApiTest.java +++ b/android/src/test/java/com/instabug/flutter/InstabugApiTest.java @@ -7,6 +7,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -30,8 +31,11 @@ import com.instabug.library.Instabug; import com.instabug.library.InstabugColorTheme; import com.instabug.library.InstabugCustomTextPlaceHolder; +import com.instabug.library.IssueType; import com.instabug.library.LogLevel; import com.instabug.library.Platform; +import com.instabug.library.ReproConfigurations; +import com.instabug.library.ReproMode; import com.instabug.library.invocation.InstabugInvocationEvent; import com.instabug.library.model.NetworkLog; import com.instabug.library.ui.onboarding.WelcomeMessage; @@ -383,6 +387,7 @@ public void testSetDebugEnabled() { mInstabug.verify(() -> Instabug.setDebugEnabled(isEnabled)); } + @SuppressWarnings("deprecation") @Test public void testSetReproStepsMode() { String mode = "ReproStepsMode.enabled"; @@ -392,6 +397,28 @@ public void testSetReproStepsMode() { mInstabug.verify(() -> Instabug.setReproStepsState(State.ENABLED)); } + @Test + public void testSetReproStepsConfig() { + String bug = "ReproStepsMode.enabled"; + String crash = "ReproStepsMode.disabled"; + + ReproConfigurations config = mock(ReproConfigurations.class); + MockedConstruction mReproConfigurationsBuilder = mockConstruction(ReproConfigurations.Builder.class, (mock, context) -> { + when(mock.setIssueMode(anyInt(), anyInt())).thenReturn(mock); + when(mock.build()).thenReturn(config); + }); + + api.setReproStepsConfig(bug, crash); + + ReproConfigurations.Builder builder = mReproConfigurationsBuilder.constructed().get(0); + + verify(builder).setIssueMode(IssueType.Bug, ReproMode.EnableWithScreenshots); + verify(builder).setIssueMode(IssueType.Crash, ReproMode.Disable); + verify(builder).build(); + + mInstabug.verify(() -> Instabug.setReproConfigurations(config)); + } + @Test public void testReportScreenChange() { String screenName = "HomeScreen"; diff --git a/e2e/Utils/CaptainTest.cs b/e2e/Utils/CaptainTest.cs index 5c0abfb13..d73f37e3c 100644 --- a/e2e/Utils/CaptainTest.cs +++ b/e2e/Utils/CaptainTest.cs @@ -8,9 +8,12 @@ public class CaptainTest : IDisposable private static readonly CaptainConfig _config = new() { AndroidApp = Path.GetFullPath("../../../../example/build/app/outputs/flutter-apk/app-debug.apk"), + AndroidAppId = "com.instabug.flutter.example", AndroidVersion = "11", IosApp = Path.GetFullPath("../../../../example/build/ios/iphonesimulator/Runner.app"), - IosVersion = "15.5" + IosAppId = "com.instabug.InstabugSample", + IosVersion = "15.5", + IosDevice = "iPhone 13 Pro Max" }; protected static readonly Captain captain = new(_config); diff --git a/example/ios/InstabugTests/ArgsRegistryTests.m b/example/ios/InstabugTests/ArgsRegistryTests.m index 203dab567..f3790f407 100644 --- a/example/ios/InstabugTests/ArgsRegistryTests.m +++ b/example/ios/InstabugTests/ArgsRegistryTests.m @@ -157,7 +157,7 @@ - (void)testExtendedBugReportStates { } } -- (void)testReproStates { +- (void)testReproModes { NSArray *values = @[ @(IBGUserStepsModeEnable), @(IBGUserStepsModeDisable), @@ -165,7 +165,7 @@ - (void)testReproStates { ]; for (NSNumber *value in values) { - XCTAssertTrue([[ArgsRegistry.reproStates allValues] containsObject:value]); + XCTAssertTrue([[ArgsRegistry.reproModes allValues] containsObject:value]); } } diff --git a/example/ios/InstabugTests/InstabugApiTests.m b/example/ios/InstabugTests/InstabugApiTests.m index 22e958b79..0fd431260 100644 --- a/example/ios/InstabugTests/InstabugApiTests.m +++ b/example/ios/InstabugTests/InstabugApiTests.m @@ -300,6 +300,17 @@ - (void)testSetReproStepsMode { OCMVerify([self.mInstabug setReproStepsMode:IBGUserStepsModeEnable]); } +- (void)testSetReproStepsConfig { + NSString *bugMode = @"ReproStepsMode.enabled"; + NSString *crashMode = @"ReproStepsMode.disabled"; + FlutterError *error; + + [self.api setReproStepsConfigBugMode:bugMode crashMode:crashMode error:&error]; + + OCMVerify([self.mInstabug setReproStepsFor:IBGIssueTypeBug withMode:IBGUserStepsModeEnable]); + OCMVerify([self.mInstabug setReproStepsFor:IBGIssueTypeCrash withMode:IBGUserStepsModeDisable]); +} + - (void)testReportScreenChange { NSString *screenName = @"HomeScreen"; FlutterError *error; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index dabcb7368..a414ee9d5 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Flutter (1.0.0) - - Instabug (11.13.3) - - instabug_flutter (11.13.0): + - Instabug (11.14.0) + - instabug_flutter (11.14.0): - Flutter - - Instabug (= 11.13.3) + - Instabug (= 11.14.0) - OCMock (3.6) DEPENDENCIES: @@ -24,8 +24,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - Instabug: f7c3880823873cff9ee70307d373e8266c9ea0f8 - instabug_flutter: e935f80f03127586ebb4fc61c6b891d87a978ee3 + Instabug: b4976b1a96ed7462b772ff63f7c4e0166389c86a + instabug_flutter: 91b418918d7e173556b013adf8ce45940bbbedec OCMock: 5ea90566be239f179ba766fd9fbae5885040b992 PODFILE CHECKSUM: 637e800c0a0982493b68adb612d2dd60c15c8e5c diff --git a/example/lib/main.dart b/example/lib/main.dart index b42390e9d..31705db25 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,21 +4,23 @@ import 'package:flutter/material.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; void main() { - WidgetsFlutterBinding.ensureInitialized(); + runZonedGuarded( + () { + WidgetsFlutterBinding.ensureInitialized(); - Instabug.init( - token: 'ed6f659591566da19b67857e1b9d40ab', - invocationEvents: [InvocationEvent.floatingButton], - debugLogsLevel: LogLevel.verbose, - ); - - Instabug.setWelcomeMessageMode(WelcomeMessageMode.disabled); + Instabug.init( + token: 'ed6f659591566da19b67857e1b9d40ab', + invocationEvents: [InvocationEvent.floatingButton], + ); - FlutterError.onError = (FlutterErrorDetails details) { - Zone.current.handleUncaughtError(details.exception, details.stack!); - }; + FlutterError.onError = (FlutterErrorDetails details) { + Zone.current.handleUncaughtError(details.exception, details.stack!); + }; - runZonedGuarded(() => runApp(MyApp()), CrashReporting.reportCrash); + runApp(MyApp()); + }, + CrashReporting.reportCrash, + ); } class MyApp extends StatelessWidget { @@ -160,11 +162,6 @@ class _MyHomePageState extends State { ReportType.feedback, [InvocationOption.emailFieldOptional]); } - void askQuestion() { - BugReporting.show( - ReportType.question, [InvocationOption.emailFieldOptional]); - } - void showNpsSurvey() { Surveys.showSurvey('pcV_mE2ttqHxT1iqvBxL0w'); } @@ -331,10 +328,6 @@ class _MyHomePageState extends State { onPressed: sendFeedback, text: 'Send Feedback', ), - InstabugButton( - onPressed: askQuestion, - text: 'Ask a Question', - ), InstabugButton( onPressed: showNpsSurvey, text: 'Show NPS Survey', diff --git a/example/pubspec.lock b/example/pubspec.lock index 10a0273a0..d1455e4ed 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" espresso: dependency: "direct dev" description: @@ -91,31 +91,23 @@ packages: path: ".." relative: true source: path - version: "11.13.0" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "11.14.0" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -157,10 +149,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -205,10 +197,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" vector_math: dependency: transitive description: @@ -221,10 +213,18 @@ packages: dependency: transitive description: name: vm_service - sha256: f6deed8ed625c52864792459709183da231ebf66ff0cf09e69b573227c377efe + sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f + url: "https://pub.dev" + source: hosted + version: "11.7.1" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "11.3.0" + version: "0.1.4-beta" webdriver: dependency: transitive description: @@ -234,5 +234,5 @@ packages: source: hosted version: "3.0.2" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=2.10.0" diff --git a/ios/Classes/Modules/BugReportingApi.m b/ios/Classes/Modules/BugReportingApi.m index 80c4e79e9..3c914b789 100644 --- a/ios/Classes/Modules/BugReportingApi.m +++ b/ios/Classes/Modules/BugReportingApi.m @@ -112,7 +112,7 @@ - (void)setEnabledAttachmentTypesScreenshot:(NSNumber *)screenshot extraScreensh - (void)bindOnInvokeCallbackWithError:(FlutterError *_Nullable *_Nonnull)error { IBGBugReporting.willInvokeHandler = ^{ - [self->_flutterApi onSdkInvokeWithCompletion:^(NSError *_Nullable _){ + [self->_flutterApi onSdkInvokeWithCompletion:^(FlutterError *_Nullable _){ }]; }; } @@ -141,7 +141,7 @@ - (void)bindOnDismissCallbackWithError:(FlutterError *_Nullable *_Nonnull)error [self->_flutterApi onSdkDismissDismissType:dismissTypeString reportType:reportTypeString - completion:^(NSError *_Nullable _){ + completion:^(FlutterError *_Nullable _){ }]; }; } diff --git a/ios/Classes/Modules/InstabugApi.m b/ios/Classes/Modules/InstabugApi.m index 8762682d3..97a7ab020 100644 --- a/ios/Classes/Modules/InstabugApi.m +++ b/ios/Classes/Modules/InstabugApi.m @@ -2,6 +2,7 @@ #import #import #import "Instabug.h" +#import "IBGNetworkLogger+CP.h" #import "InstabugApi.h" #import "ArgsRegistry.h" @@ -28,6 +29,10 @@ - (void)initToken:(NSString *)token invocationEvents:(NSArray *)invo [inv setArgument:&(platformID) atIndex:2]; [inv invoke]; } + + // Disable automatic capturing of native iOS network logs to avoid duplicate + // logs of the same request when using a native network client like cupertino_http + [IBGNetworkLogger disableAutomaticCapturingOfNetworkLogs]; IBGInvocationEvent resolvedEvents = 0; @@ -154,10 +159,22 @@ - (void)setSdkDebugLogsLevelLevel:(NSString *)level error:(FlutterError *_Nullab } - (void)setReproStepsModeMode:(NSString *)mode error:(FlutterError *_Nullable *_Nonnull)error { - IBGUserStepsMode resolvedMode = (ArgsRegistry.reproStates[mode]).integerValue; + IBGUserStepsMode resolvedMode = (ArgsRegistry.reproModes[mode]).integerValue; [Instabug setReproStepsMode:resolvedMode]; } +- (void)setReproStepsConfigBugMode:(nullable NSString *)bugMode crashMode:(nullable NSString *)crashMode error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error { + if (bugMode != nil) { + IBGUserStepsMode resolvedBugMode = ArgsRegistry.reproModes[bugMode].integerValue; + [Instabug setReproStepsFor:IBGIssueTypeBug withMode:resolvedBugMode]; + } + + if (crashMode != nil) { + IBGUserStepsMode resolvedCrashMode = ArgsRegistry.reproModes[crashMode].integerValue; + [Instabug setReproStepsFor:IBGIssueTypeCrash withMode:resolvedCrashMode]; + } +} + - (UIImage *)getImageForAsset:(NSString *)assetName { NSString *key = [FlutterDartProject lookupKeyForAsset:assetName]; NSString *path = [[NSBundle mainBundle] pathForResource:key ofType:nil]; diff --git a/ios/Classes/Modules/RepliesApi.m b/ios/Classes/Modules/RepliesApi.m index 799fe6f34..636040181 100644 --- a/ios/Classes/Modules/RepliesApi.m +++ b/ios/Classes/Modules/RepliesApi.m @@ -43,7 +43,7 @@ - (void)hasChatsWithCompletion:(nonnull void (^)(NSNumber * _Nullable, FlutterEr - (void)bindOnNewReplyCallbackWithError:(FlutterError *_Nullable *_Nonnull)error { IBGReplies.didReceiveReplyHandler = ^{ - [self->_flutterApi onNewReplyWithCompletion:^(NSError *_Nullable _){ + [self->_flutterApi onNewReplyWithCompletion:^(FlutterError *_Nullable _){ }]; }; } diff --git a/ios/Classes/Modules/SurveysApi.m b/ios/Classes/Modules/SurveysApi.m index 5cb0b2076..fb6ee9fa4 100644 --- a/ios/Classes/Modules/SurveysApi.m +++ b/ios/Classes/Modules/SurveysApi.m @@ -65,14 +65,14 @@ - (void)getAvailableSurveysWithCompletion:(void (^)(NSArray *_Nullab - (void)bindOnShowSurveyCallbackWithError:(FlutterError *_Nullable *_Nonnull)error { IBGSurveys.willShowSurveyHandler = ^{ - [self->_flutterApi onShowSurveyWithCompletion:^(NSError *_Nullable _){ + [self->_flutterApi onShowSurveyWithCompletion:^(FlutterError *_Nullable _){ }]; }; } - (void)bindOnDismissSurveyCallbackWithError:(FlutterError *_Nullable *_Nonnull)error { IBGSurveys.didDismissSurveyHandler = ^{ - [self->_flutterApi onDismissSurveyWithCompletion:^(NSError *_Nullable _){ + [self->_flutterApi onDismissSurveyWithCompletion:^(FlutterError *_Nullable _){ }]; }; } diff --git a/ios/Classes/Util/ArgsRegistry.h b/ios/Classes/Util/ArgsRegistry.h index 896bc07bd..5d9fb6263 100644 --- a/ios/Classes/Util/ArgsRegistry.h +++ b/ios/Classes/Util/ArgsRegistry.h @@ -17,7 +17,7 @@ typedef NSDictionary ArgsDictionary; + (ArgsDictionary *)dismissTypes; + (ArgsDictionary *)actionTypes; + (ArgsDictionary *)extendedBugReportStates; -+ (ArgsDictionary *)reproStates; ++ (ArgsDictionary *)reproModes; + (ArgsDictionary *)locales; + (NSDictionary *)placeholders; diff --git a/ios/Classes/Util/ArgsRegistry.m b/ios/Classes/Util/ArgsRegistry.m index 14bae4ac2..dd13f46fc 100644 --- a/ios/Classes/Util/ArgsRegistry.m +++ b/ios/Classes/Util/ArgsRegistry.m @@ -112,7 +112,7 @@ + (ArgsDictionary *)extendedBugReportStates { }; } -+ (ArgsDictionary *)reproStates { ++ (ArgsDictionary *)reproModes { return @{ @"ReproStepsMode.enabled" : @(IBGUserStepsModeEnable), @"ReproStepsMode.disabled" : @(IBGUserStepsModeDisable), diff --git a/ios/Classes/Util/IBGNetworkLogger+CP.h b/ios/Classes/Util/IBGNetworkLogger+CP.h new file mode 100644 index 000000000..ae5d32d66 --- /dev/null +++ b/ios/Classes/Util/IBGNetworkLogger+CP.h @@ -0,0 +1,11 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface IBGNetworkLogger (CP) + ++ (void)disableAutomaticCapturingOfNetworkLogs; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/instabug_flutter.podspec b/ios/instabug_flutter.podspec index a8265b75a..823cd89d5 100644 --- a/ios/instabug_flutter.podspec +++ b/ios/instabug_flutter.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'instabug_flutter' - s.version = '11.13.0' + s.version = '11.14.0' s.summary = 'Flutter plugin for integrating the Instabug SDK.' s.author = 'Instabug' s.homepage = 'https://www.instabug.com/platforms/flutter' @@ -17,6 +17,6 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { 'OTHER_LDFLAGS' => '-framework "Flutter" -framework "Instabug"'} s.dependency 'Flutter' - s.dependency 'Instabug', '11.13.3' + s.dependency 'Instabug', '11.14.0' end diff --git a/lib/src/modules/instabug.dart b/lib/src/modules/instabug.dart index 82f0a44de..ac62c509d 100644 --- a/lib/src/modules/instabug.dart +++ b/lib/src/modules/instabug.dart @@ -393,10 +393,52 @@ class Instabug { /// Sets the repro steps mode /// [mode] repro steps mode + @Deprecated('Use [setReproStepsConfig] instead.') static Future setReproStepsMode(ReproStepsMode reproStepsMode) async { return _host.setReproStepsMode(reproStepsMode.toString()); } + /// Sets the repro steps mode for bugs and crashes. + /// + /// [bug] repro steps mode for bug reports. + /// [crash] repro steps mode for crash reports. + /// [all] repro steps mode for both bug and crash reports, when present it + /// overrides [bug] and [crash]. + /// + /// Example: + /// ```dart + /// Instabug.setReproStepsConfig( + /// bug: ReproStepsMode.enabled, + /// crash: ReproStepsMode.disabled, + /// ); + /// ``` + static Future setReproStepsConfig({ + ReproStepsMode? bug, + ReproStepsMode? crash, + ReproStepsMode? all, + }) async { + var bugMode = bug; + var crashMode = crash; + + if (all != null) { + bugMode = all; + crashMode = all; + } + + // There's an issue with crashes repro steps with screenshots in the iOS SDK + // at the moment, so we'll map enabled with screenshots to enabled with no + // screenshots to avoid storing the images on disk if it's not needed until + // this issue is fixed in a future version. + if (IBGBuildInfo.I.isIOS && crashMode == ReproStepsMode.enabled) { + crashMode = ReproStepsMode.enabledWithNoScreenshots; + } + + return _host.setReproStepsConfig( + bugMode.toString(), + crashMode.toString(), + ); + } + /// Sets a custom branding image logo with [light] and [dark] images for different color modes. /// /// If no [context] is passed, [asset variants](https://docs.flutter.dev/development/ui/assets-and-images#asset-variants) won't work as expected; diff --git a/lib/src/modules/network_logger.dart b/lib/src/modules/network_logger.dart index 0f054be6c..83a88538c 100644 --- a/lib/src/modules/network_logger.dart +++ b/lib/src/modules/network_logger.dart @@ -2,13 +2,15 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:instabug_flutter/src/generated/instabug.api.g.dart'; import 'package:instabug_flutter/src/models/network_data.dart'; import 'package:instabug_flutter/src/modules/apm.dart'; -import 'package:meta/meta.dart'; +import 'package:instabug_flutter/src/utils/network_manager.dart'; class NetworkLogger { static var _host = InstabugHostApi(); + static var _manager = NetworkManager(); /// @nodoc @visibleForTesting @@ -17,8 +19,60 @@ class NetworkLogger { _host = host; } + /// @nodoc + @visibleForTesting + // ignore: use_setters_to_change_properties + static void $setManager(NetworkManager manager) { + _manager = manager; + } + + /// Registers a callback to selectively obfuscate network log data. + /// + /// The [callback] function takes a [NetworkData] object as its argument and + /// should return a modified [NetworkData] object with sensitive information + /// obfuscated. + /// + /// Example: + /// + /// ```dart + /// NetworkLogger.obfuscateLog((data) { + /// // Modify 'data' as needed and return it. + /// }); + /// ``` + static void obfuscateLog(ObfuscateLogCallback callback) { + _manager.setObfuscateLogCallback(callback); + } + + /// Registers a callback to selectively omit network log data. + /// + /// Use this method to set a callback function that determines whether + /// specific network log data should be excluded before recording it. + /// + /// The [callback] function takes a [NetworkData] object as its argument and + /// should return a boolean value indicating whether the data should be omitted + /// (`true`) or included (`false`). + /// + /// Example: + /// + /// ```dart + /// NetworkLogger.omitLog((data) { + /// // Implement logic to decide whether to omit the data. + /// // For example, ignore requests to a specific URL: + /// return data.url.startsWith('https://example.com'); + /// }); + /// ``` + static void omitLog(OmitLogCallback callback) { + _manager.setOmitLogCallback(callback); + } + Future networkLog(NetworkData data) async { - await _host.networkLog(data.toJson()); - await APM.networkLogAndroid(data); + final omit = await _manager.omitLog(data); + + if (omit) return; + + final obfuscated = await _manager.obfuscateLog(data); + + await _host.networkLog(obfuscated.toJson()); + await APM.networkLogAndroid(obfuscated); } } diff --git a/lib/src/utils/network_manager.dart b/lib/src/utils/network_manager.dart new file mode 100644 index 000000000..7aaf8f563 --- /dev/null +++ b/lib/src/utils/network_manager.dart @@ -0,0 +1,39 @@ +import 'dart:async'; + +import 'package:instabug_flutter/instabug_flutter.dart'; + +typedef ObfuscateLogCallback = FutureOr Function(NetworkData data); +typedef OmitLogCallback = FutureOr Function(NetworkData data); + +/// Mockable [NetworkManager] responsible for processing network logs +/// before they are sent to the native SDKs. +class NetworkManager { + ObfuscateLogCallback? _obfuscateLogCallback; + OmitLogCallback? _omitLogCallback; + + // ignore: use_setters_to_change_properties + void setObfuscateLogCallback(ObfuscateLogCallback callback) { + _obfuscateLogCallback = callback; + } + + // ignore: use_setters_to_change_properties + void setOmitLogCallback(OmitLogCallback callback) { + _omitLogCallback = callback; + } + + FutureOr obfuscateLog(NetworkData data) { + if (_obfuscateLogCallback == null) { + return data; + } + + return _obfuscateLogCallback!(data); + } + + FutureOr omitLog(NetworkData data) { + if (_omitLogCallback == null) { + return false; + } + + return _omitLogCallback!(data); + } +} diff --git a/pigeons/instabug.api.dart b/pigeons/instabug.api.dart index bba471cd2..fe56c0ccf 100644 --- a/pigeons/instabug.api.dart +++ b/pigeons/instabug.api.dart @@ -42,7 +42,9 @@ abstract class InstabugHostApi { void setDebugEnabled(bool enabled); void setSdkDebugLogsLevel(String level); + @Deprecated('Use [setReproStepsConfig] instead') void setReproStepsMode(String mode); + void setReproStepsConfig(String? bugMode, String? crashMode); void reportScreenChange(String screenName); void setCustomBrandingImage(String light, String dark); diff --git a/pubspec.yaml b/pubspec.yaml index 5e5dfef36..ab57d2af4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,8 @@ name: instabug_flutter -version: 11.13.0 +version: 11.14.0 description: >- - Instabug empowers mobile teams to monitor, prioritize, and debug - performance and stability issues throughout the app development lifecycle. + Instabug empowers mobile teams to monitor, prioritize, and debug + performance and stability issues throughout the app development lifecycle. homepage: https://www.instabug.com/platforms/flutter repository: https://github.com/Instabug/Instabug-Flutter documentation: https://docs.instabug.com/docs/flutter-overview @@ -18,9 +18,11 @@ dev_dependencies: flutter_test: sdk: flutter lint: ^1.0.0 - mockito: 5.2.0 + # mockito v5.2.0 is needed for running Flutter 2 tests on CI + mockito: '>=5.2.0 <=5.4.2' pana: ^0.21.0 - pigeon: ^3.0.0 + # pigeon v3.0.0 is needed for running Flutter 2 tests on CI + pigeon: '>=3.0.0 <=10.1.5' flutter: plugin: diff --git a/test/instabug_test.dart b/test/instabug_test.dart index bb6d4114e..d54863e72 100644 --- a/test/instabug_test.dart +++ b/test/instabug_test.dart @@ -300,6 +300,7 @@ void main() { test('[setReproStepsMode] should call host method', () async { const mode = ReproStepsMode.enabled; + // ignore: deprecated_member_use_from_same_package await Instabug.setReproStepsMode(mode); verify( @@ -307,6 +308,34 @@ void main() { ).called(1); }); + test('[setReproStepsConfig] should call host method', () async { + const bug = ReproStepsMode.enabled; + const crash = ReproStepsMode.enabledWithNoScreenshots; + + when(mBuildInfo.isIOS).thenReturn(false); + + await Instabug.setReproStepsConfig( + bug: bug, + crash: crash, + ); + + verify( + mHost.setReproStepsConfig(bug.toString(), crash.toString()), + ).called(1); + }); + + test( + '[setReproStepsConfig] should use [all] for [bug] and [crash] if present', + () async { + const all = ReproStepsMode.enabled; + + await Instabug.setReproStepsConfig(all: all); + + verify( + mHost.setReproStepsConfig(all.toString(), all.toString()), + ).called(1); + }); + test('[setCustomBrandingImage] should call host method', () async { const lightImage = 'images/light_logo.jpeg'; const darkImage = 'images/dark_logo.jpeg'; diff --git a/test/network_logger_test.dart b/test/network_logger_test.dart index aad1be1ca..e5a04e236 100644 --- a/test/network_logger_test.dart +++ b/test/network_logger_test.dart @@ -1,9 +1,12 @@ +import 'dart:async'; + import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; import 'package:instabug_flutter/src/generated/apm.api.g.dart'; import 'package:instabug_flutter/src/generated/instabug.api.g.dart'; import 'package:instabug_flutter/src/utils/ibg_build_info.dart'; +import 'package:instabug_flutter/src/utils/network_manager.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -13,6 +16,7 @@ import 'network_logger_test.mocks.dart'; ApmHostApi, InstabugHostApi, IBGBuildInfo, + NetworkManager, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -21,6 +25,7 @@ void main() { final mApmHost = MockApmHostApi(); final mInstabugHost = MockInstabugHostApi(); final mBuildInfo = MockIBGBuildInfo(); + final mManager = MockNetworkManager(); final logger = NetworkLogger(); final data = NetworkData( @@ -32,11 +37,21 @@ void main() { setUpAll(() { APM.$setHostApi(mApmHost); NetworkLogger.$setHostApi(mInstabugHost); + NetworkLogger.$setManager(mManager); IBGBuildInfo.setInstance(mBuildInfo); }); + setUp(() { + reset(mApmHost); + reset(mInstabugHost); + reset(mBuildInfo); + reset(mManager); + }); + test('[networkLog] should call 1 host method on iOS', () async { when(mBuildInfo.isAndroid).thenReturn(false); + when(mManager.obfuscateLog(data)).thenReturn(data); + when(mManager.omitLog(data)).thenReturn(false); await logger.networkLog(data); @@ -51,6 +66,8 @@ void main() { test('[networkLog] should call 2 host methods on Android', () async { when(mBuildInfo.isAndroid).thenReturn(true); + when(mManager.obfuscateLog(data)).thenReturn(data); + when(mManager.omitLog(data)).thenReturn(false); await logger.networkLog(data); @@ -62,4 +79,68 @@ void main() { mApmHost.networkLogAndroid(data.toJson()), ).called(1); }); + + test('[networkLog] should obfuscate network data before logging', () async { + final obfuscated = data.copyWith(requestBody: 'obfuscated'); + + when(mBuildInfo.isAndroid).thenReturn(true); + when(mManager.obfuscateLog(data)).thenReturn(obfuscated); + when(mManager.omitLog(data)).thenReturn(false); + + await logger.networkLog(data); + + verify( + mManager.obfuscateLog(data), + ).called(1); + + verify( + mInstabugHost.networkLog(obfuscated.toJson()), + ).called(1); + + verify( + mApmHost.networkLogAndroid(obfuscated.toJson()), + ).called(1); + }); + + test('[networkLog] should not log data if it should be omitted', () async { + const omit = true; + + when(mBuildInfo.isAndroid).thenReturn(true); + when(mManager.obfuscateLog(data)).thenReturn(data); + when(mManager.omitLog(data)).thenReturn(omit); + + await logger.networkLog(data); + + verify( + mManager.omitLog(data), + ).called(1); + + verifyNever( + mInstabugHost.networkLog(data.toJson()), + ); + + verifyNever( + mApmHost.networkLogAndroid(data.toJson()), + ); + }); + + test('[obfuscateLog] should set obfuscation callback on manager', () async { + FutureOr callback(NetworkData data) => data; + + NetworkLogger.obfuscateLog(callback); + + verify( + mManager.setObfuscateLogCallback(callback), + ).called(1); + }); + + test('[omitLog] should set omission callback on manager', () async { + FutureOr callback(NetworkData data) => true; + + NetworkLogger.omitLog(callback); + + verify( + mManager.setOmitLogCallback(callback), + ).called(1); + }); } diff --git a/test/network_manager_test.dart b/test/network_manager_test.dart new file mode 100644 index 000000000..31776c7ea --- /dev/null +++ b/test/network_manager_test.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:instabug_flutter/src/utils/network_manager.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + WidgetsFlutterBinding.ensureInitialized(); + + final data = NetworkData( + url: "https://httpbin.org/get", + method: "GET", + startTime: DateTime.now(), + ); + late NetworkManager manager; + + setUp(() { + manager = NetworkManager(); + }); + + test('[obfuscateLog] should return same data when obfuscate log callback', + () async { + final result = await manager.obfuscateLog(data); + + expect(result, equals(data)); + }); + + test( + '[obfuscateLog] should obfuscate data when [setObfuscateLogCallback] has set a callback', + () async { + final obfuscated = data.copyWith(requestBody: 'obfuscated'); + final completer = Completer(); + FutureOr callback(NetworkData data) { + completer.complete(data); + return obfuscated; + } + + manager.setObfuscateLogCallback(callback); + + final result = await manager.obfuscateLog(data); + + expect(completer.isCompleted, isTrue); + + expect(await completer.future, data); + + expect(result, equals(obfuscated)); + }); + + test('[omitLog] should return false when no omit log callback', () async { + const expected = false; + + final result = await manager.omitLog(data); + + expect(result, equals(expected)); + }); + + test( + '[omitLog] should use omit callback when [setOmitLogCallback] has set a callback', + () async { + const omit = true; + final completer = Completer(); + FutureOr callback(NetworkData data) { + completer.complete(data); + return omit; + } + + manager.setOmitLogCallback(callback); + + final result = await manager.omitLog(data); + + expect(completer.isCompleted, isTrue); + + expect(await completer.future, data); + + expect(result, equals(omit)); + }); +}