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
56 lines
1.8 KiB
Dart
56 lines
1.8 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
enum ConnectionMode { tun, proxy }
|
|
|
|
abstract final class ConnectionSettings {
|
|
ConnectionSettings._();
|
|
|
|
static const String _modeKey = 'connection_mode';
|
|
|
|
static ConnectionMode _mode = ConnectionMode.tun;
|
|
static bool _initialized = false;
|
|
|
|
static ConnectionMode get mode => _mode;
|
|
|
|
static Future<void> initialize() async {
|
|
if (_initialized) return;
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final storedMode = prefs.getString(_modeKey);
|
|
|
|
// 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) {
|
|
// 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<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;
|
|
return true;
|
|
}
|
|
}
|