From 363404f9ed094813452513513144854585703d4b Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 14 Oct 2023 14:42:12 +0600 Subject: [PATCH] feat(fl_query_connectivity_plus_adapter): lookup based offline detection --- packages/fl_query/example/pubspec.lock | 6 +- .../lib/connection_checker.dart | 74 +++++++++++++++++++ .../fl_query_connectivity_plus_adapter.dart | 28 ++----- .../lib/lookups/connection_lookup_io.dart | 55 ++++++++++++++ .../lib/lookups/connection_lookup_web.dart | 29 ++++++++ 5 files changed, 169 insertions(+), 23 deletions(-) create mode 100644 packages/fl_query_connectivity_plus_adapter/lib/connection_checker.dart create mode 100644 packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_io.dart create mode 100644 packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_web.dart diff --git a/packages/fl_query/example/pubspec.lock b/packages/fl_query/example/pubspec.lock index 1fcd6d9..746084c 100644 --- a/packages/fl_query/example/pubspec.lock +++ b/packages/fl_query/example/pubspec.lock @@ -111,21 +111,21 @@ packages: path: ".." relative: true source: path - version: "1.0.0-alpha.4" + version: "1.0.0-alpha.5" fl_query_connectivity_plus_adapter: dependency: "direct main" description: path: "../../fl_query_connectivity_plus_adapter" relative: true source: path - version: "0.1.0-alpha.3" + version: "0.1.0-alpha.4" fl_query_devtools: dependency: "direct main" description: path: "../../fl_query_devtools" relative: true source: path - version: "0.1.0-alpha.2" + version: "0.1.0-alpha.3" flutter: dependency: "direct main" description: flutter diff --git a/packages/fl_query_connectivity_plus_adapter/lib/connection_checker.dart b/packages/fl_query_connectivity_plus_adapter/lib/connection_checker.dart new file mode 100644 index 0000000..b0a6302 --- /dev/null +++ b/packages/fl_query_connectivity_plus_adapter/lib/connection_checker.dart @@ -0,0 +1,74 @@ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:fl_query_connectivity_plus_adapter/lookups/connection_lookup_io.dart' + if (dart.library.html) 'package:fl_query_connectivity_plus_adapter/lookups/connection_lookup_web.dart'; + +abstract class ConnectionLookup { + Future doesConnectTo(String address); + Future isVpnActive(); +} + +class InternetConnectivityChecker + with WidgetsBindingObserver, ConnectionLookupMixin { + final StreamController controller; + + bool _wasConnected = true; + + bool get isAppPaused => + WidgetsBinding.instance.lifecycleState == AppLifecycleState.paused; + + InternetConnectivityChecker(Duration duration) + : controller = StreamController.broadcast() { + Timer.periodic(duration, (timer) async { + if (isAppPaused) { + return; + } + await hasConnection(); + }); + Connectivity().onConnectivityChanged.listen((event) async { + if (isAppPaused) { + return; + } + await hasConnection(); + }); + } + + @override + didChangeAppLifecycleState(AppLifecycleState state) async { + if (state == AppLifecycleState.resumed) { + await hasConnection(); + } + } + + Future hasConnection() async { + final connectivityResult = await (Connectivity().checkConnectivity()); + final hasNetwork = [ + ConnectivityResult.mobile, + ConnectivityResult.wifi, + ConnectivityResult.ethernet, + ].contains(connectivityResult); + + final isVpn = + connectivityResult == ConnectivityResult.vpn || await isVpnActive(); + + final connected = hasNetwork + ? kIsWeb + ? await doesConnectTo('/favicon.ico') || + await doesConnectTo('/favicon.png') || + await doesConnectTo('/favicon.gif') + : await doesConnectTo('google.com') || + await doesConnectTo('www.baidu.com') + : isVpn; + + if (connected != _wasConnected) { + _wasConnected = connected; + controller.add(connected); + } + return connected; + } + + Stream get onConnectionChanged => controller.stream; +} diff --git a/packages/fl_query_connectivity_plus_adapter/lib/fl_query_connectivity_plus_adapter.dart b/packages/fl_query_connectivity_plus_adapter/lib/fl_query_connectivity_plus_adapter.dart index 31022d0..98cdfff 100644 --- a/packages/fl_query_connectivity_plus_adapter/lib/fl_query_connectivity_plus_adapter.dart +++ b/packages/fl_query_connectivity_plus_adapter/lib/fl_query_connectivity_plus_adapter.dart @@ -1,32 +1,20 @@ library fl_query_connectivity_plus_adapter; import 'package:fl_query/fl_query.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:fl_query_connectivity_plus_adapter/connection_checker.dart'; /// A [ConnectivityAdapter] that uses the `connectivity_plus` package. class FlQueryConnectivityPlusAdapter extends ConnectivityAdapter { + final InternetConnectivityChecker _adapter; + FlQueryConnectivityPlusAdapter({ + Duration pollingDuration = const Duration(minutes: 5), + }) : _adapter = InternetConnectivityChecker(pollingDuration); + @override Future get isConnected async { - final connection = await Connectivity().checkConnectivity(); - - switch (connection) { - case ConnectivityResult.bluetooth: - case ConnectivityResult.none: - return false; - default: - return true; - } + return await _adapter.hasConnection(); } @override - Stream get onConnectivityChanged => - Connectivity().onConnectivityChanged.map((event) { - switch (event) { - case ConnectivityResult.bluetooth: - case ConnectivityResult.none: - return false; - default: - return true; - } - }); + Stream get onConnectivityChanged => _adapter.onConnectionChanged; } diff --git a/packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_io.dart b/packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_io.dart new file mode 100644 index 0000000..154fc1d --- /dev/null +++ b/packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_io.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:fl_query_connectivity_plus_adapter/connection_checker.dart'; + +mixin ConnectionLookupMixin implements ConnectionLookup { + @override + Future doesConnectTo(String address) async { + try { + final result = await InternetAddress.lookup(address); + return result.isNotEmpty && result[0].rawAddress.isNotEmpty; + } catch (_) { + return false; + } + } + + static const _vpnNames = [ + 'tun', + 'tap', + 'ppp', + 'pptp', + 'l2tp', + 'ipsec', + 'vpn', + 'wireguard', + 'openvpn', + 'softether', + 'proton', + 'strongswan', + 'cisco', + 'forticlient', + 'fortinet', + 'hideme', + 'hidemy', + 'hideman', + 'hidester', + 'lightway', + ]; + + @override + Future isVpnActive() async { + final interfaces = await NetworkInterface.list( + includeLoopback: false, + type: InternetAddressType.any, + ); + + if (interfaces.isEmpty) { + return false; + } + + return interfaces.any( + (interface) => + _vpnNames.any((name) => interface.name.toLowerCase().contains(name)), + ); + } +} diff --git a/packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_web.dart b/packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_web.dart new file mode 100644 index 0000000..2440c76 --- /dev/null +++ b/packages/fl_query_connectivity_plus_adapter/lib/lookups/connection_lookup_web.dart @@ -0,0 +1,29 @@ +import 'dart:html'; +import 'package:fl_query_connectivity_plus_adapter/connection_checker.dart'; + +mixin ConnectionLookupMixin implements ConnectionLookup { + @override + Future doesConnectTo(String address) async { + try { + final url = address.startsWith("http") || address.startsWith("/") + ? address + : "https://$address"; + final request = await HttpRequest.request( + url, + requestHeaders: { + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache', + }, + ); + + return (request.status ?? 0) < 400; + } catch (_) { + return false; + } + } + + @override + Future isVpnActive() { + return Future.value(false); + } +}