fix(ui): pre-flight ping + cancel button for register
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:
<error>" 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) <noreply@anthropic.com>
This commit is contained in:
@@ -1450,11 +1450,51 @@ clearHistoryBtn.addEventListener("click", async () => {
|
|||||||
} catch (e) { console.error(e); }
|
} 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 () => {
|
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();
|
const relay = getSelectedRelay();
|
||||||
if (!relay) { connectError.textContent = "No relay selected"; return; }
|
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.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 {
|
try {
|
||||||
const fp = await invoke<string>("register_signal", { relay: relay.address });
|
const fp = await invoke<string>("register_signal", { relay: relay.address });
|
||||||
registerBtn.classList.add("hidden");
|
registerBtn.classList.add("hidden");
|
||||||
@@ -1462,9 +1502,14 @@ registerBtn.addEventListener("click", async () => {
|
|||||||
callStatusText.textContent = `Your fingerprint: ${fp}`;
|
callStatusText.textContent = `Your fingerprint: ${fp}`;
|
||||||
refreshHistory();
|
refreshHistory();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
if (registerInFlight) {
|
||||||
|
// Real failure, not a user cancel
|
||||||
connectError.textContent = String(e);
|
connectError.textContent = String(e);
|
||||||
|
}
|
||||||
registerBtn.disabled = false;
|
registerBtn.disabled = false;
|
||||||
registerBtn.textContent = "Register on Relay";
|
registerBtn.textContent = "Register on Relay";
|
||||||
|
} finally {
|
||||||
|
registerInFlight = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user