diff --git a/src/signer/nip46_client.rs b/src/signer/nip46_client.rs index f9a9fa8..18b48fc 100644 --- a/src/signer/nip46_client.rs +++ b/src/signer/nip46_client.rs @@ -1450,58 +1450,22 @@ impl Nip46ClientSigner { .to_bech32() .map_err(|e| format!("Could not encode identity npub: {e}"))?; - // Best-effort: look up the identity's kind-0 metadata so the profile - // row carries the real display name / picture / nip05 instead of a - // bare npub or the generic pairing label. Relay hiccups fall back to - // the pairing label — the connection must not depend on them. - let relays_for_meta = { - let app = self.app.lock().await; - crate::relays::enabled_urls(&app.settings) - }; - let meta_npub = identity; - let metadata = match tokio::time::timeout( - Duration::from_secs(3), - tokio::task::spawn_blocking(move || { - profiles::fetch_profile_metadata(&meta_npub, &relays_for_meta) - }), - ) - .await - { - Ok(joined) => joined.ok().flatten(), - Err(_elapsed) => None, - }; - let display_label = metadata - .as_ref() - .and_then(|m| m.display_name.as_deref().or(m.name.as_deref())) - .map(str::trim) - .filter(|name| !name.is_empty()) - .map(str::to_string) - .unwrap_or(label); + // NOTE: kind-0 metadata (display name / picture / nip05) is fetched + // AFTER the session flips to Connected, as a background enrichment + // (see below). A slow or silent relay must never delay the connection + // becoming usable — this used to block here and made pairing look + // dead in the UI for many seconds after the signer had already + // approved. + let pairing_label = label; + let display_label = pairing_label.clone(); // Persist: profile row for the identity, connection + secret store // re-keyed under it. Refuses (fails the handshake) if the identity // collides with a local profile. { let mut app = self.app.lock().await; - profiles::store_remote_profile(&mut app.vault, &identity_npub, display_label) + profiles::store_remote_profile(&mut app.vault, &identity_npub, display_label.clone()) .map_err(|e| e.message().to_string())?; - // Carry the looked-up metadata (picture / nip05) onto the row so - // the profile shows its real identity, not just a name. - if let Some(meta) = &metadata { - if let Some(row) = app - .vault - .profiles - .iter_mut() - .find(|p| p.public_key == identity_npub) - { - if row.picture.is_none() { - row.picture = meta.picture.clone(); - } - if row.nip05.is_none() { - row.nip05 = meta.nip05.clone(); - } - } - } // Re-key the stored secret under the identity npub so the // connection row, VaultRef, and secret store all agree. let connect_ref = crate::signer::VaultRef::from_connection(&connection); @@ -1541,9 +1505,71 @@ impl Nip46ClientSigner { // so later teardown (disconnect) deletes the secret under the ref it // was actually stored at. if let Some(conn) = inner.connection.as_mut() { - conn.profile_npub = Some(identity_npub); + conn.profile_npub = Some(identity_npub.clone()); } inner.phase = Nip46Phase::Connected; + drop(inner); + + // Background enrichment (post-Connected by design): look up the + // identity's kind-0 metadata so the profile row carries the real + // display name / picture / nip05 instead of the generic pairing + // label. A slow or silent relay must never gate the session — the + // UI polls status and vault, so the row fills in a moment later. + { + let app = self.app.clone(); + tokio::spawn(async move { + let relays_for_meta = { + let app = app.lock().await; + crate::relays::enabled_urls(&app.settings) + }; + let meta = match tokio::time::timeout( + Duration::from_secs(10), + tokio::task::spawn_blocking(move || { + profiles::fetch_profile_metadata(&identity, &relays_for_meta) + }), + ) + .await + { + Ok(Ok(Some(meta))) => meta, + _ => return, + }; + let mut app = app.lock().await; + let mut changed = false; + if let Some(row) = app + .vault + .profiles + .iter_mut() + .find(|p| p.public_key == identity_npub) + { + if row.picture.is_none() { + row.picture = meta.picture.clone(); + changed = true; + } + if row.nip05.is_none() { + row.nip05 = meta.nip05.clone(); + changed = true; + } + // Upgrade the generic pairing label to the real display + // name only while the row still carries the label we set + // during pairing — a user rename always wins. + let real_name = meta + .display_name + .as_deref() + .or(meta.name.as_deref()) + .map(str::trim) + .filter(|name| !name.is_empty()); + if let Some(name) = real_name { + if row.label == pairing_label { + row.label = name.to_string(); + changed = true; + } + } + } + if changed { + let _ = app.save_vault(); + } + }); + } Ok(()) }