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

DOH for web & custom reachability checker #50

Merged
merged 9 commits into from
Apr 22, 2024
3 changes: 3 additions & 0 deletions .fvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"flutter": "3.16.9"
}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
## 3.0.0

- Added possibility to adding custom implementation of network checker.
- Added DNS Over HTTPS for WebHostReachalilityChecker
- Removed deprecated fields.

## 2.2.1

- Support new connectivity plus multiple connection type
- Fix crash on iphone.

Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A plugin that wraps [connectivity_plus](https://pub.dev/packages/connectivity_pl
- [isConnected](#get-isconnected)
- [connectionTypes](#get-connectiontypes)
- [untilConnects](#untilconnects)
- [custom network checker](#custom-Network-checker)

## Motivation

Expand Down Expand Up @@ -156,6 +157,34 @@ connecteo.untilConnects().then((_) {
});
```

### Custom network checker

In the `ConnectionChecker` there's a factory method `fromReachabilityChecker` designed for integrating custom network checker implementations. To begin, you'll need to create a class that extends `HostReachabilityChecker` and implements 2 methods `hostLookup` and `canReachAnyHost`.

> **Note**
> If you don't want to use one of those `canReachAnyHost` or `hostLookup` methods, simply return true`.

Let's look at parameters of `fromReachabilityChecker` constructor:

- hostReachabilityChecker - it is a custom implementation of network checker.
- requestInterval - it is a `Duration` which is being used for the interval how often the internet connection status should be refreshed. By default, its value is set to 3 seconds.
- failureAttempts - the number of maximum trials between changing the online to offline state.When the lost connection won't go back after number of `failureAttempts`, the `connectionStream` and `isConnected` will return false values until the connection gets back. The default value is set to 4 attempts.

Example:

```dart

class CustomReachabilityChecker extends HostReachabilityChecker {
// TODO implementation
}

final connecteo = ConnectionChecker.fromReachabilityChecker(
hostReachabilityChecker: CustomReachabilityChecker(),
failureAttempts: 7,
requestInterval: Duration(seconds: 5),
);
```

---

For more detailed information about the package possibilities, visit the [API documentation](https://pub.dev/documentation/connecteo/latest/connecteo/connecteo-library.html).
Expand Down
1 change: 1 addition & 0 deletions lib/connecteo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export 'src/connection_checker.dart';
export 'src/connection_entry.dart';
export 'src/connection_entry_type.dart';
export 'src/connection_type.dart';
export 'src/host_reachability_checker.dart' show HostReachabilityChecker;
86 changes: 48 additions & 38 deletions lib/src/connection_checker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,28 +71,47 @@ class ConnectionChecker {
String? baseUrlLookupAddress,
Duration? requestInterval,
int? failureAttempts,
@Deprecated('Use [checkConnectionEntriesNative] instead')
List<ConnectionEntry>? checkAddresses,
@Deprecated('Use [checkConnectionEntriesWeb] instead')
List<ConnectionEntry>? checkApiUrls,
@Deprecated('Use [hostReachabilityTimeout] instead')
Duration? checkOverDnsTimeout,
}) {
final reachabilityTimeout = hostReachabilityTimeout ?? checkOverDnsTimeout;
final nativeEntries = checkConnectionEntriesNative ?? checkAddresses;
final webEntries = checkConnectionEntriesWeb ?? checkApiUrls;

return ConnectionChecker._(
checkConnectionEntriesNative: nativeEntries,
checkConnectionEntriesWeb: webEntries,
hostReachabilityTimeout: reachabilityTimeout,
checkConnectionEntriesNative: checkConnectionEntriesNative,
checkConnectionEntriesWeb: checkConnectionEntriesWeb,
hostReachabilityTimeout: hostReachabilityTimeout,
baseUrlLookupAddress: baseUrlLookupAddress,
checkHostReachability: checkHostReachability,
failureAttempts: failureAttempts,
requestInterval: requestInterval,
);
}

/// Factory method an instance of the [ConnectionChecker] class.
///
/// - `hostReachabilityChecker` - [HostReachabilityChecker] is custom
/// implementation of network checking methods, which is used by
/// [ConnectionChecker].
///
/// - `reguestInterval` - a [Duration] that is being used for the interval
/// how often the internet connection status should be refreshed. By default
/// its value is set to 3 seconds.
///
/// - `failureAttempts` - the number of maximum trials between changing the
/// online to offline state. If the internet connection is lost, the
/// [ConnectionChecker] will try to reconnect by every [requestInterval].
/// When the lost connection won't go back after a number of [failureAttempts],
/// the [connectionStream] and [isConnected] will return false values until
/// connection get back. The default value is set to 4 attempts.
factory ConnectionChecker.fromReachabilityChecker({
required HostReachabilityChecker hostReachabilityChecker,
Duration? requestInterval,
int? failureAttempts,
}) {
return ConnectionChecker._(
checkHostReachability: true,
failureAttempts: failureAttempts,
requestInterval: requestInterval,
hostReachabilityChecker: hostReachabilityChecker,
);
}

ConnectionChecker._({
required bool checkHostReachability,
List<ConnectionEntry>? checkConnectionEntriesNative,
Expand All @@ -106,18 +125,20 @@ class ConnectionChecker {
Mapper<List<ConnectivityResult>, List<ConnectionType>>?
connectionTypeMapper,
}) : _checkHostReachability = checkHostReachability,
_hostReachabilityTimeout = hostReachabilityTimeout,
_baseUrlLookupAddress = baseUrlLookupAddress,
_checkBaseUrlReachability =
baseUrlLookupAddress != null || hostReachabilityChecker != null,
_failureAttempts = failureAttempts ?? _defaultFailureAttempts,
_requestInterval = requestInterval ?? _defaultRequestInterval,
_connectivity = connectivity ?? Connectivity(),
_connectionTypeMapper = connectionTypeMapper ?? ConnectionTypeMapper(),
_checkConnectionEntries =
(kIsWeb ? checkConnectionEntriesWeb : checkConnectionEntriesNative),
_hostReachabilityChecker = hostReachabilityChecker ??
(kIsWeb
? WebHostReachabilityChecker()
: DefaultHostReachabilityChecker());
getPlatformHostReachabilityChecker(
baseUrl: baseUrlLookupAddress,
connectionEntries: kIsWeb
? checkConnectionEntriesWeb
: checkConnectionEntriesNative,
timeout: hostReachabilityTimeout,
);

/// Constructs a special [ConnectionChecker] instance, for the sake of
/// unit testing.
Expand Down Expand Up @@ -153,10 +174,8 @@ class ConnectionChecker {
final Mapper<List<ConnectivityResult>, List<ConnectionType>>
_connectionTypeMapper;

final List<ConnectionEntry>? _checkConnectionEntries;
final Duration? _hostReachabilityTimeout;
final String? _baseUrlLookupAddress;
final bool _checkHostReachability;
final bool _checkBaseUrlReachability;
final Duration _requestInterval;
final int _failureAttempts;

Expand Down Expand Up @@ -217,7 +236,7 @@ class ConnectionChecker {
return isHostReachable;
}

/// Returns the current [ConnectionType] of your device.
/// Returns the current [List<ConnectionType>] of your device.
///
/// Do not use it for determination of your current connection status - this
/// getter only provides the information about current connection type,
Expand Down Expand Up @@ -274,23 +293,14 @@ class ConnectionChecker {
}

Future<bool> get _hostReachable async {
final hostReachable = _checkHostReachability
? await _hostReachabilityChecker.canReachAnyHost(
connectionEntries: _checkConnectionEntries,
timeout: _hostReachabilityTimeout,
)
return _checkHostReachability
? await _hostReachabilityChecker.canReachAnyHost()
: true;
return hostReachable;
}

Future<bool> get _baseUrlReachable async {
if (_baseUrlLookupAddress != null && _baseUrlLookupAddress!.isNotEmpty) {
final result = await _hostReachabilityChecker.hostLookup(
baseUrl: _baseUrlLookupAddress!,
);
return result;
} else {
return true;
}
return _checkBaseUrlReachability
? await _hostReachabilityChecker.hostLookup()
: true;
}
}
2 changes: 0 additions & 2 deletions lib/src/connection_entry_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@
enum ConnectionEntryType {
ip,
url,
@Deprecated('Use [ConnectionEntryType.url] instead')
apiUrl,
}
110 changes: 84 additions & 26 deletions lib/src/host_reachability_checker.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:connecteo/src/connection_entry.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;

const _dnsPort = 53;
Expand Down Expand Up @@ -31,20 +32,73 @@ final _defaultUrls = List<ConnectionEntry>.unmodifiable([
),
]);

/// A class for custom implementation how the desired hosts are reached.
///
/// This abstract class provides a contract used by ConnectionChecker class.
/// The contract assumes own implementation of methods to perform
/// host lookup of desired url along with the reachabiltiy of provided hosts.
///
/// Example usage:
/// ```dart
/// class CustomReachabilityChecker extends HostReachabilityChecker { [...] }
///
/// final connecteo = ConnectionChecker.fromReachabilityChecker(
/// hostReachabilityChecker: CustomReachabilityChecker(), [...]
/// );
/// ```
abstract class HostReachabilityChecker {
AndriiChemer marked this conversation as resolved.
Show resolved Hide resolved
Future<bool> hostLookup({required String baseUrl});
/// Should perform a host lookup.
///
/// Returns a [Future] that completes with a boolean value which should indicate
/// if the desired host lookup was successful.
Future<bool> hostLookup();

/// Should check if desired hosts are reachable (e.g. via opening a socket
/// connection to each address)
///
/// Returns a [Future] that completes with a boolean value which should indicate
/// if any of the desired hosts are reachable.
Future<bool> canReachAnyHost();
}

HostReachabilityChecker getPlatformHostReachabilityChecker({
String? baseUrl,
List<ConnectionEntry>? connectionEntries,
Duration? timeout,
}) {
if (kIsWeb) {
return WebHostReachabilityChecker(
baseUrl: baseUrl,
connectionEntries: connectionEntries,
);
} else {
return DefaultHostReachabilityChecker(
baseUrl: baseUrl,
connectionEntries: connectionEntries,
timeout: timeout,
);
}
}

Future<bool> canReachAnyHost({
class DefaultHostReachabilityChecker extends HostReachabilityChecker {
DefaultHostReachabilityChecker({
String? baseUrl,
List<ConnectionEntry>? connectionEntries,
Duration? timeout,
});
}
}) : _baseUrl = baseUrl ?? '',
_connectionEntries = connectionEntries ?? _defaultIpAddresses,
_timeout = timeout ?? _defaultTimeout;

final String _baseUrl;
final List<ConnectionEntry> _connectionEntries;
final Duration _timeout;

class DefaultHostReachabilityChecker implements HostReachabilityChecker {
@override
Future<bool> hostLookup({required String baseUrl}) async {
Future<bool> hostLookup() async {
if (_baseUrl.isEmpty) return false;

try {
final host = Uri.parse(baseUrl).host;
final host = Uri.parse(_baseUrl).host;
await InternetAddress.lookup(host);

return true;
Expand All @@ -54,16 +108,12 @@ class DefaultHostReachabilityChecker implements HostReachabilityChecker {
}

@override
Future<bool> canReachAnyHost({
List<ConnectionEntry>? connectionEntries,
Duration? timeout,
}) async {
final addresses = connectionEntries ?? _defaultIpAddresses;
Future<bool> canReachAnyHost() async {
final connectionResults = await Future.wait(
addresses.map(
_connectionEntries.map(
(host) => _canIoReachHost(
entry: host,
timeout: timeout ?? _defaultTimeout,
timeout: _timeout,
),
),
);
Expand All @@ -89,11 +139,22 @@ class DefaultHostReachabilityChecker implements HostReachabilityChecker {
}
}

class WebHostReachabilityChecker implements HostReachabilityChecker {
class WebHostReachabilityChecker extends HostReachabilityChecker {
WebHostReachabilityChecker({
String? baseUrl,
List<ConnectionEntry>? connectionEntries,
}) : _baseUrl = baseUrl ?? '',
_connectionEntries = connectionEntries ?? _defaultUrls;

final String _baseUrl;
final List<ConnectionEntry> _connectionEntries;

@override
Future<bool> hostLookup({required String baseUrl}) async {
Future<bool> hostLookup() async {
if (_baseUrl.isEmpty) return false;

try {
final uri = Uri.parse(baseUrl);
final uri = Uri.parse(_baseUrl);
final result = await http.head(uri);

return (result.statusCode == HttpStatus.ok);
Expand All @@ -103,16 +164,11 @@ class WebHostReachabilityChecker implements HostReachabilityChecker {
}

@override
Future<bool> canReachAnyHost({
List<ConnectionEntry>? connectionEntries,
Duration? timeout,
}) async {
final addresses = connectionEntries ?? _defaultUrls;
Future<bool> canReachAnyHost() async {
final connectionResults = await Future.wait(
addresses.map(
_connectionEntries.map(
(host) => _canWebReachHost(
entry: host,
timeout: timeout ?? _defaultTimeout,
),
),
);
Expand All @@ -122,11 +178,13 @@ class WebHostReachabilityChecker implements HostReachabilityChecker {

Future<bool> _canWebReachHost({
required ConnectionEntry entry,
required Duration timeout,
}) async {
try {
final uri = Uri.parse(entry.host);
final result = await http.get(uri);
final result = await http.get(
uri,
headers: {'accept': 'application/dns-json'},
);

return (result.statusCode == HttpStatus.ok);
} catch (_) {
Expand Down
6 changes: 3 additions & 3 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: connecteo
description: A plugin that wraps connectivity_plus and adds some additional data connection checks.
version: 2.2.1
version: 3.0.0
repository: https://github.com/Iteo/connecteo
issue_tracker: https://github.com/Iteo/connecteo/issues
homepage: https://github.com/Iteo/connecteo
documentation: https://github.com/Iteo/connecteo/blob/master/README.md

environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.10.0"
sdk: ">=3.2.0 <4.0.0"
flutter: ">=3.16.0"

dependencies:
connectivity_plus: ^6.0.1
Expand Down
Loading