Author SHA1 Message Date
esefxdzandClaude Opus 5 f26c459f6c fix(app): do not throw from ConnectionSettings.initialize
The migration write is best-effort again. `_mode` is already the safe TUN
default in memory by the time it runs, so a failed write leaves nothing
insecure -- it only means the migration is retried on the next launch.

Throwing there was a startup brick rather than a fail-closed guard.
`initialize()` is awaited from `main()` at lib/main.dart:19, before
`runApp()`, with no try/catch and no `runZonedGuarded`; `FlutterError.onError`
and `PlatformDispatcher.onError` are installed later and would not catch it
either. The branch also runs on every fresh install, since `storedMode` is null
and `null != 'tun'`. A `setString` returning false would therefore have meant a
black screen with no error UI, repeating every launch, in exchange for no
security benefit.

Also restores two comments this PR removed from code it does not otherwise
touch -- the `_reconnect` note on why the repair path shows no rewarded ad, and
the explanation of how an orphaned proxy outlives the app process -- and drops
a stray 'ass' from the legacy-values comment.

The `setMode` half of the PR is unchanged: persisting before mutating memory
and surfacing failures to the UI is the right behaviour.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019HT2x2JvJNg7XNsJubfHFf
2026-09-06 11:41:15 +03:00
Naveax 8cd450d5b1 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.
2026-09-04 00:07:09 +03:00
2 changed files with 28 additions and 7 deletions

No files matched your search

+21 -6
View File
@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
enum ConnectionMode { tun, proxy }
@@ -18,23 +19,37 @@ 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 pre-V3 'auto' label, 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);
// Best-effort. _mode is already the safe TUN default in memory, so a
// failed write leaves nothing insecure — it only means the migration
// is retried next launch. initialize() runs from main() before
// runApp(), with no zone guard, so throwing here would black-screen
// the app on the fresh-install path for no security gain.
final migrated = await prefs.setString(_modeKey, _mode.name);
if (!migrated) {
debugPrint('[ConnectionSettings] Could not persist migrated mode.');
}
}
_initialized = true;
}
static Future<void> setMode(ConnectionMode mode) async {
static Future<bool> 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;
}
}
@@ -51,8 +51,14 @@ class _ConnectionModeTileState extends State<ConnectionModeTile> {
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;