From 847699bf66939aec4e432b7dcf5a909914fd1234 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Sun, 12 Apr 2026 09:13:35 +0400 Subject: [PATCH] fix(ui): pre-flight ping + cancel button for register MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two UX issues when the selected relay is unreachable (e.g. user switched from WiFi to LTE and the LAN relay is gone): 1. Pressing Register blocked the UI for ~30s while the QUIC connect timed out against a dead host. No way to abort. 2. No feedback that the relay was unreachable — just a long wait followed by a cryptic error. Fix: **Pre-flight ping**: before attempting the full register flow, run `ping_relay` (existing Tauri command, 3s QUIC handshake timeout). If it fails, immediately show "Server unavailable: " and re-enable the Register button. No blocking, no wasted time. If it succeeds, proceed to register_signal. **Cancel button**: during the register_signal await, the Register button becomes "Cancel". Tapping it calls `deregister` which closes the in-flight transport and makes the connect fail immediately, breaking the await. The button goes back to "Register on Relay" with a "Registration cancelled" message. Flow: [Register] → "Checking..." (disabled, 3s ping) → ping fails → "Server unavailable" (re-enabled) ping ok → "Cancel" (enabled, register in flight) → user taps Cancel → "Registration cancelled" (re-enabled) register succeeds → registered panel shown register fails → error shown (re-enabled) Co-Authored-By: Claude Opus 4.6 (1M context) --- desktop/src/main.ts | 49 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 5f52d71..4897f70 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -1450,11 +1450,51 @@ clearHistoryBtn.addEventListener("click", async () => { } catch (e) { console.error(e); } }); +// Track whether a registration is in flight so the same button +// can toggle between "Register" and "Cancel". The cancel path +// calls deregister which closes the transport and makes the +// in-flight connect fail, breaking the await cleanly. +let registerInFlight = false; + registerBtn.addEventListener("click", async () => { + // ── Cancel path: user tapped the button while registration + // is in flight (it says "Cancel") → tear down the attempt + // so we don't block for 30s on an unreachable relay. + if (registerInFlight) { + registerInFlight = false; + try { await invoke("deregister"); } catch {} + registerBtn.textContent = "Register on Relay"; + registerBtn.disabled = false; + connectError.textContent = "Registration cancelled"; + return; + } + const relay = getSelectedRelay(); if (!relay) { connectError.textContent = "No relay selected"; return; } + connectError.textContent = ""; + + // ── Pre-flight ping: quick 3s QUIC handshake to check if + // the relay is reachable BEFORE committing to the full + // register flow (which takes ~10s to time out against a dead + // host). If the ping fails, show "server unavailable" + // immediately without blocking. + registerBtn.textContent = "Checking..."; registerBtn.disabled = true; - registerBtn.textContent = "Registering..."; + try { + await invoke("ping_relay", { relay: relay.address }); + } catch (e: any) { + connectError.textContent = `Server unavailable: ${String(e)}`; + registerBtn.disabled = false; + registerBtn.textContent = "Register on Relay"; + return; + } + + // ── Register path: ping succeeded, proceed with the full + // registration. Show "Cancel" on the button so the user + // can bail if the relay goes unreachable mid-handshake. + registerInFlight = true; + registerBtn.disabled = false; + registerBtn.textContent = "Cancel"; try { const fp = await invoke("register_signal", { relay: relay.address }); registerBtn.classList.add("hidden"); @@ -1462,9 +1502,14 @@ registerBtn.addEventListener("click", async () => { callStatusText.textContent = `Your fingerprint: ${fp}`; refreshHistory(); } catch (e: any) { - connectError.textContent = String(e); + if (registerInFlight) { + // Real failure, not a user cancel + connectError.textContent = String(e); + } registerBtn.disabled = false; registerBtn.textContent = "Register on Relay"; + } finally { + registerInFlight = false; } });