From 3f7aead26319b11ee46538dfd067c12136246e6b Mon Sep 17 00:00:00 2001 From: Naveax Date: Fri, 4 Sep 2026 00:07:09 +0300 Subject: [PATCH] fix(app): fail closed on invalid connection mode state Restore SOCKS5 only from the current proxy value and keep UI state unchanged when persistence fails. --- lib/logic/connection_settings.dart | 21 +++++++++++++------ .../settings/in_settings/connection_mode.dart | 17 +++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/lib/logic/connection_settings.dart b/lib/logic/connection_settings.dart index 6189360..e7a6e36 100644 --- a/lib/logic/connection_settings.dart +++ b/lib/logic/connection_settings.dart @@ -18,23 +18,32 @@ abstract final class ConnectionSettings { final prefs = await SharedPreferences.getInstance(); final storedMode = prefs.getString(_modeKey); - // Any unrecognized value (including the pre-V3 'auto') is TUN. 'proxy' is - // the only persisted value that selects the SOCKS5 mode. + // Only the explicit current proxy value restores SOCKS5. Unknown and + // obsolete values, including the former 'auto'/'ass' labels, fail closed + // to full-tunnel TUN instead of silently selecting a weaker route. _mode = storedMode == ConnectionMode.proxy.name ? ConnectionMode.proxy : ConnectionMode.tun; if (storedMode != _mode.name) { - await prefs.setString(_modeKey, _mode.name); + final migrated = await prefs.setString(_modeKey, _mode.name); + if (!migrated) { + throw StateError('Could not persist migrated connection mode.'); + } } _initialized = true; } - static Future setMode(ConnectionMode mode) async { + static Future setMode(ConnectionMode mode) async { + if (_initialized && _mode == mode) return true; + + final prefs = await SharedPreferences.getInstance(); + final saved = await prefs.setString(_modeKey, mode.name); + if (!saved) return false; + _mode = mode; _initialized = true; - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_modeKey, mode.name); + return true; } } diff --git a/lib/screens/settings/in_settings/connection_mode.dart b/lib/screens/settings/in_settings/connection_mode.dart index 021c3b0..dde1703 100644 --- a/lib/screens/settings/in_settings/connection_mode.dart +++ b/lib/screens/settings/in_settings/connection_mode.dart @@ -51,8 +51,14 @@ class _ConnectionModeTileState extends State { return; } - await ConnectionSettings.setMode(next); + final saved = await ConnectionSettings.setMode(next); if (!mounted) return; + if (!saved) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Could not save connection mode.')), + ); + return; + } setState(() { _mode = next; @@ -61,10 +67,6 @@ class _ConnectionModeTileState extends State { widget.onChanged?.call(next); } - /// Recovers from an orphaned proxy: tear the runtime down and start a fresh - /// session so a readable set of credentials exists again. No rewarded ad — - /// the user is repairing a connection they already paid for, not opening a - /// new one. Future _reconnect() async { if (_reconnecting) return; final vpn = context.read(); @@ -168,11 +170,6 @@ class _ConnectionModeTileState extends State { builder: (context, vpn, _) { final session = vpn.activeSocksSession; if (session == null) { - // The proxy can be running while this session is unknown: - // Android killed the app process, the foreground service kept - // the listener alive, and its credentials existed only in the - // memory that died with it. Saying "Connect" there would be a - // lie — the user is already connected. final orphaned = vpn.status == VpnStatus.connected && vpn.adoptedRunningRuntime; if (orphaned) {