feat(peer-discovery): implement global peer discovery and update peer profile handling

This commit is contained in:
cloudwithax 2026-02-14 20:42:29 -05:00
parent 34cc1883f5
commit b4f75cd995
9 changed files with 83 additions and 9 deletions

View File

@ -157,6 +157,16 @@ pub async fn start_node(app: tauri::AppHandle, state: State<'_, AppState>) -> Re
namespace: personal_ns,
})
.await;
// register under the global "dusk/peers" namespace so any peer can
// discover us via the relay tracker, enabling global peer discovery
// without exposing ip addresses (all connections use relay circuit)
let _ = handle
.command_tx
.send(NodeCommand::RegisterRendezvous {
namespace: "dusk/peers".to_string(),
})
.await;
}
Ok(())

View File

@ -258,6 +258,22 @@ pub async fn remove_friend(state: State<'_, AppState>, peer_id: String) -> Resul
.map_err(|e| format!("failed to remove friend: {}", e))
}
// discover online peers via the global relay tracker namespace
// this allows finding peers without sharing a community or knowing their peer_id
#[tauri::command]
pub async fn discover_global_peers(state: State<'_, AppState>) -> Result<(), String> {
let node_handle = state.node_handle.lock().await;
if let Some(ref handle) = *node_handle {
let _ = handle
.command_tx
.send(crate::node::NodeCommand::DiscoverRendezvous {
namespace: "dusk/peers".to_string(),
})
.await;
}
Ok(())
}
// broadcast a revocation to all peers, stop the node, and wipe all local data
#[tauri::command]
pub async fn reset_identity(state: State<'_, AppState>) -> Result<(), String> {

View File

@ -89,6 +89,7 @@ pub fn run() {
commands::identity::get_friends,
commands::identity::add_friend,
commands::identity::remove_friend,
commands::identity::discover_global_peers,
commands::identity::reset_identity,
commands::chat::send_message,
commands::chat::get_messages,

View File

@ -118,6 +118,7 @@ pub enum DuskEvent {
peer_id: String,
display_name: String,
bio: String,
public_key: String,
},
#[serde(rename = "profile_revoked")]
ProfileRevoked { peer_id: String },
@ -396,6 +397,7 @@ pub async fn start(
peer_id: profile.peer_id,
display_name: profile.display_name,
bio: profile.bio,
public_key: profile.public_key,
});
}
crate::protocol::messages::GossipMessage::ProfileRevoke(revocation) => {

View File

@ -342,6 +342,7 @@ const App: Component = () => {
event.payload.peer_id,
event.payload.display_name,
event.payload.bio,
event.payload.public_key,
);
// keep dm conversation names in sync
updateDMPeerDisplayName(

View File

@ -1,4 +1,11 @@
import { Component, createSignal, createMemo, For, Show } from "solid-js";
import {
Component,
createSignal,
createMemo,
createEffect,
For,
Show,
} from "solid-js";
import { Portal } from "solid-js/web";
import {
X,
@ -34,6 +41,17 @@ const UserDirectoryModal: Component<UserDirectoryModalProps> = (props) => {
const [activeTab, setActiveTab] = createSignal<DirectoryTab>("all");
const [copiedId, setCopiedId] = createSignal<string | null>(null);
// trigger global peer discovery when modal opens
createEffect(() => {
if (props.isOpen) {
// discover peers registered on the relay's global "dusk/peers" namespace
// this allows finding online peers without sharing a community
tauri.discoverGlobalPeers().catch((e) => {
console.error("failed to discover global peers:", e);
});
}
});
// filter out our own peer id from the directory
const filteredPeers = createMemo(() => {
const myId = identity()?.peer_id;

View File

@ -202,6 +202,10 @@ export async function removeFriend(peerId: string): Promise<void> {
return invoke("remove_friend", { peerId });
}
export async function discoverGlobalPeers(): Promise<void> {
return invoke("discover_global_peers");
}
export async function resetIdentity(): Promise<void> {
return invoke("reset_identity");
}

View File

@ -174,7 +174,12 @@ export type DuskEvent =
| { kind: "sync_complete"; payload: { community_id: string } }
| {
kind: "profile_received";
payload: { peer_id: string; display_name: string; bio: string };
payload: {
peer_id: string;
display_name: string;
bio: string;
public_key: string;
};
}
| { kind: "profile_revoked"; payload: { peer_id: string } }
| { kind: "relay_status"; payload: { connected: boolean } }

View File

@ -21,17 +21,34 @@ export function updatePeerProfile(
peerId: string,
displayName: string,
bio: string,
publicKey: string,
) {
const now = Date.now();
setKnownPeers((prev) =>
prev.map((p) =>
setKnownPeers((prev) => {
const existing = prev.find((p) => p.peer_id === peerId);
if (existing) {
// update existing peer
return prev.map((p) =>
p.peer_id === peerId
? { ...p, display_name: displayName, bio, last_seen: now }
: p,
),
);
} else {
// add new peer that just announced themselves
const newEntry: DirectoryEntry = {
peer_id: peerId,
display_name: displayName,
bio,
public_key: publicKey,
last_seen: now,
is_friend: false,
};
return [...prev, newEntry];
}
});
// update friends list if this peer is a friend
setFriends((prev) =>
prev.map((p) =>
p.peer_id === peerId