diff --git a/crates/wzp-client/src/handshake.rs b/crates/wzp-client/src/handshake.rs index dd14419..14180eb 100644 --- a/crates/wzp-client/src/handshake.rs +++ b/crates/wzp-client/src/handshake.rs @@ -101,12 +101,15 @@ pub async fn perform_handshake( .await .map_err(HandshakeError::Transport)?; - // 5. Wait for CallAnswer - let answer = transport - .recv_signal() - .await - .map_err(HandshakeError::Transport)? - .ok_or(HandshakeError::ConnectionClosed)?; + // 5. Wait for CallAnswer — 10s timeout guards against relay not responding. + let answer = tokio::time::timeout( + std::time::Duration::from_secs(10), + transport.recv_signal(), + ) + .await + .map_err(|_| HandshakeError::Transport(wzp_proto::TransportError::Timeout { ms: 10_000 }))? + .map_err(HandshakeError::Transport)? + .ok_or(HandshakeError::ConnectionClosed)?; let (callee_identity_pub, callee_ephemeral_pub, callee_signature, _chosen_profile) = match answer { diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 3481718..9161d10 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -331,7 +331,9 @@ joinVoiceBtn.addEventListener("click", async () => { const s = loadSettings(); if (!relay) { showToast("No relay configured"); return; } connectPending = true; - joinVoiceBtn.classList.add("hidden"); + const origText = joinVoiceBtn.textContent; + joinVoiceBtn.textContent = "Connecting…"; + (joinVoiceBtn as HTMLButtonElement).disabled = true; try { await invoke("connect", { relay: relay.address, @@ -344,9 +346,10 @@ joinVoiceBtn.addEventListener("click", async () => { } catch (e: any) { console.error("connect failed:", e); showToast(`Join failed: ${e}`); - joinVoiceBtn.classList.remove("hidden"); } finally { connectPending = false; + joinVoiceBtn.textContent = origText; + (joinVoiceBtn as HTMLButtonElement).disabled = false; } });