fix(app): stop probing the control plane while disconnected
Android CI / android (push) Canceled after 0s

VpnConnection._init() started a 30 s /health poll from the constructor
with no lifecycle gating, so it ran for the life of the app process -
backgrounded, and whether or not the user ever connected. The app is
excluded from its own tunnel, so those requests left in the clear. A
periodic request that fires while the user is deliberately not connected
is an avoidable fingerprint, and rule 60 already ruled probes out:
reachability is meant to be passive.

All it bought was the status dot before the first connect, and it got
that wrong in both directions - "Offline" on a transient resolver
failure against a server that was fine.

Reachability now comes from a session sync that succeeded. serverHealthy
is isConnected && hasSyncedOnce, so a green dot means a working session
rather than a check made beforehand; while disconnected the bar reads
"Ready" and claims nothing either way.

checkHealth() goes with it. Nothing calls it, and leaving a probe helper
in place invites its reuse.

Pinned in test/session_lifecycle_regression_test.dart so it cannot come
back as a tidy-up. flutter analyze clean, 10/10 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
esefxdzandClaude Opus 5 committed 2026-10-17 21:17:17 +03:00
1 parent 571c07bca8
commit 62e968f133
5 files changed
+39 -37

No files matched your search

-18
View File
@@ -38,9 +38,6 @@ class VpnConnection extends ChangeNotifier {
bool _adoptedRunningRuntime = false;
bool get adoptedRunningRuntime => _adoptedRunningRuntime;
bool _serverReachable = false;
bool get serverReachable => _serverReachable;
ConnectionMode _activeMode = ConnectionMode.tun;
ConnectionMode get activeMode => _activeMode;
@@ -73,7 +70,6 @@ class VpnConnection extends ChangeNotifier {
);
}
Timer? _healthTimer;
StreamSubscription<NetworkSnapshot>? _networkSubscription;
SecureSocksSession? _lastSecureSocks;
@@ -142,11 +138,6 @@ class VpnConnection extends ChangeNotifier {
},
);
unawaited(_checkHealth());
_healthTimer = Timer.periodic(const Duration(seconds: 30), (_) {
unawaited(_checkHealth());
});
try {
final coreVersion = await _vless.getCoreVersion();
if (!_disposed) {
@@ -472,14 +463,6 @@ class VpnConnection extends ChangeNotifier {
notifyListeners();
}
Future<void> _checkHealth() async {
if (_disposed || _status == VpnStatus.connected) return;
final reachable = await HivemindService.checkHealth();
if (_disposed || _status == VpnStatus.connected) return;
_serverReachable = reachable;
notifyListeners();
}
@override
void dispose() {
_disposed = true;
@@ -487,7 +470,6 @@ class VpnConnection extends ChangeNotifier {
_suppressNativeConnect = true;
_userDisconnecting = true;
HivemindService.cancel();
_healthTimer?.cancel();
_networkSubscription?.cancel();
// Provider/UI disposal is not a user disconnect command. Keeping teardown
// in disconnect() prevents lifecycle churn from silently dropping the VPN.