fix(handshake): peer requests actually arrive — background poll, unified relays, real send errors
Three silent failure modes killed nostr peer requests (analyzed from the live code 2026-07-22): (1) nothing ever polled the relays except the manual Federation Poll button — a 5-minute background poll now fetches inbound requests and nudges the websocket when something lands (the handler's discoverability gate still applies); (2) handshake send/poll used only the two hardcoded config relays (one defunct) and ignored the user-managed relay list — every nostr op now uses the merged set; (3) publish errors were swallowed (let _ =) so 'ok' was reported even when no relay accepted the event — now a delivery failure is an error the UI shows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
770e3386f4
commit
6a3b46b5a9
@@ -86,7 +86,7 @@ impl RpcHandler {
|
||||
let did = crate::identity::did_key_from_pubkey_hex(&data.server_info.pubkey)
|
||||
.unwrap_or_default();
|
||||
let version = data.server_info.version.clone();
|
||||
let relays = self.config.nostr_relays.clone();
|
||||
let relays = self.handshake_relays().await;
|
||||
let tor_proxy = self.config.nostr_tor_proxy.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = nostr_handshake::publish_presence(
|
||||
@@ -106,6 +106,17 @@ impl RpcHandler {
|
||||
Ok(serde_json::json!({ "enabled": enabled }))
|
||||
}
|
||||
|
||||
/// The relay set every handshake operation uses: the user-managed relay
|
||||
/// list (Settings → Relays, `nostr_relays.json`) merged with the config
|
||||
/// defaults. Before 2026-07-22 handshake send/poll used ONLY the two
|
||||
/// hardcoded config relays (one of which is defunct) and ignored user
|
||||
/// relay edits entirely — so a sender publishing where the receiver
|
||||
/// never read was a routine, silent way for peer requests to vanish.
|
||||
pub(super) async fn handshake_relays(&self) -> Vec<String> {
|
||||
crate::nostr_relays::merged_relay_list(&self.config.data_dir, &self.config.nostr_relays)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Discover discoverable nodes via Nostr presence events.
|
||||
/// Returns (nostr_pubkey, npub, DID, version) only — never an onion.
|
||||
pub(super) async fn handle_handshake_discover(&self) -> Result<serde_json::Value> {
|
||||
@@ -113,9 +124,10 @@ impl RpcHandler {
|
||||
// to query relays as long as the user is actively browsing — they're
|
||||
// an anonymous observer of presence events, not publishing anything.
|
||||
let identity_dir = self.config.data_dir.join("identity");
|
||||
let relays = self.handshake_relays().await;
|
||||
let nodes = nostr_handshake::discover_nodes(
|
||||
&identity_dir,
|
||||
&self.config.nostr_relays,
|
||||
&relays,
|
||||
self.config.nostr_tor_proxy.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
@@ -161,7 +173,7 @@ impl RpcHandler {
|
||||
our_version,
|
||||
our_name,
|
||||
message,
|
||||
&self.config.nostr_relays,
|
||||
&self.handshake_relays().await,
|
||||
self.config.nostr_tor_proxy.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
@@ -191,6 +203,40 @@ impl RpcHandler {
|
||||
/// - `PeerReject` → mark matching outbound row as `Rejected`
|
||||
///
|
||||
/// Never auto-adds peers, never auto-responds, never sends our onion.
|
||||
/// Background relay poll (2026-07-22): before this, `handshake.poll` ran
|
||||
/// ONLY when a user opened Federation and pressed the Poll button — a
|
||||
/// peer request sat on the relay until the target's operator happened to
|
||||
/// click, i.e. for most nodes forever ("requests never arrive"). Runs the
|
||||
/// same poll+dispatch as the RPC (the disabled gate inside still applies)
|
||||
/// and nudges the websocket revision when anything new lands so open UIs
|
||||
/// refresh immediately.
|
||||
pub async fn background_handshake_poll(self: &std::sync::Arc<Self>) {
|
||||
match self.handle_handshake_poll().await {
|
||||
Ok(res) => {
|
||||
let new = res
|
||||
.get("new_requests")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|a| a.len())
|
||||
.unwrap_or(0);
|
||||
let applied = res
|
||||
.get("applied_invites")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|a| a.len())
|
||||
.unwrap_or(0);
|
||||
if new > 0 || applied > 0 {
|
||||
tracing::info!(
|
||||
new_requests = new,
|
||||
applied_invites = applied,
|
||||
"handshake poll: inbound peer activity"
|
||||
);
|
||||
let (data, _) = self.state_manager.get_snapshot().await;
|
||||
self.state_manager.update_data(data).await;
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::debug!("background handshake poll failed: {e:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn handle_handshake_poll(&self) -> Result<serde_json::Value> {
|
||||
// Runtime gate: if the user hasn't enabled discoverability, don't
|
||||
// touch the relays. The poll endpoint is a hard no-op until they
|
||||
@@ -207,9 +253,10 @@ impl RpcHandler {
|
||||
}));
|
||||
}
|
||||
let identity_dir = self.config.data_dir.join("identity");
|
||||
let relays = self.handshake_relays().await;
|
||||
let handshakes = nostr_handshake::poll_handshakes(
|
||||
&identity_dir,
|
||||
&self.config.nostr_relays,
|
||||
&relays,
|
||||
self.config.nostr_tor_proxy.as_deref(),
|
||||
None,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user