From 42f3f86f081aadab5042c80003ee67b8e3ce69ad Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Sun, 15 Feb 2026 13:49:30 -0500 Subject: [PATCH] feat(directory): update peer profile to include public key and improve placeholder handling in user directory --- src-tauri/src/node/mod.rs | 38 ++++++++++++ .../directory/UserDirectoryModal.tsx | 59 +++++++++++++++++-- src/stores/directory.ts | 8 ++- 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/node/mod.rs b/src-tauri/src/node/mod.rs index aeaf4b0..05736a8 100644 --- a/src-tauri/src/node/mod.rs +++ b/src-tauri/src/node/mod.rs @@ -730,8 +730,46 @@ pub async fn start( continue; } + // never expose relay infrastructure in the user directory + if Some(discovered_peer) == relay_peer { + continue; + } + log::info!("discovered peer {} via rendezvous", discovered_peer); + // cache a placeholder entry so global discovery is visible + // before we receive the peer's signed profile announcement + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + let discovered_peer_str = discovered_peer.to_string(); + let already_known = storage + .load_directory() + .ok() + .map(|d| d.contains_key(&discovered_peer_str)) + .unwrap_or(false); + + // add a lightweight placeholder if we have not learned this peer's profile yet + if !already_known { + let placeholder = DirectoryEntry { + peer_id: discovered_peer_str.clone(), + display_name: "discovered peer".to_string(), + bio: String::new(), + public_key: String::new(), + last_seen: now, + is_friend: false, + }; + let _ = storage.save_directory_entry(&placeholder); + + let _ = app_handle.emit("dusk-event", DuskEvent::ProfileReceived { + peer_id: placeholder.peer_id, + display_name: placeholder.display_name, + bio: placeholder.bio, + public_key: placeholder.public_key, + }); + } + // connect through the relay circuit so neither peer reveals their IP if let Some(ref relay_addr) = relay_multiaddr { let circuit_addr = relay_addr.clone() diff --git a/src/components/directory/UserDirectoryModal.tsx b/src/components/directory/UserDirectoryModal.tsx index fb174b7..f8cda4b 100644 --- a/src/components/directory/UserDirectoryModal.tsx +++ b/src/components/directory/UserDirectoryModal.tsx @@ -3,6 +3,7 @@ import { createSignal, createMemo, createEffect, + onCleanup, For, Show, } from "solid-js"; @@ -29,6 +30,7 @@ import { identity } from "../../stores/identity"; import { setActiveDM } from "../../stores/dms"; import { addDMConversation } from "../../stores/dms"; import * as tauri from "../../lib/tauri"; +import type { DirectoryEntry } from "../../lib/types"; interface UserDirectoryModalProps { isOpen: boolean; @@ -41,10 +43,18 @@ const UserDirectoryModal: Component = (props) => { const [searchQuery, setSearchQuery] = createSignal(""); const [activeTab, setActiveTab] = createSignal("all"); const [copiedId, setCopiedId] = createSignal(null); + const [isSearching, setIsSearching] = createSignal(false); + const [searchResults, setSearchResults] = createSignal( + null, + ); // reload directory from disk and trigger fresh discovery when modal opens createEffect(() => { if (props.isOpen) { + setSearchQuery(""); + setIsSearching(false); + setSearchResults(null); + // refresh the in-memory peer list from disk so any profiles received // while the modal was closed are visible immediately tauri.getKnownPeers().then((peers) => { @@ -59,13 +69,46 @@ const UserDirectoryModal: Component = (props) => { } }); + createEffect(() => { + if (!props.isOpen) return; + + const query = searchQuery().trim(); + if (!query) { + setIsSearching(false); + setSearchResults(null); + return; + } + + let cancelled = false; + setIsSearching(true); + const searchTimeout = window.setTimeout(async () => { + try { + const results = await tauri.searchDirectory(query); + if (!cancelled) { + setSearchResults(results); + setIsSearching(false); + } + } catch { + if (!cancelled) { + setIsSearching(false); + setSearchResults(null); + } + } + }, 180); + + onCleanup(() => { + cancelled = true; + window.clearTimeout(searchTimeout); + }); + }); + // filter out our own peer id from the directory const filteredPeers = createMemo(() => { const myId = identity()?.peer_id; const query = searchQuery().toLowerCase().trim(); const tab = activeTab(); - let peers = knownPeers(); + let peers = (searchResults() ?? knownPeers()).filter((p) => p.peer_id !== myId); if (tab === "friends") { peers = peers.filter((p) => p.is_friend); @@ -77,9 +120,6 @@ const UserDirectoryModal: Component = (props) => { p.display_name.toLowerCase().includes(query) || p.peer_id.toLowerCase().includes(query), ); - } else { - // if not searching, hide self from the list to avoid confusion - peers = peers.filter((p) => p.peer_id !== myId); } return peers; @@ -195,6 +235,11 @@ const UserDirectoryModal: Component = (props) => { onInput={(e) => setSearchQuery(e.currentTarget.value)} /> + 0}> +

+ searching directory... +

+
@@ -207,8 +252,10 @@ const UserDirectoryModal: Component = (props) => {

- {searchQuery() - ? "no peers matching your search" + {searchQuery().trim().length > 0 + ? isSearching() + ? "searching directory" + : "no peers matching your search" : activeTab() === "friends" ? "no friends added yet" : "no peers discovered yet"} diff --git a/src/stores/directory.ts b/src/stores/directory.ts index d5063f2..716679d 100644 --- a/src/stores/directory.ts +++ b/src/stores/directory.ts @@ -31,7 +31,13 @@ export function updatePeerProfile( // update existing peer return prev.map((p) => p.peer_id === peerId - ? { ...p, display_name: displayName, bio, last_seen: now } + ? { + ...p, + display_name: displayName, + bio, + public_key: publicKey || p.public_key, + last_seen: now, + } : p, ); } else {