From c0750fb50c78740bed42bd82ff99d78af6995263 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Thu, 15 Oct 2026 19:52:19 +0800 Subject: [PATCH] fix(client): report one error when both WebRTC race legs fail (#16563) Both legs of race_transports_prefer_webrtc send their own punch request to the same rendezvous server for the same peer, so a refusal there (offline, unknown ID, key mismatch, a server-supplied other_failure) fails both with the same text. Joining them as "WebRTC failed: X; fallback failed: X" showed it twice and broke the UI's whole-string matching: the offline auto-retry needs exactly "Remote desktop is offline", the message box translates the text as one string, and check_if_retry hides the retry button for any text containing "failed". Return the error of the leg that failed last, as select_ok did before the race existed, and log the other one once. Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R Co-authored-by: Claude Opus 5.5 --- src/client.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 9e8202483..a62f8717a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -267,7 +267,13 @@ async fn race_transports_prefer_webrtc<'a, T: 'a>( } } match others_err.take() { - Some(oe) => bail!("WebRTC failed: {}; fallback failed: {}", e, oe), + // Report one error, as `select_ok` does: the UI matches and translates + // the text whole (e.g. the offline auto-retry), and both legs often + // carry the same rendezvous refusal anyway. + Some(oe) => { + log::warn!("fallback leg also failed: {}", oe); + return Err(e); + } None if others_fut.is_none() => bail!("WebRTC failed: {}", e), None => webrtc_err = Some(e), } @@ -308,7 +314,10 @@ async fn race_transports_prefer_webrtc<'a, T: 'a>( // it before failing the connection. Some(we) => match held.take() { Some(conn) => return Ok(conn), - None => bail!("WebRTC failed: {}; fallback failed: {}", we, e), + None => { + log::warn!("WebRTC leg also failed: {}", we); + return Err(e); + } }, None if webrtc_fut.is_none() => match held.take() { Some(conn) => return Ok(conn), @@ -5709,6 +5718,23 @@ mod webrtc_race_tests { assert_eq!(got, "ipv6"); assert!(start.elapsed() < Duration::from_secs(5)); } + + // Both legs get the same rendezvous refusal; the UI needs the text verbatim, e.g. the + // offline auto-retry matches it whole. Either leg may fail last. + #[tokio::test] + async fn same_failure_on_both_legs_reported_once() { + for (webrtc_ms, fallback_ms) in [(10, 20), (20, 10)] { + let err = race_transports_prefer_webrtc( + err_after(webrtc_ms, "Remote desktop is offline"), + vec![err_after(fallback_ms, "Remote desktop is offline")], + 60_000, + NOT_P2P, + ) + .await + .unwrap_err(); + assert_eq!(err.to_string(), "Remote desktop is offline"); + } + } } #[cfg(test)]