Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ConnectivityIntegration for web #1765

Merged
merged 11 commits into from
Dec 18, 2023
57 changes: 57 additions & 0 deletions flutter/lib/src/integrations/connectivity_integration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'dart:async';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:meta/meta.dart';
import '../../sentry_flutter.dart';

class ConnectivityIntegration extends Integration<SentryFlutterOptions> {
Connectivity connectivity = Connectivity();
Hub? _hub;
StreamSubscription<ConnectivityResult>? _subscription;

@override
void call(Hub hub, SentryFlutterOptions options) {
_hub = hub;
_subscription = connectivity.onConnectivityChanged.listen(addBreadcrumb);

options.sdk.addIntegration('connectivityIntegration');
}

@override
void close() {
_hub = null;
_subscription?.cancel();
_subscription = null;
}

@internal
@visibleForTesting
void addBreadcrumb(ConnectivityResult result) {
_hub?.addBreadcrumb(
Breadcrumb(
category: 'device.connectivity',
level: SentryLevel.info,
type: 'connectivity',
data: {'connectivity': result.toSentryConnectivity()}),
);
}
}

extension on ConnectivityResult {
String toSentryConnectivity() {
switch (this) {
case ConnectivityResult.bluetooth:
case ConnectivityResult.vpn:
case ConnectivityResult.wifi:
return 'wifi';
case ConnectivityResult.ethernet:
return 'ethernet';
case ConnectivityResult.mobile:
return 'cellular';
case ConnectivityResult.none:
return 'none';
case ConnectivityResult.other:
return 'other';
}
}
}
5 changes: 5 additions & 0 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import '../sentry_flutter.dart';
import 'event_processor/android_platform_exception_event_processor.dart';
import 'event_processor/flutter_exception_event_processor.dart';
import 'event_processor/platform_exception_event_processor.dart';
import 'integrations/connectivity_integration.dart';
import 'integrations/screenshot_integration.dart';
import 'native/factory.dart';
import 'native/native_scope_observer.dart';
Expand Down Expand Up @@ -171,6 +172,10 @@ mixin SentryFlutter {
integrations.add(ScreenshotIntegration());
}

if (!platform.isIOS && !platform.isMacOS || !platform.isAndroid) {
integrations.add(ConnectivityIntegration());
}

// works with Skia, CanvasKit and HTML renderer
integrations.add(SentryViewHierarchyIntegration());

Expand Down
1 change: 1 addition & 0 deletions flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies:
package_info_plus: '>=1.0.0 <=5.0.1'
meta: ^1.3.0
ffi: ^2.0.0
connectivity_plus: ^5.0.2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda against https://develop.sentry.dev/philosophy/#dependencies-cost and as known from package_info_plus it's a quite frequent source of major updates.

denrase marked this conversation as resolved.
Show resolved Hide resolved

dev_dependencies:
build_runner: ^2.4.2
Expand Down
149 changes: 149 additions & 0 deletions flutter/test/integrations/connectivity_integration_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry_flutter/src/integrations/connectivity_integration.dart';
import 'package:sentry_flutter/src/sentry_flutter_options.dart';

import '../mocks.dart';
import '../mocks.mocks.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();

late Fixture fixture;

setUp(() {
fixture = Fixture();
});

verifyBreadcrumb(Breadcrumb crumb, String connectivityData) {
expect(crumb.category, 'device.connectivity');
expect(crumb.type, 'connectivity');
expect(crumb.level, SentryLevel.info);
expect(crumb.data?['connectivity'], connectivityData);
}

test('adds integration', () {
final sut = fixture.getSut();
sut(fixture.hub, fixture.options);

expect(fixture.options.sdk.integrations.contains('connectivityIntegration'),
true);
});

test(
'$ConnectivityIntegration: connectivity changed `bluetooth` adds `wifi` breadcrumb',
() {
final integration = fixture.getSut();
integration.call(fixture.hub, fixture.options);

integration.addBreadcrumb(ConnectivityResult.bluetooth);

final crumb = verify(
fixture.hub.addBreadcrumb(captureAny),
).captured.first as Breadcrumb;

verifyBreadcrumb(crumb, 'wifi');
});

test(
'$ConnectivityIntegration: connectivity changed `wifi` adds `wifi` breadcrumb',
() {
final integration = fixture.getSut();
integration.call(fixture.hub, fixture.options);

integration.addBreadcrumb(ConnectivityResult.wifi);

final crumb = verify(
fixture.hub.addBreadcrumb(captureAny),
).captured.first as Breadcrumb;

verifyBreadcrumb(crumb, 'wifi');
});

test(
'$ConnectivityIntegration: connectivity changed `vpn` adds `vpn` breadcrumb',
() {
final integration = fixture.getSut();
integration.call(fixture.hub, fixture.options);

integration.addBreadcrumb(ConnectivityResult.vpn);

final crumb = verify(
fixture.hub.addBreadcrumb(captureAny),
).captured.first as Breadcrumb;

verifyBreadcrumb(crumb, 'wifi');
});

test(
'$ConnectivityIntegration: connectivity changed `ethernet` adds `ethernet` breadcrumb',
() {
final integration = fixture.getSut();
integration.call(fixture.hub, fixture.options);

integration.addBreadcrumb(ConnectivityResult.ethernet);

final crumb = verify(
fixture.hub.addBreadcrumb(captureAny),
).captured.first as Breadcrumb;

verifyBreadcrumb(crumb, 'ethernet');
});

test(
'$ConnectivityIntegration: connectivity changed `mobile` adds `cellular` breadcrumb',
() {
final integration = fixture.getSut();
integration.call(fixture.hub, fixture.options);

integration.addBreadcrumb(ConnectivityResult.mobile);

final crumb = verify(
fixture.hub.addBreadcrumb(captureAny),
).captured.first as Breadcrumb;

verifyBreadcrumb(crumb, 'cellular');
});

test(
'$ConnectivityIntegration: connectivity changed `other` adds `other` breadcrumb',
() {
final integration = fixture.getSut();
integration.call(fixture.hub, fixture.options);

integration.addBreadcrumb(ConnectivityResult.other);

final crumb = verify(
fixture.hub.addBreadcrumb(captureAny),
).captured.first as Breadcrumb;

verifyBreadcrumb(crumb, 'other');
});

test(
'$ConnectivityIntegration: connectivity changed `none` adds `none` breadcrumb',
() {
final integration = fixture.getSut();
integration.call(fixture.hub, fixture.options);

integration.addBreadcrumb(ConnectivityResult.none);

final crumb = verify(
fixture.hub.addBreadcrumb(captureAny),
).captured.first as Breadcrumb;

verifyBreadcrumb(crumb, 'none');
});
}

class Fixture {
final hub = MockHub();
final options = SentryFlutterOptions(dsn: fakeDsn);

ConnectivityIntegration getSut() {
return ConnectivityIntegration();
}
}
2 changes: 1 addition & 1 deletion flutter/test/sentry_flutter_options_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void main() {
expect(options.enableAutoNativeBreadcrumbs, isFalse);
});

testWidgets('useFlutterBreadcrumbTracking', (WidgetTester tester) async {
testWidgets('useNativeBreadcrumbTracking', (WidgetTester tester) async {
final options = SentryFlutterOptions();
options.useNativeBreadcrumbTracking();

Expand Down
29 changes: 25 additions & 4 deletions flutter/test/sentry_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_flutter/src/integrations/connectivity_integration.dart';
import 'package:sentry_flutter/src/integrations/integrations.dart';
import 'package:sentry_flutter/src/integrations/screenshot_integration.dart';
import 'package:sentry_flutter/src/profiling.dart';
Expand Down Expand Up @@ -43,6 +44,11 @@ final nativeIntegrations = [
NativeSdkIntegration,
];

// These should be added to every platform except Android & iOS/macOS.
final nonMobileIntegrations = [
ConnectivityIntegration,
];

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

Expand Down Expand Up @@ -88,7 +94,11 @@ void main() {
...platformAgnosticIntegrations,
...nonWebIntegrations,
],
shouldNotHaveIntegrations: iOsAndMacOsIntegrations);
shouldNotHaveIntegrations: [
...iOsAndMacOsIntegrations,
...nonWebIntegrations,
],
);

integrations
.indexWhere((element) => element is WidgetsFlutterBindingIntegration);
Expand Down Expand Up @@ -138,7 +148,10 @@ void main() {
...platformAgnosticIntegrations,
...nonWebIntegrations,
],
shouldNotHaveIntegrations: androidIntegrations,
shouldNotHaveIntegrations: [
...androidIntegrations,
...nonWebIntegrations,
],
);

testBefore(
Expand Down Expand Up @@ -187,7 +200,10 @@ void main() {
...platformAgnosticIntegrations,
...nonWebIntegrations,
],
shouldNotHaveIntegrations: androidIntegrations,
shouldNotHaveIntegrations: [
...androidIntegrations,
...nonWebIntegrations,
]
);

testBefore(
Expand Down Expand Up @@ -234,6 +250,7 @@ void main() {
shouldHaveIntegrations: [
...platformAgnosticIntegrations,
...nonWebIntegrations,
...nonMobileIntegrations,
],
shouldNotHaveIntegrations: [
...androidIntegrations,
Expand Down Expand Up @@ -285,6 +302,7 @@ void main() {
shouldHaveIntegrations: [
...platformAgnosticIntegrations,
...nonWebIntegrations,
...nonMobileIntegrations,
],
shouldNotHaveIntegrations: [
...androidIntegrations,
Expand Down Expand Up @@ -336,7 +354,10 @@ void main() {

testConfiguration(
integrations: integrations,
shouldHaveIntegrations: platformAgnosticIntegrations,
shouldHaveIntegrations: [
...platformAgnosticIntegrations,
...nonMobileIntegrations,
],
shouldNotHaveIntegrations: [
...androidIntegrations,
...iOsAndMacOsIntegrations,
Expand Down