diff --git a/Cargo.lock b/Cargo.lock index fcff3416c91..33eff117548 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5724,7 +5724,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg 0.50.0", ] @@ -7051,8 +7051,15 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", + "native-tls", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", + "rustls-pki-types", "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.0", "tungstenite", + "webpki-roots 0.26.9", ] [[package]] @@ -7253,10 +7260,15 @@ dependencies = [ "http 1.3.1", "httparse", "log", + "native-tls", "rand 0.9.0", + "rustls 0.23.26", + "rustls-native-certs 0.8.1", + "rustls-pki-types", "sha1", "thiserror 2.0.11", "utf-8", + "webpki-roots 0.26.9", ] [[package]] @@ -7753,6 +7765,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29aad86cec885cafd03e8305fd727c418e970a521322c91688414d5b8efba16b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.8" diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 617c17e38a6..c2382bf20f5 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -147,9 +147,13 @@ const String kOptionDirectxCapture = "enable-directx-capture"; const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification"; const String kOptionEnableTrustedDevices = "enable-trusted-devices"; +// network options +const String kOptionAllowWebSocket = "allow-websocket"; + // buildin opitons const String kOptionHideServerSetting = "hide-server-settings"; const String kOptionHideProxySetting = "hide-proxy-settings"; +const String kOptionHideWebSocketSetting = "hide-websocket-settings"; const String kOptionHideRemotePrinterSetting = "hide-remote-printer-settings"; const String kOptionHideSecuritySetting = "hide-security-settings"; const String kOptionHideNetworkSetting = "hide-network-settings"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index fc35687cf1d..351606d57ee 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1475,11 +1475,70 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y'; final hideProxy = isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; + // final hideWebSocket = isWeb || + // bind.mainGetBuildinOption(key: kOptionHideWebSocketSetting) == 'Y'; + final hideWebSocket = true; - if (hideServer && hideProxy) { + if (hideServer && hideProxy && hideWebSocket) { return Offstage(); } + // Helper function to create network setting ListTiles + Widget listTile({ + required IconData icon, + required String title, + VoidCallback? onTap, + Widget? trailing, + bool showTooltip = false, + String tooltipMessage = '', + }) { + final titleWidget = showTooltip + ? Row( + children: [ + Tooltip( + waitDuration: Duration(milliseconds: 1000), + message: translate(tooltipMessage), + child: Row( + children: [ + Text( + translate(title), + style: TextStyle(fontSize: _kContentFontSize), + ), + SizedBox(width: 5), + Icon( + Icons.help_outline, + size: 14, + color: Theme.of(context) + .textTheme + .titleLarge + ?.color + ?.withOpacity(0.7), + ), + ], + ), + ), + ], + ) + : Text( + translate(title), + style: TextStyle(fontSize: _kContentFontSize), + ); + + return ListTile( + leading: Icon(icon, color: _accentColor), + title: titleWidget, + enabled: !locked, + onTap: onTap, + trailing: trailing, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 16), + minLeadingWidth: 0, + horizontalTitleGap: 10, + ); + } + return _Card( title: 'Network', children: [ @@ -1488,39 +1547,36 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!hideServer) - ListTile( - leading: Icon(Icons.dns_outlined, color: _accentColor), - title: Text( - translate('ID/Relay Server'), - style: TextStyle(fontSize: _kContentFontSize), - ), - enabled: !locked, + listTile( + icon: Icons.dns_outlined, + title: 'ID/Relay Server', onTap: () => showServerSettings(gFFI.dialogManager), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - contentPadding: EdgeInsets.symmetric(horizontal: 16), - minLeadingWidth: 0, - horizontalTitleGap: 10, ), - if (!hideServer && !hideProxy) + if (!hideServer && (!hideProxy || !hideWebSocket)) Divider(height: 1, indent: 16, endIndent: 16), if (!hideProxy) - ListTile( - leading: - Icon(Icons.network_ping_outlined, color: _accentColor), - title: Text( - translate('Socks5/Http(s) Proxy'), - style: TextStyle(fontSize: _kContentFontSize), - ), - enabled: !locked, + listTile( + icon: Icons.network_ping_outlined, + title: 'Socks5/Http(s) Proxy', onTap: changeSocks5Proxy, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + ), + if (!hideProxy && !hideWebSocket) + Divider(height: 1, indent: 16, endIndent: 16), + if (!hideWebSocket) + listTile( + icon: Icons.web_asset_outlined, + title: 'Use WebSocket', + showTooltip: true, + tooltipMessage: 'websocket_tip', + trailing: Switch( + value: mainGetBoolOptionSync(kOptionAllowWebSocket), + onChanged: locked + ? null + : (value) { + mainSetBoolOption(kOptionAllowWebSocket, value); + setState(() {}); + }, ), - contentPadding: EdgeInsets.symmetric(horizontal: 16), - minLeadingWidth: 0, - horizontalTitleGap: 10, ), ], ), diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 600f807e832..4681d231339 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -80,6 +80,7 @@ class _SettingsState extends State with WidgetsBindingObserver { var _enableDirectIPAccess = false; var _enableRecordSession = false; var _enableHardwareCodec = false; + var _allowWebSocket = false; var _autoRecordIncomingSession = false; var _autoRecordOutgoingSession = false; var _allowAutoDisconnect = false; @@ -91,6 +92,7 @@ class _SettingsState extends State with WidgetsBindingObserver { var _hideServer = false; var _hideProxy = false; var _hideNetwork = false; + var _hideWebSocket = false; var _enableTrustedDevices = false; _SettingsState() { @@ -105,6 +107,7 @@ class _SettingsState extends State with WidgetsBindingObserver { bind.mainGetOptionSync(key: kOptionEnableRecordSession)); _enableHardwareCodec = option2bool(kOptionEnableHwcodec, bind.mainGetOptionSync(key: kOptionEnableHwcodec)); + _allowWebSocket = mainGetBoolOptionSync(kOptionAllowWebSocket); _autoRecordIncomingSession = option2bool(kOptionAllowAutoRecordIncoming, bind.mainGetOptionSync(key: kOptionAllowAutoRecordIncoming)); _autoRecordOutgoingSession = option2bool(kOptionAllowAutoRecordOutgoing, @@ -120,6 +123,8 @@ class _SettingsState extends State with WidgetsBindingObserver { _hideProxy = bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; _hideNetwork = bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) == 'Y'; + _hideWebSocket = + true; //bind.mainGetBuildinOption(key: kOptionHideWebSocketSetting) == 'Y'; _enableTrustedDevices = mainGetBoolOptionSync(kOptionEnableTrustedDevices); } @@ -667,6 +672,21 @@ class _SettingsState extends State with WidgetsBindingObserver { onPressed: (context) { changeSocks5Proxy(); }), + if (!disabledSettings && !_hideNetwork && !_hideWebSocket) + SettingsTile.switchTile( + title: Text(translate('Use WebSocket')), + initialValue: _allowWebSocket, + onToggle: isOptionFixed(kOptionAllowWebSocket) + ? null + : (v) async { + await mainSetBoolOption(kOptionAllowWebSocket, v); + final newValue = + await mainGetBoolOption(kOptionAllowWebSocket); + setState(() { + _allowWebSocket = newValue; + }); + }, + ), SettingsTile( title: Text(translate('Language')), leading: Icon(Icons.translate), diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index a4312d959c7..7fd6993dbe2 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -49,14 +49,15 @@ class PlatformFFI { } bool registerEventHandler( - String eventName, String handlerName, HandleEvent handler) { + String eventName, String handlerName, HandleEvent handler, + {bool replace = false}) { debugPrint('registerEventHandler $eventName $handlerName'); var handlers = _eventHandlers[eventName]; if (handlers == null) { _eventHandlers[eventName] = {handlerName: handler}; return true; } else { - if (handlers.containsKey(handlerName)) { + if (!replace && handlers.containsKey(handlerName)) { return false; } else { handlers[handlerName] = handler; diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index 331012aa90f..d831e7bdc1d 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -1852,5 +1852,49 @@ class RustdeskImpl { throw UnimplementedError("sessionGetConnToken"); } + String mainGetPrinterNames({dynamic hint}) { + return ''; + } + + Future sessionPrinterResponse( + {required UuidValue sessionId, + required int id, + required String path, + required String printerName, + dynamic hint}) { + throw UnimplementedError("sessionPrinterResponse"); + } + + Future mainGetCommon({required String key, dynamic hint}) { + throw UnimplementedError("mainGetCommon"); + } + + String mainGetCommonSync({required String key, dynamic hint}) { + throw UnimplementedError("mainGetCommonSync"); + } + + Future mainSetCommon( + {required String key, required String value, dynamic hint}) { + throw UnimplementedError("mainSetCommon"); + } + + Future sessionHandleScreenshot( + {required UuidValue sessionId, required String action, dynamic hint}) { + throw UnimplementedError("sessionHandleScreenshot"); + } + + String? sessionGetCommonSync( + {required UuidValue sessionId, + required String key, + required String param, + dynamic hint}) { + throw UnimplementedError("sessionGetCommonSync"); + } + + Future sessionTakeScreenshot( + {required UuidValue sessionId, required int display, dynamic hint}) { + throw UnimplementedError("sessionTakeScreenshot"); + } + void dispose() {} } diff --git a/libs/hbb_common b/libs/hbb_common index d64954ae221..7839dcf4e4d 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit d64954ae2211e5154fd9036ec4862413f24bbd7b +Subproject commit 7839dcf4e4ddc3feab9b52b0ae1903d443bc084d diff --git a/src/client.rs b/src/client.rs index 137701c54a5..1372f93eb24 100644 --- a/src/client.rs +++ b/src/client.rs @@ -46,7 +46,7 @@ use hbb_common::{ anyhow::{anyhow, Context}, bail, config::{ - self, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, + self, use_ws, Config, LocalConfig, PeerConfig, PeerInfoSerde, Resolution, CONNECT_TIMEOUT, READ_TIMEOUT, RELAY_PORT, RENDEZVOUS_PORT, RENDEZVOUS_SERVERS, }, fs::JobType, @@ -216,7 +216,8 @@ impl Client { if hbb_common::is_ip_str(peer) { return Ok(( ( - connect_tcp(check_port(peer, RELAY_PORT + 1), CONNECT_TIMEOUT).await?, + connect_tcp_local(check_port(peer, RELAY_PORT + 1), None, CONNECT_TIMEOUT) + .await?, true, None, ), @@ -226,7 +227,11 @@ impl Client { // Allow connect to {domain}:{port} if hbb_common::is_domain_port_str(peer) { return Ok(( - (connect_tcp(peer, CONNECT_TIMEOUT).await?, true, None), + ( + connect_tcp_local(peer, None, CONNECT_TIMEOUT).await?, + true, + None, + ), (0, "".to_owned()), )); } @@ -291,7 +296,7 @@ impl Client { log::info!("#{} punch attempt with {}, id: {}", i, my_addr, peer); let mut msg_out = RendezvousMessage::new(); use hbb_common::protobuf::Enum; - let nat_type = if interface.is_force_relay() { + let nat_type = if interface.is_force_relay() || use_ws() || Config::is_proxy() { NatType::SYMMETRIC } else { NatType::from_i32(my_nat_type).unwrap_or(NatType::UNKNOWN_NAT) diff --git a/src/common.rs b/src/common.rs index 61569eb0574..2daf3aa2508 100644 --- a/src/common.rs +++ b/src/common.rs @@ -14,7 +14,7 @@ use hbb_common::{ anyhow::{anyhow, Context}, bail, base64, bytes::Bytes, - config::{self, Config, CONNECT_TIMEOUT, READ_TIMEOUT, RENDEZVOUS_PORT}, + config::{self, use_ws, Config, CONNECT_TIMEOUT, READ_TIMEOUT, RENDEZVOUS_PORT}, futures::future::join_all, futures_util::future::poll_fn, get_version_number, log, @@ -504,6 +504,27 @@ audio_rechannel!(audio_rechannel_8_5, 8, 5); audio_rechannel!(audio_rechannel_8_6, 8, 6); audio_rechannel!(audio_rechannel_8_7, 8, 7); +pub struct CheckTestNatType { + is_direct: bool, +} + +impl CheckTestNatType { + pub fn new() -> Self { + Self { + is_direct: Config::get_socks().is_none() && !config::use_ws(), + } + } +} + +impl Drop for CheckTestNatType { + fn drop(&mut self) { + let is_direct = Config::get_socks().is_none() && !config::use_ws(); + if self.is_direct != is_direct { + test_nat_type(); + } + } +} + pub fn test_nat_type() { use std::sync::atomic::{AtomicBool, Ordering}; std::thread::spawn(move || { @@ -514,9 +535,8 @@ pub fn test_nat_type() { IS_RUNNING.store(true, Ordering::SeqCst); #[cfg(not(any(target_os = "android", target_os = "ios")))] - let is_direct = crate::ipc::get_socks().is_none(); // sync socks BTW - #[cfg(any(target_os = "android", target_os = "ios"))] - let is_direct = Config::get_socks().is_none(); // sync socks BTW + crate::ipc::get_socks_ws(); + let is_direct = Config::get_socks().is_none() && !config::use_ws(); if !is_direct { Config::set_nat_type(NatType::SYMMETRIC as _); IS_RUNNING.store(false, Ordering::SeqCst); @@ -1319,6 +1339,13 @@ pub fn check_process(arg: &str, mut same_uid: bool) -> bool { } pub async fn secure_tcp(conn: &mut Stream, key: &str) -> ResultType<()> { + // Skip additional encryption when using WebSocket connections (wss://) + // as WebSocket Secure (wss://) already provides transport layer encryption. + // This doesn't affect the end-to-end encryption between clients, + // it only avoids redundant encryption between client and server. + if use_ws() { + return Ok(()); + } let rs_pk = get_rs_pk(key); let Some(rs_pk) = rs_pk else { bail!("Handshake failed: invalid public key from rendezvous server"); diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index c1c34ef8299..254a119d55a 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -891,7 +891,10 @@ pub fn main_set_option(key: String, value: String) { ); } - if key.eq("custom-rendezvous-server") { + if key.eq("custom-rendezvous-server") + || key.eq(config::keys::OPTION_ALLOW_WEBSOCKET) + || key.eq("api-server") + { set_option(key, value.clone()); #[cfg(target_os = "android")] crate::rendezvous_mediator::RendezvousMediator::restart(); @@ -2533,7 +2536,10 @@ pub fn main_set_common(_key: String, _value: String) { ); } else if _key == "update-me" { if let Some(new_version_file) = get_download_file_from_url(&_value) { - log::debug!("New version file is downloaed, update begin, {:?}", new_version_file.to_str()); + log::debug!( + "New version file is downloaed, update begin, {:?}", + new_version_file.to_str() + ); if let Some(f) = new_version_file.to_str() { // 1.4.0 does not support "--update" // But we can assume that the new version supports it. diff --git a/src/ipc.rs b/src/ipc.rs index 0b214efd38d..a74a0c1034a 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,4 +1,5 @@ use crate::{ + common::CheckTestNatType, privacy_mode::PrivacyModeState, ui_interface::{get_local_option, set_local_option}, }; @@ -22,7 +23,7 @@ pub use clipboard::ClipboardFile; use hbb_common::{ allow_err, bail, bytes, bytes_codec::BytesCodec, - config::{self, Config, Config2}, + config::{self, keys::OPTION_ALLOW_WEBSOCKET, Config, Config2}, futures::StreamExt as _, futures_util::sink::SinkExt, log, password_security as password, timeout, @@ -282,6 +283,7 @@ pub enum Data { ControllingSessionCount(usize), #[cfg(target_os = "windows")] PortForwardSessionCount(Option), + SocksWs(Option, String)>>), } #[tokio::main(flavor = "current_thread")] @@ -348,29 +350,40 @@ pub async fn new_listener(postfix: &str) -> ResultType { } } -pub struct CheckIfRestart(String, Vec, String, String); +pub struct CheckIfRestart { + stop_service: String, + rendezvous_servers: Vec, + audio_input: String, + voice_call_input: String, + ws: String, + api_server: String, +} impl CheckIfRestart { pub fn new() -> CheckIfRestart { - CheckIfRestart( - Config::get_option("stop-service"), - Config::get_rendezvous_servers(), - Config::get_option("audio-input"), - Config::get_option("voice-call-input"), - ) + CheckIfRestart { + stop_service: Config::get_option("stop-service"), + rendezvous_servers: Config::get_rendezvous_servers(), + audio_input: Config::get_option("audio-input"), + voice_call_input: Config::get_option("voice-call-input"), + ws: Config::get_option(OPTION_ALLOW_WEBSOCKET), + api_server: Config::get_option("api-server"), + } } } impl Drop for CheckIfRestart { fn drop(&mut self) { - if self.0 != Config::get_option("stop-service") - || self.1 != Config::get_rendezvous_servers() + if self.stop_service != Config::get_option("stop-service") + || self.rendezvous_servers != Config::get_rendezvous_servers() + || self.ws != Config::get_option(OPTION_ALLOW_WEBSOCKET) + || self.api_server != Config::get_option("api-server") { RendezvousMediator::restart(); } - if self.2 != Config::get_option("audio-input") { + if self.audio_input != Config::get_option("audio-input") { crate::audio_service::restart(); } - if self.3 != Config::get_option("voice-call-input") { + if self.voice_call_input != Config::get_option("voice-call-input") { crate::audio_service::set_voice_call_input_device( Some(Config::get_option("voice-call-input")), true, @@ -455,16 +468,29 @@ async fn handle(data: Data, stream: &mut Connection) { allow_err!(stream.send(&Data::Socks(Config::get_socks())).await); } Some(data) => { + let _nat = CheckTestNatType::new(); if data.proxy.is_empty() { Config::set_socks(None); } else { Config::set_socks(Some(data)); } - crate::common::test_nat_type(); RendezvousMediator::restart(); log::info!("socks updated"); } }, + Data::SocksWs(s) => match s { + None => { + allow_err!( + stream + .send(&Data::SocksWs(Some(Box::new(( + Config::get_socks(), + Config::get_option(OPTION_ALLOW_WEBSOCKET) + ))))) + .await + ); + } + _ => {} + }, #[cfg(feature = "flutter")] Data::VideoConnCount(None) => { let n = crate::server::AUTHED_CONNS @@ -501,7 +527,8 @@ async fn handle(data: Data, stream: &mut Connection) { None }; } else if name == "hide_cm" { - value = if crate::hbbs_http::sync::is_pro() || crate::common::is_custom_client() { + value = if crate::hbbs_http::sync::is_pro() || crate::common::is_custom_client() + { Some(hbb_common::password_security::hide_cm().to_string()) } else { None @@ -544,6 +571,7 @@ async fn handle(data: Data, stream: &mut Connection) { } Some(value) => { let _chk = CheckIfRestart::new(); + let _nat = CheckTestNatType::new(); if let Some(v) = value.get("privacy-mode-impl-key") { crate::privacy_mode::switch(v); } @@ -1113,6 +1141,7 @@ pub fn set_option(key: &str, value: &str) { #[tokio::main(flavor = "current_thread")] pub async fn set_options(value: HashMap) -> ResultType<()> { + let _nat = CheckTestNatType::new(); if let Ok(mut c) = connect(1000, "").await { c.send(&Data::Options(Some(value.clone()))).await?; // do not put below before connect, because we need to check should_exit @@ -1170,6 +1199,7 @@ pub async fn get_socks() -> Option { #[tokio::main(flavor = "current_thread")] pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> { + let _nat = CheckTestNatType::new(); Config::set_socks(if value.proxy.is_empty() { None } else { @@ -1182,6 +1212,29 @@ pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> { Ok(()) } +async fn get_socks_ws_(ms_timeout: u64) -> ResultType<(Option, String)> { + let mut c = connect(ms_timeout, "").await?; + c.send(&Data::SocksWs(None)).await?; + if let Some(Data::SocksWs(Some(value))) = c.next_timeout(ms_timeout).await? { + Config::set_socks(value.0.clone()); + Config::set_option(OPTION_ALLOW_WEBSOCKET.to_string(), value.1.clone()); + Ok(*value) + } else { + Ok(( + Config::get_socks(), + Config::get_option(OPTION_ALLOW_WEBSOCKET), + )) + } +} + +#[tokio::main(flavor = "current_thread")] +pub async fn get_socks_ws() -> (Option, String) { + get_socks_ws_(1_000).await.unwrap_or(( + Config::get_socks(), + Config::get_option(OPTION_ALLOW_WEBSOCKET), + )) +} + pub fn get_proxy_status() -> bool { Config::get_socks().is_some() } diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 3469a580157..b1a3193e633 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index d6ac708d452..dbf4f2422c1 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 4e52f1f977d..8f6b4536b3c 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 78e5577ca12..8609332a259 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 86cefbdf6a7..8842d2b9b5f 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", "下载失败,您可以重试或者点击\"下载\"按钮,从发布网址下载,并手动升级。"), ("Auto update", ""), ("update-failed-check-msi-tip", "安装方式检测失败。请点击\"下载\"按钮,从发布网址下载,并手动升级。"), + ("websocket_tip", "使用 WebSocket 时,仅支持中继连接。"), + ("Use WebSocket", "使用 WebSocket"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 9422a0a7c08..3354ba0c7e8 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 90029b99e54..d5f8abc1e0f 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 3b683dacf1e..46bd4c1be14 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", "Download fehlgeschlagen. Sie können es erneut versuchen oder auf die Schaltfläche \"Herunterladen\" klicken, um von der Versionsseite herunterzuladen und manuell zu aktualisieren."), ("Auto update", "Automatisch aktualisieren"), ("update-failed-check-msi-tip", "Prüfung der Installationsmethode fehlgeschlagen. Bitte klicken Sie auf die Schaltfläche \"Herunterladen\", um von der Versionsseite herunterzuladen und manuell zu aktualisieren."), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index d58a80ae581..01043a7ef0c 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 92b9697fbb2..d0ee84239e6 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -257,6 +257,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("screenshot-action-tip", "Please select how to continue with the screenshot."), ("{}-to-update-tip", "{} will close now and install the new version."), ("download-new-version-failed-tip", "Download failed. You can try again or click the \"Download\" button to download from the release page and upgrade manually."), - ("update-failed-check-msi-tip", "Installation method check failed. Please click the \"Download\" button to download from the release page and upgrade manually.") + ("update-failed-check-msi-tip", "Installation method check failed. Please click the \"Download\" button to download from the release page and upgrade manually."), + ("websocket_tip", "When using WebSocket, only relay connections are supported."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 8070f4d57ae..e6e151e5e91 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index a109a15e9a9..27c4847f3a7 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index e40fd32ead4..28987fbe357 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index d01796d38a6..01e1068dadd 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index f8df1f5df5f..bb43dad5400 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 603c150d580..21668b7fb06 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ge.rs b/src/lang/ge.rs index ce0ab2287b3..0d9f6853938 100644 --- a/src/lang/ge.rs +++ b/src/lang/ge.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 3448b50c8df..37280449c59 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index ab4df6eec85..8b782873cee 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 8ca0cdaca6a..ee8f53734c5 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 54028649472..ee796e42b27 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 865b10b2ba2..0a10f658a8c 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", "Download non riuscito.\nÈ possibile riprovare o selezionare 'Download' per scaricare e aggiornarlo manualmente."), ("Auto update", "Aggiornamento automatico"), ("update-failed-check-msi-tip", "Controllo metodo installazione non riuscito.\nSeleziona 'Download' per scaricare il programma e aggiornarlo manualmente."), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index b8ff5687c1b..42eda1c6893 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 2c1e4fe43f2..0dded1ff2df 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", "새 버전 다운로드에 실패했습니다"), ("Auto update", "자동 업데이트"), ("update-failed-check-msi-tip", "업데이트에 실패했습니다. .msi 설치 파일을 확인하세요."), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 45323affc50..a68f19c63dd 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index adf0cccd56b..7fbf6eea4c4 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index eaacf762116..8057ac862f0 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index af2355e2cc4..07ed5a45b75 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 82f980d98cf..608fabbde31 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", "Fout bij het downloaden. Je kunt het opnieuw proberen of op de knop Downloaden klikken om de applicatie van de officiële website te downloaden en handmatig bij te werken."), ("Auto update", "Automatisch updaten"), ("update-failed-check-msi-tip", "Kan de installatiemethode niet bepalen. Klik op “Downloaden” om de applicatie van de officiële website te downloaden en handmatig bij te werken."), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 3cb3f24b22e..6ca59e54b76 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", "Pobieranie nie powiodło się. Możesz spróbować ponownie lub kliknąć przycisk \"Pobierz\", aby pobrać ze strony programu i uaktualnić ręcznie."), ("Auto update", "Automatyczna aktualizacja"), ("update-failed-check-msi-tip", "Sprawdzenie metody instalacji nie powiodło się. Kliknij przycisk \"Pobierz\", aby pobrać ze strony wydania i uaktualnić ręcznie."), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 67ad3e876fa..9f8fae3b879 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index cc37de1d5d3..e6d94db5600 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 18111c6b3e5..a0cc49c66b7 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 90e4cde98b8..35e387d98a4 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", "Ошибка загрузки. Можно повторить попытку или нажать кнопку \"Скачать\", чтобы скачать приложение с официального сайта и обновить вручную."), ("Auto update", "Автоматическое обновление"), ("update-failed-check-msi-tip", "Невозможно определить метод установки. Нажмите кнопку \"Скачать\", чтобы скачать приложение с официального сайта и обновить его вручную."), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sc.rs b/src/lang/sc.rs index c0372896b2b..10ea9c52b96 100644 --- a/src/lang/sc.rs +++ b/src/lang/sc.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 9ca482d2414..97b21eeec48 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index fffa2283c61..206fec074b4 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 6c318dbc35c..834098067d7 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 241be0419b9..79f0f46bc15 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 523e4cd41be..a06dacc39c6 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ta.rs b/src/lang/ta.rs index 1ba56301b4c..37303d2dfce 100644 --- a/src/lang/ta.rs +++ b/src/lang/ta.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 1e94b8ce853..e956f8ab518 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 6e9676b4e95..e8185a0c1da 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 1d63e0ba227..0eabb55a89d 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 87144d79723..c10c132ee3c 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index dcc06684ad5..34b77415e95 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 8ec202f05ff..0e98344165f 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -694,5 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("download-new-version-failed-tip", ""), ("Auto update", ""), ("update-failed-check-msi-tip", ""), + ("websocket_tip", ""), + ("Use WebSocket", ""), ].iter().cloned().collect(); } diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index d53fd0e05c0..b8f3de4080c 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -12,7 +12,9 @@ use uuid::Uuid; use hbb_common::{ allow_err, anyhow::{self, bail}, - config::{self, keys::*, option2bool, Config, CONNECT_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT}, + config::{ + self, keys::*, option2bool, use_ws, Config, CONNECT_TIMEOUT, REG_INTERVAL, RENDEZVOUS_PORT, + }, futures::future::join_all, log, protobuf::Message as _, @@ -139,6 +141,7 @@ impl RendezvousMediator { pub async fn start_udp(server: ServerPtr, host: String) -> ResultType<()> { let host = check_port(&host, RENDEZVOUS_PORT); + log::info!("start udp: {host}"); let (mut socket, mut addr) = socket_client::new_udp_for(&host, CONNECT_TIMEOUT).await?; let mut rz = Self { addr: addr.clone(), @@ -323,6 +326,7 @@ impl RendezvousMediator { pub async fn start_tcp(server: ServerPtr, host: String) -> ResultType<()> { let host = check_port(&host, RENDEZVOUS_PORT); + log::info!("start tcp: {}", hbb_common::websocket::check_ws(&host)); let mut conn = connect_tcp(host.clone(), CONNECT_TIMEOUT).await?; let key = crate::get_key(true).await; crate::secure_tcp(&mut conn, &key).await?; @@ -336,7 +340,7 @@ impl RendezvousMediator { let mut last_register_sent: Option = None; let mut last_recv_msg = Instant::now(); // we won't support connecting to multiple rendzvous servers any more, so we can use a global variable here. - Config::set_host_key_confirmed(&host, false); + Config::set_host_key_confirmed(&rz.host_prefix, false); loop { let mut update_latency = || { let latency = last_register_sent @@ -350,6 +354,8 @@ impl RendezvousMediator { last_recv_msg = Instant::now(); let bytes = res.ok_or_else(|| anyhow::anyhow!("Rendezvous connection is reset by the peer"))??; if bytes.is_empty() { + // After fixing frequent register_pk, for websocket, nginx need to set proxy_read_timeout to more than 60 seconds, eg: 120s + // https://serverfault.com/questions/1060525/why-is-my-websocket-connection-gets-closed-in-60-seconds conn.send_bytes(bytes::Bytes::new()).await?; continue; // heartbeat } @@ -365,7 +371,7 @@ impl RendezvousMediator { bail!("Rendezvous connection is timeout"); } if (!Config::get_key_confirmed() || - !Config::get_host_key_confirmed(&host)) && + !Config::get_host_key_confirmed(&rz.host_prefix)) && last_register_sent.map(|x| x.elapsed().as_millis() as i64).unwrap_or(REG_INTERVAL) >= REG_INTERVAL { rz.register_pk(Sink::Stream(&mut conn)).await?; last_register_sent = Some(Instant::now()); @@ -380,7 +386,8 @@ impl RendezvousMediator { log::info!("start rendezvous mediator of {}", host); //If the investment agent type is http or https, then tcp forwarding is enabled. if (cfg!(debug_assertions) && option_env!("TEST_TCP").is_some()) - || Config::is_proxy() + || Config::is_proxy() + || use_ws() || get_builtin_option(config::keys::OPTION_DISABLE_UDP) == "Y" { Self::start_tcp(server, host).await @@ -449,7 +456,12 @@ impl RendezvousMediator { async fn handle_intranet(&self, fla: FetchLocalAddr, server: ServerPtr) -> ResultType<()> { let relay_server = self.get_relay_server(fla.relay_server.clone()); // nat64, go relay directly, because current hbbs will crash if demangle ipv6 address - if is_ipv4(&self.addr) && !config::is_disable_tcp_listen() && !Config::is_proxy() { + // websocket, go relay directly + if is_ipv4(&self.addr) + && !config::is_disable_tcp_listen() + && !Config::is_proxy() + && !use_ws() + { if let Err(err) = self .handle_intranet_(fla.clone(), server.clone(), relay_server.clone()) .await @@ -501,9 +513,12 @@ impl RendezvousMediator { async fn handle_punch_hole(&self, ph: PunchHole, server: ServerPtr) -> ResultType<()> { let relay_server = self.get_relay_server(ph.relay_server); + // for ensure, websocket go relay directly if ph.nat_type.enum_value() == Ok(NatType::SYMMETRIC) || Config::get_nat_type() == NatType::SYMMETRIC as i32 || config::is_disable_tcp_listen() + || use_ws() + || Config::is_proxy() { let uuid = Uuid::new_v4().to_string(); return self diff --git a/src/ui/index.tis b/src/ui/index.tis index eb76c675455..fd505854462 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -325,6 +325,7 @@ class MyIdMenu: Reactor.Component {
  • {translate('ID/Relay Server')}
  • {translate('IP Whitelisting')}
  • {translate('Socks5 Proxy')}
  • + { false &&
  • {svg_checkmark}{translate('Use WebSocket')}
  • }
  • {svg_checkmark}{translate("Enable service")}
  • {is_win && handler.is_installed() ? : ""} diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 1aa4130deb0..b1ce626431a 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -429,7 +429,10 @@ pub fn set_option(key: String, value: String) { ipc::set_options(options.clone()).ok(); } #[cfg(any(target_os = "android", target_os = "ios"))] - Config::set_option(key, value); + { + let _nat = crate::CheckTestNatType::new(); + Config::set_option(key, value); + } } #[inline] @@ -479,12 +482,12 @@ pub fn set_socks(proxy: String, username: String, password: String) { ipc::set_socks(socks).ok(); #[cfg(target_os = "android")] { + let _nat = crate::CheckTestNatType::new(); if socks.proxy.is_empty() { Config::set_socks(None); } else { Config::set_socks(Some(socks)); } - crate::common::test_nat_type(); crate::RendezvousMediator::restart(); log::info!("socks updated"); }