feat: relay ping with RTT display, fix dead_code warning
Some checks failed
Build Release Binaries / build-amd64 (push) Has been cancelled

- New ping_relay Tauri command: QUIC connect with 3s timeout, returns RTT ms
- Relay status shown next to input field: "42ms" (green) or "offline" (red)
- Auto-pings on app startup and debounced on relay input change
- Fix SyncWrapper dead_code warning with #[allow(dead_code)]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-06 12:41:28 +04:00
parent ed272d29f8
commit dddf5d2e2d
5 changed files with 78 additions and 1 deletions

View File

@@ -14,7 +14,10 @@
<p class="subtitle">Encrypted Voice</p> <p class="subtitle">Encrypted Voice</p>
<div class="form"> <div class="form">
<label>Relay <label>Relay
<input id="relay" type="text" value="193.180.213.68:4433" /> <div class="relay-row">
<input id="relay" type="text" value="193.180.213.68:4433" />
<span id="relay-status" class="relay-status"></span>
</div>
</label> </label>
<label>Room <label>Room
<input id="room" type="text" value="android" /> <input id="room" type="text" value="android" />

View File

@@ -18,6 +18,7 @@ const FRAME_SAMPLES: usize = 960;
/// Wrapper to make non-Sync audio handles safe to store in shared state. /// Wrapper to make non-Sync audio handles safe to store in shared state.
/// The audio handle is only accessed from the thread that created it (drop), /// The audio handle is only accessed from the thread that created it (drop),
/// never shared across threads — Sync is safe. /// never shared across threads — Sync is safe.
#[allow(dead_code)]
struct SyncWrapper(Box<dyn std::any::Any + Send>); struct SyncWrapper(Box<dyn std::any::Any + Send>);
unsafe impl Sync for SyncWrapper {} unsafe impl Sync for SyncWrapper {}

View File

@@ -37,6 +37,32 @@ struct AppState {
engine: Mutex<Option<CallEngine>>, engine: Mutex<Option<CallEngine>>,
} }
/// Ping a relay to check if it's online and measure RTT.
#[tauri::command]
async fn ping_relay(relay: String) -> Result<u32, String> {
let addr: std::net::SocketAddr = relay.parse().map_err(|e| format!("bad address: {e}"))?;
let _ = rustls::crypto::ring::default_provider().install_default();
let bind: std::net::SocketAddr = "0.0.0.0:0".parse().unwrap();
let endpoint = wzp_transport::create_endpoint(bind, None).map_err(|e| format!("{e}"))?;
let client_cfg = wzp_transport::client_config();
let start = std::time::Instant::now();
match tokio::time::timeout(
std::time::Duration::from_secs(3),
wzp_transport::connect(&endpoint, addr, "ping", client_cfg),
)
.await
{
Ok(Ok(conn)) => {
let rtt_ms = start.elapsed().as_millis() as u32;
conn.close(0u32.into(), b"ping");
Ok(rtt_ms)
}
Ok(Err(e)) => Err(format!("{e}")),
Err(_) => Err("timeout (3s)".into()),
}
}
/// Read fingerprint from ~/.wzp/identity without connecting. /// Read fingerprint from ~/.wzp/identity without connecting.
#[tauri::command] #[tauri::command]
fn get_identity() -> Result<String, String> { fn get_identity() -> Result<String, String> {
@@ -175,6 +201,7 @@ fn main() {
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.manage(state) .manage(state)
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
ping_relay,
get_identity, get_identity,
connect, connect,
disconnect, disconnect,

View File

@@ -126,6 +126,33 @@ function renderRecentRooms(rooms: RecentRoom[]) {
applySettings(); applySettings();
// ── Relay ping ──
const relayStatusEl = document.getElementById("relay-status")!;
let pingDebounce: number | null = null;
async function pingRelay(address: string) {
relayStatusEl.textContent = "...";
relayStatusEl.className = "relay-status pinging";
try {
const rtt: number = await invoke("ping_relay", { relay: address });
relayStatusEl.textContent = `${rtt}ms`;
relayStatusEl.className = "relay-status online";
connectBtn.disabled = false;
} catch {
relayStatusEl.textContent = "offline";
relayStatusEl.className = "relay-status offline";
}
}
// Ping on load and when relay input changes
relayInput.addEventListener("input", () => {
if (pingDebounce) clearTimeout(pingDebounce);
pingDebounce = window.setTimeout(() => pingRelay(relayInput.value), 500);
});
// Initial ping
setTimeout(() => pingRelay(relayInput.value), 300);
// ── Load fingerprint at startup (no connection needed) ── // ── Load fingerprint at startup (no connection needed) ──
(async () => { (async () => {
try { try {

View File

@@ -89,6 +89,25 @@ body {
border-color: var(--accent); border-color: var(--accent);
} }
.relay-row {
display: flex;
align-items: center;
gap: 8px;
}
.relay-row input { flex: 1; }
.relay-status {
font-size: 11px;
white-space: nowrap;
min-width: 50px;
text-align: right;
}
.relay-status.online { color: var(--green); }
.relay-status.offline { color: var(--red); }
.relay-status.pinging { color: var(--text-dim); }
.form-row { .form-row {
display: flex; display: flex;
gap: 16px; gap: 16px;