add browse panel for discovering relay groups

This commit is contained in:
2026-09-30 15:53:17 +07:00
parent 6e24a6033e
commit 5b87b2fe27
7 changed files with 383 additions and 108 deletions
+33 -57
View File
@@ -35,17 +35,12 @@ struct GlobalGroupsRegistry(Entity<GroupsRegistry>);
impl Global for GlobalGroupsRegistry {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone)]
pub enum GroupsEvent {
Updated,
Error(String),
}
#[derive(Debug, Clone)]
pub struct Browse {
pub relay: RelayUrl,
pub loading: bool,
pub groups: Vec<GroupCandidate>,
Browsed(Box<GroupCandidate>),
BrowseSettled(RelayUrl),
}
#[derive(Debug)]
@@ -53,6 +48,7 @@ enum Signal {
Event(SubscriptionId, Event),
Eose(SubscriptionId),
Closed(SubscriptionId, String),
BrowseSettled(SubscriptionId),
Forks,
}
@@ -97,7 +93,7 @@ impl EventEmitter<GroupsEvent> for GroupsRegistry {}
pub struct GroupsRegistry {
groups: Vec<Entity<Group>>,
list: GroupList,
browse: HashMap<SubscriptionId, Browse>,
browse: HashMap<SubscriptionId, RelayUrl>,
routes: HashMap<SubscriptionId, GroupKey>,
observers: HashMap<GroupKey, Subscription>,
forks: HashMap<PublicKey, (Timestamp, GroupList)>,
@@ -246,19 +242,11 @@ impl GroupsRegistry {
pub fn browse(&mut self, relay: RelayUrl, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let id = browse_subscription(&relay);
let tx = self.signal_tx.clone();
self.browse.insert(
id.clone(),
Browse {
relay: relay.clone(),
loading: true,
groups: Vec::new(),
},
);
cx.emit(GroupsEvent::Updated);
cx.notify();
self.browse.insert(id.clone(), relay.clone());
self.tasks.push(cx.background_spawn(async move {
client.add_relay(relay.clone()).and_connect().await.ok();
@@ -269,18 +257,25 @@ impl GroupsRegistry {
.timeout(Some(BROWSE_TIMEOUT));
if let Err(error) = client
.subscribe(ReqTarget::single(relay, [filter]))
.with_id(id)
.subscribe(ReqTarget::single(relay.clone(), [filter]))
.with_id(id.clone())
.close_on(opts)
.await
{
log::warn!("nip29: browsing failed: {error}");
log::warn!("nip29: browsing {relay} failed: {error}");
}
if let Err(error) = tx.send_async(Signal::BrowseSettled(id)).await {
log::warn!("nip29: the browse settle signal was dropped: {error}");
}
}));
}
pub fn browsed(&self, relay: &RelayUrl) -> Option<&Browse> {
self.browse.get(&browse_subscription(relay))
fn settle_browse(&mut self, id: SubscriptionId, cx: &mut Context<Self>) {
let Some(relay) = self.browse.get(&id).cloned() else {
return;
};
cx.emit(GroupsEvent::BrowseSettled(relay));
}
pub fn load_more(&mut self, key: &GroupKey, cx: &mut Context<Self>) {
@@ -516,6 +511,9 @@ impl GroupsRegistry {
GroupEvent::Updated => {
this.refresh_entry(group, cx);
this.sync_forks(false, cx);
cx.emit(GroupsEvent::Updated);
cx.notify();
}
_ => {}
}),
@@ -611,9 +609,6 @@ impl GroupsRegistry {
self.upsert_entry(&key, Some(name));
self.publish_list(cx);
cx.emit(GroupsEvent::Updated);
cx.notify();
}
fn publish_list(&mut self, cx: &mut Context<Self>) {
@@ -741,15 +736,12 @@ impl GroupsRegistry {
}
fn absorb_candidate(&mut self, id: &SubscriptionId, event: &Event, cx: &mut Context<Self>) {
let Some(browse) = self.browse.get_mut(id) else {
let Some(relay) = self.browse.get(id).cloned() else {
return;
};
let Ok(metadata) = GroupMetadata::parse(event) else {
log::warn!(
"nip29: ignoring malformed group metadata from {}",
browse.relay
);
log::warn!("nip29: ignoring malformed group metadata from {relay}");
return;
};
@@ -757,28 +749,13 @@ impl GroupsRegistry {
return;
};
let candidate = GroupCandidate {
key: GroupKey::new(browse.relay.clone(), group),
cx.emit(GroupsEvent::Browsed(Box::new(GroupCandidate {
key: GroupKey::new(relay, group),
metadata,
};
match browse.groups.iter_mut().find(|e| e.key == candidate.key) {
Some(existing) => *existing = candidate,
None => browse.groups.push(candidate),
}
cx.emit(GroupsEvent::Updated);
cx.notify();
})));
}
fn handle_eose(&mut self, id: SubscriptionId, cx: &mut Context<Self>) {
if let Some(browse) = self.browse.get_mut(&id) {
browse.loading = false;
cx.emit(GroupsEvent::Updated);
cx.notify();
return;
}
let Some(key) = self.routes.get(&id).cloned() else {
return;
};
@@ -807,12 +784,8 @@ impl GroupsRegistry {
}
fn handle_closed(&mut self, id: SubscriptionId, reason: String, cx: &mut Context<Self>) {
if let Some(browse) = self.browse.get_mut(&id) {
log::warn!("nip29: browsing {} was refused: {reason}", browse.relay);
browse.loading = false;
cx.emit(GroupsEvent::Updated);
cx.notify();
if let Some(relay) = self.browse.get(&id) {
log::warn!("nip29: browsing {relay} was refused: {reason}");
return;
}
@@ -922,6 +895,9 @@ impl GroupsRegistry {
Signal::Closed(id, reason) => {
this.handle_closed(id, reason, cx);
}
Signal::BrowseSettled(id) => {
this.settle_browse(id, cx);
}
Signal::Forks => {
this.sync_forks(true, cx);
}
+12
View File
@@ -469,6 +469,18 @@ pub struct GroupCandidate {
pub metadata: GroupMetadata,
}
impl GroupCandidate {
pub fn display_name(&self) -> &str {
self.metadata
.name()
.unwrap_or_else(|| self.key.id().as_str())
}
pub fn display_image(&self) -> Option<&str> {
self.metadata.picture.as_deref()
}
}
#[derive(Debug, Clone, Default)]
pub struct GroupAdmins(Vec<(PublicKey, Vec<String>)>);