From 6526f6eb95813a5d0217b89bb45af9a0477a26f8 Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Wed, 16 Sep 2026 07:51:46 +1000 Subject: [PATCH 1/2] add second DNS query for internet check --- lib/src/utils/network_diagnosis.dart | 51 +++++++++++++++++++++++----- test/network_diagnosis_test.dart | 10 ++++++ 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/lib/src/utils/network_diagnosis.dart b/lib/src/utils/network_diagnosis.dart index 7bb06838..f82285e8 100644 --- a/lib/src/utils/network_diagnosis.dart +++ b/lib/src/utils/network_diagnosis.dart @@ -70,7 +70,10 @@ const _controlTimeout = Duration(seconds: 3); /// Public DNS resolvers, as IP literals so that reaching them needs no name /// lookup of its own. They are contacted only to tell "the internet is down" /// apart from "that server name does not exist", and only after the login -/// itself has already failed. Two are tried because a network may block one. +/// itself has already failed. Two are tried because a network may block one +/// — some networks (certain mobile carriers in particular) block direct +/// connections to well-known public resolvers specifically, which would +/// otherwise make a perfectly reachable target look "offline". const _controlHosts = ['1.1.1.1', '8.8.8.8']; @@ -79,6 +82,17 @@ const _controlHosts = ['1.1.1.1', '8.8.8.8']; const _controlPort = 443; +/// Number of times the actual target is probed before its failure is +/// trusted. A single dropped DNS query or SYN packet on a slow or lossy +/// link can look exactly like the server being down. Have a +/// second try before anything is blamed on the network or the server. + +const _targetAttempts = 2; + +/// Gap between target probe attempts. + +const _targetRetryDelay = Duration(seconds: 1); + /// Returns the URI for [target], which may be a server URL, a WebID, or a /// bare host name. Returns null when no host can be extracted. @@ -133,16 +147,37 @@ Future _canConnect(String host, int port, Duration timeout) async { } } -/// Reports whether [host] resolves to an address. +/// Reports whether [host] resolves to an address. Retried [_targetAttempts] +/// times, since a single dropped query is not enough to trust. Future _canResolve(String host) async { - try { - await InternetAddress.lookup(host).timeout(_probeTimeout); + for (var attempt = 1; attempt <= _targetAttempts; attempt++) { + try { + await InternetAddress.lookup(host).timeout(_probeTimeout); + + return true; + } on Object { + if (attempt < _targetAttempts) await Future.delayed(_targetRetryDelay); + } + } - return true; - } on Object { - return false; + return false; +} + +/// Reports whether a TCP connection to [host] on [port] can be opened, +/// retried [_targetAttempts] times for the same reason as [_canResolve]. + +Future _canConnectRetrying( + String host, + int port, + Duration timeout, +) async { + for (var attempt = 1; attempt <= _targetAttempts; attempt++) { + if (await _canConnect(host, port, timeout)) return true; + if (attempt < _targetAttempts) await Future.delayed(_targetRetryDelay); } + + return false; } /// Reports whether anything out on the internet can be reached. Uses the IP @@ -186,7 +221,7 @@ Future diagnoseConnection(String target) async { final port = uri.hasPort ? uri.port : (uri.scheme == 'http' ? 80 : 443); - if (await _canConnect(uri.host, port, _probeTimeout)) { + if (await _canConnectRetrying(uri.host, port, _probeTimeout)) { return NetworkStatus.reachable; } diff --git a/test/network_diagnosis_test.dart b/test/network_diagnosis_test.dart index 1af487b6..85542482 100644 --- a/test/network_diagnosis_test.dart +++ b/test/network_diagnosis_test.dart @@ -123,5 +123,15 @@ void main() { anyOf(NetworkStatus.unknownHost, NetworkStatus.offline), ); }); + + test('a genuinely reachable host is reported reachable', () async { + // 20260915 gjw Guards against a control-host-only reachability check: + // this must come back reachable on its own, not merely because + // 1.1.1.1/8.8.8.8 answered — those are not even consulted on this path. + + final status = await diagnoseConnection(server); + + expect(status, NetworkStatus.reachable); + }); }); } From 95308058f9eca4cfa7ce3c38433f1226675ca4ee Mon Sep 17 00:00:00 2001 From: Jess Moore Date: Wed, 16 Sep 2026 07:53:08 +1000 Subject: [PATCH 2/2] update changelog and bump version --- CHANGELOG.md | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 239403dd..9dbf1601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The package is available from ## 1.1 Consolidate Android Login ++ Add internet connection retries [1.0.46 20260916 jesscmoore] + Report offline rather than doing nothing on Login [1.0.45 20260915 gjw] + Name login errors: internet, address or server [1.0.44 20260915 gjw] + Updated version widget dependency [1.0.43 20260914 gjw] diff --git a/pubspec.yaml b/pubspec.yaml index 8c03515b..9a69b2d5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: solidui description: 'A UI library for building Solid applications with Flutter.' -version: 1.0.45 +version: 1.0.46 homepage: https://github.com/anusii/solidui # Scaffold a new Solid Pod file-browser app (a pod browser, with navigation