diff --git a/src/nostr/client.rs b/src/nostr/client.rs index 8d07fcb1..2c0519e6 100644 --- a/src/nostr/client.rs +++ b/src/nostr/client.rs @@ -554,10 +554,23 @@ async fn run_service(svc: Arc, wallet: Wallet) { svc.store.set_last_connected_at(unix_time()); svc.store.prune_processed(); + // Reflect the connection the moment we reach the loop instead of leaving the + // UI on "Connecting…" until the first heartbeat — by now catch-up has run, so + // a relay is typically already up. + svc.connected + .store(relays_connected(&client).await, Ordering::Relaxed); + let mut notifications = client.notifications(); - // Re-run TTL pruning periodically, not just at startup: a session that - // never restarts would otherwise let the processed-dedup store grow - // unbounded under fresh-keypair spam (the 30-day TTL never applied). + // Poll connection state on a SHORT, INDEPENDENT interval. This used to live in + // the `select!` behind a `sleep(30s)` that restarted on every notification, so + // the flag could lag the real relay state by 30s+ (or, under steady event + // flow, never update) — that's the "stuck on Connecting…" the mixnet gets + // blamed for, even though a relay handshake over Nym takes ~2s. An `interval` + // fires on its own schedule regardless of notifications; the heavier heartbeat + // work (persisting last-seen, TTL pruning) stays on a ~30s cadence. + let mut status_tick = tokio::time::interval(Duration::from_secs(2)); + status_tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + let mut last_heartbeat = unix_time(); let mut last_prune = unix_time(); loop { if svc.shutdown.load(Ordering::SeqCst) || !wallet.is_open() { @@ -576,15 +589,17 @@ async fn run_service(svc: Arc, wallet: Wallet) { Err(tokio::sync::broadcast::error::RecvError::Closed) => break, } } - _ = tokio::time::sleep(Duration::from_secs(30)) => { - // Heartbeat: persist last seen time, update connection state. - svc.store.set_last_connected_at(unix_time()); - let connected = client.relays().await.values() - .any(|r| r.status() == RelayStatus::Connected); - svc.connected.store(connected, Ordering::Relaxed); - if unix_time() - last_prune >= 3600 { - svc.store.prune_processed(); - last_prune = unix_time(); + _ = status_tick.tick() => { + svc.connected + .store(relays_connected(&client).await, Ordering::Relaxed); + let now = unix_time(); + if now - last_heartbeat >= 30 { + last_heartbeat = now; + svc.store.set_last_connected_at(now); + if now - last_prune >= 3600 { + svc.store.prune_processed(); + last_prune = now; + } } } } @@ -597,6 +612,15 @@ async fn run_service(svc: Arc, wallet: Wallet) { client.disconnect().await; } +/// True when at least one relay has completed its handshake. +async fn relays_connected(client: &Client) -> bool { + client + .relays() + .await + .values() + .any(|r| r.status() == RelayStatus::Connected) +} + /// Publish kind 10050 DM relay list and, for named identities, kind 0 metadata. async fn publish_identity(svc: &Arc, client: &Client) { let relays = svc.relays();