use super::RpcHandler; use crate::{nostr_handshake, peers}; use anyhow::Result; use nostr_sdk::FromBech32; impl RpcHandler { /// Discover nodes (presence-only — returns Nostr pubkeys + DIDs, no onion addresses). pub(super) async fn handle_handshake_discover(&self) -> Result { let identity_dir = self.config.data_dir.join("identity"); let nodes = nostr_handshake::discover_nodes( &identity_dir, &self.config.nostr_relays, self.config.nostr_tor_proxy.as_deref(), ) .await?; Ok(serde_json::json!({ "nodes": nodes })) } /// Send encrypted connection request to a peer's Nostr pubkey. /// Params: { recipient_nostr_pubkey } pub(super) async fn handle_handshake_connect( &self, params: Option, ) -> Result { let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?; // Accept either hex pubkey or npub1... bech32 format let recipient_raw = params .get("recipient_nostr_pubkey") .and_then(|v| v.as_str()) .ok_or_else(|| anyhow::anyhow!("Missing recipient_nostr_pubkey"))?; let recipient = if recipient_raw.starts_with("npub1") { nostr_sdk::PublicKey::from_bech32(recipient_raw) .map_err(|e| anyhow::anyhow!("Invalid npub: {}", e))? .to_hex() } else { recipient_raw.to_string() }; let recipient = recipient.as_str(); let (data, _) = self.state_manager.get_snapshot().await; let our_onion = data .server_info .tor_address .as_deref() .ok_or_else(|| anyhow::anyhow!("No Tor address available — is Tor running?"))?; let our_node_pubkey = &data.server_info.pubkey; let our_did = crate::identity::did_key_from_pubkey_hex(our_node_pubkey) .unwrap_or_default(); let our_version = &data.server_info.version; let our_name = data.server_info.name.as_deref(); let identity_dir = self.config.data_dir.join("identity"); nostr_handshake::send_connect_request( &identity_dir, recipient, our_onion, our_node_pubkey, &our_did, our_version, our_name, &self.config.nostr_relays, self.config.nostr_tor_proxy.as_deref(), ) .await?; Ok(serde_json::json!({ "ok": true, "sent_to": recipient })) } /// Poll for incoming encrypted handshake messages (connect requests/responses). /// Auto-adds peers and auto-responds to requests. pub(super) async fn handle_handshake_poll(&self) -> Result { let identity_dir = self.config.data_dir.join("identity"); let handshakes = nostr_handshake::poll_handshakes( &identity_dir, &self.config.nostr_relays, self.config.nostr_tor_proxy.as_deref(), None, // TODO: track last-seen timestamp to avoid re-processing ) .await?; let (data, _) = self.state_manager.get_snapshot().await; let mut added_peers = Vec::new(); for hs in &handshakes { let (onion, node_pubkey, name) = match &hs.message { nostr_handshake::HandshakeMessage::ConnectRequest { onion, node_pubkey, name, .. } => { // Auto-respond with our details if let Some(our_onion) = data.server_info.tor_address.as_deref() { let our_did = crate::identity::did_key_from_pubkey_hex( &data.server_info.pubkey, ) .unwrap_or_default(); let _ = nostr_handshake::send_connect_response( &identity_dir, &hs.from_nostr_pubkey, our_onion, &data.server_info.pubkey, &our_did, &data.server_info.version, data.server_info.name.as_deref(), &self.config.nostr_relays, self.config.nostr_tor_proxy.as_deref(), ) .await; } (onion.clone(), node_pubkey.clone(), name.clone()) } nostr_handshake::HandshakeMessage::ConnectResponse { onion, node_pubkey, name, .. } => (onion.clone(), node_pubkey.clone(), name.clone()), }; // Auto-add as peer let peer = peers::KnownPeer { onion, pubkey: node_pubkey.clone(), name, added_at: Some(chrono::Utc::now().to_rfc3339()), }; let _ = peers::add_peer(&self.config.data_dir, peer).await; added_peers.push(node_pubkey); } let serialized: Vec = handshakes .iter() .map(|hs| { serde_json::json!({ "from_nostr_pubkey": hs.from_nostr_pubkey, "from_nostr_npub": hs.from_nostr_npub, "message": hs.message, "timestamp": hs.timestamp, }) }) .collect(); Ok(serde_json::json!({ "handshakes": serialized, "added_peers": added_peers, })) } }