feat(ui): decouple WSS from UoT and add standalone Reality toggle

Extracted the WSS toggle from the UoT stealth block to make it
accessible regardless of transport mode. Added a dedicated XTLS-Reality
toggle to avoid relying on empty/non-empty PBK strings to determine
the enabled state, allowing users to toggle Reality without wiping keys.
This commit is contained in:
ospab 2026-05-29 17:36:31 +03:00
parent f88de11d98
commit 0a022a4763
1 changed files with 16 additions and 6 deletions

View File

@ -149,7 +149,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
"sessions": int.tryParse(muxSessions) ?? 2, "sessions": int.tryParse(muxSessions) ?? 2,
}, },
"reality": { "reality": {
"enabled": widget.prefs.getString('pbk')?.isNotEmpty ?? false, "enabled": widget.prefs.getBool('reality_enabled') ?? false,
"dest": "", "dest": "",
"private_key": "", "private_key": "",
"pbk": widget.prefs.getString('pbk') ?? "", "pbk": widget.prefs.getString('pbk') ?? "",
@ -247,7 +247,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
"sessions": int.tryParse(muxSessions) ?? 2, "sessions": int.tryParse(muxSessions) ?? 2,
}, },
"reality": { "reality": {
"enabled": widget.prefs.getString('pbk')?.isNotEmpty ?? false, "enabled": widget.prefs.getBool('reality_enabled') ?? false,
"dest": "", "dest": "",
"private_key": "", "private_key": "",
"pbk": widget.prefs.getString('pbk') ?? "", "pbk": widget.prefs.getString('pbk') ?? "",
@ -866,6 +866,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
bool _obscureKey = true; bool _obscureKey = true;
bool _debugMode = false; bool _debugMode = false;
bool _wss = false; bool _wss = false;
bool _realityEnabled = false;
String _transportMode = 'udp'; // 'udp' | 'uot' String _transportMode = 'udp'; // 'udp' | 'uot'
String _tunStack = 'ostp'; // 'system' | 'ostp' String _tunStack = 'ostp'; // 'system' | 'ostp'
bool _muxEnabled = false; bool _muxEnabled = false;
@ -889,6 +890,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_pbkCtrl = TextEditingController(text: widget.prefs.getString('pbk') ?? ''); _pbkCtrl = TextEditingController(text: widget.prefs.getString('pbk') ?? '');
_sidCtrl = TextEditingController(text: widget.prefs.getString('sid') ?? ''); _sidCtrl = TextEditingController(text: widget.prefs.getString('sid') ?? '');
_wss = widget.prefs.getBool('wss') ?? false; _wss = widget.prefs.getBool('wss') ?? false;
_realityEnabled = widget.prefs.getBool('reality_enabled') ?? false;
_transportMode = widget.prefs.getString('transport_mode') ?? 'udp'; _transportMode = widget.prefs.getString('transport_mode') ?? 'udp';
_tunStack = widget.prefs.getString('tun_stack') ?? 'ostp'; _tunStack = widget.prefs.getString('tun_stack') ?? 'ostp';
_debugMode = widget.prefs.getBool('debug_mode') ?? false; _debugMode = widget.prefs.getBool('debug_mode') ?? false;
@ -928,6 +930,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
widget.prefs.setString('ex_processes', _processesCtrl.text.trim()); widget.prefs.setString('ex_processes', _processesCtrl.text.trim());
widget.prefs.setBool('debug_mode', _debugMode); widget.prefs.setBool('debug_mode', _debugMode);
widget.prefs.setBool('wss', _wss); widget.prefs.setBool('wss', _wss);
widget.prefs.setBool('reality_enabled', _realityEnabled);
widget.prefs.setString('transport_mode', _transportMode); widget.prefs.setString('transport_mode', _transportMode);
widget.prefs.setString('tun_stack', _tunStack); widget.prefs.setString('tun_stack', _tunStack);
widget.prefs.setString('stealth_sni', _stealthSniCtrl.text.trim()); widget.prefs.setString('stealth_sni', _stealthSniCtrl.text.trim());
@ -1068,6 +1071,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_pbkCtrl.text = uri.queryParameters['pbk'] ?? ''; _pbkCtrl.text = uri.queryParameters['pbk'] ?? '';
_sidCtrl.text = uri.queryParameters['sid'] ?? ''; _sidCtrl.text = uri.queryParameters['sid'] ?? '';
_wss = uri.queryParameters['wss'] == 'true'; _wss = uri.queryParameters['wss'] == 'true';
_realityEnabled = uri.queryParameters['reality'] == 'true';
final type = uri.queryParameters['type'] ?? 'udp'; final type = uri.queryParameters['type'] ?? 'udp';
_transportMode = type == 'tcp' || type == 'http' ? 'uot' : 'udp'; _transportMode = type == 'tcp' || type == 'http' ? 'uot' : 'udp';
_owndns = uri.queryParameters['owndns'] == 'true'; _owndns = uri.queryParameters['owndns'] == 'true';
@ -1158,7 +1162,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 16),
_buildToggle('WebSocket (WSS)', 'Инкапсулировать транспорт в RFC 6455 (для строгого DPI)', _wss, (val) {
setState(() {
_wss = val;
});
}),
const SizedBox(height: 16),
// Stealth parameters // Stealth parameters
AnimatedCrossFade( AnimatedCrossFade(
@ -1229,13 +1239,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
}), }),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildToggle('WebSocket (WSS)', 'Использовать RFC 6455 (для строгого DPI)', _wss, (val) { _buildToggle('XTLS-Reality', 'Подделка TLS-сессии (Stealth-домен должен быть TLS 1.3)', _realityEnabled, (val) {
setState(() { setState(() {
_wss = val; _realityEnabled = val;
}); });
}), }),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildTextField('Reality PublicKey (pbk)', _pbkCtrl, hint: 'Оставьте пустым для отключения Reality'), _buildTextField('Reality PublicKey (pbk)', _pbkCtrl, hint: 'Публичный ключ сервера'),
_buildTextField('Reality ShortId (sid)', _sidCtrl, hint: 'Опционально (необязательно)'), _buildTextField('Reality ShortId (sid)', _sidCtrl, hint: 'Опционально (необязательно)'),
], ],
), ),