chore: refactor subscription (#113)

* fix duplicate set signer request

* refactor

* .
This commit is contained in:
reya
2025-08-07 20:53:21 +07:00
committed by GitHub
parent 7b20131e3b
commit 8fca202c05
7 changed files with 177 additions and 219 deletions

View File

@@ -342,7 +342,7 @@ impl ChatSpace {
) -> impl IntoElement {
let proxy = AppSettings::get_proxy_user_avatars(cx);
let need_backup = Identity::read_global(cx).need_backup();
let relay_ready = Identity::read_global(cx).relay_ready();
let has_dm_relays = Identity::read_global(cx).has_dm_relays();
let updating = AutoUpdater::read_global(cx).status.is_updating();
let updated = AutoUpdater::read_global(cx).status.is_updated();
@@ -377,7 +377,7 @@ impl ChatSpace {
}),
)
})
.when_some(relay_ready, |this, status| {
.when_some(has_dm_relays, |this, status| {
this.when(!status, |this| this.child(messaging_relays::relay_button()))
})
.when_some(need_backup, |this, keys| {

View File

@@ -9,7 +9,7 @@ use global::constants::{
APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT,
SEARCH_RELAYS, WAIT_FOR_FINISH,
};
use global::{gift_wrap_sub_id, nostr_client, starting_time, NostrSignal};
use global::{nostr_client, processed_events, starting_time, NostrSignal};
use gpui::{
actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
@@ -50,9 +50,7 @@ fn main() {
let (signal_tx, signal_rx) = channel::bounded::<NostrSignal>(2048);
let (mta_tx, mta_rx) = channel::bounded::<PublicKey>(1024);
let (event_tx, event_rx) = channel::bounded::<Event>(2048);
let signal_tx_clone = signal_tx.clone();
let mta_tx_clone = mta_tx.clone();
app.background_executor()
.spawn(async move {
@@ -64,9 +62,7 @@ fn main() {
// Handle Nostr notifications.
//
// Send the redefined signal back to GPUI via channel.
if let Err(e) =
handle_nostr_notifications(client, &signal_tx_clone, &mta_tx_clone, &event_tx).await
{
if let Err(e) = handle_nostr_notifications(&signal_tx_clone, &event_tx).await {
log::error!("Failed to handle Nostr notifications: {e}");
}
})
@@ -75,6 +71,7 @@ fn main() {
app.background_executor()
.spawn(async move {
let duration = Duration::from_millis(METADATA_BATCH_TIMEOUT);
let mut processed_pubkeys: BTreeSet<PublicKey> = BTreeSet::new();
let mut batch: BTreeSet<PublicKey> = BTreeSet::new();
/// Internal events for the metadata batching system
@@ -102,20 +99,23 @@ fn main() {
match smol::future::or(recv(), timeout()).await {
BatchEvent::NewKeys(public_key) => {
batch.insert(public_key);
// Process immediately if batch limit reached
// Prevent duplicate keys from being processed
if processed_pubkeys.insert(public_key) {
batch.insert(public_key);
}
// Process the batch if it's full
if batch.len() >= METADATA_BATCH_LIMIT {
sync_data_for_pubkeys(client, std::mem::take(&mut batch)).await;
sync_data_for_pubkeys(std::mem::take(&mut batch)).await;
}
}
BatchEvent::Timeout => {
if !batch.is_empty() {
sync_data_for_pubkeys(client, std::mem::take(&mut batch)).await;
sync_data_for_pubkeys(std::mem::take(&mut batch)).await;
}
}
BatchEvent::Closed => {
if !batch.is_empty() {
sync_data_for_pubkeys(client, std::mem::take(&mut batch)).await;
sync_data_for_pubkeys(std::mem::take(&mut batch)).await;
}
break;
}
@@ -243,7 +243,7 @@ fn main() {
while let Ok(signal) = signal_rx.recv().await {
cx.update(|window, cx| {
let registry = Registry::global(cx);
let identity = Identity::read_global(cx);
let identity = Identity::global(cx);
match signal {
// Load chat rooms and stop the loading status
@@ -267,15 +267,6 @@ fn main() {
}
});
}
// Load chat rooms without setting as finished
NostrSignal::Eose(subscription_id) => {
// Only load chat rooms if the subscription matches the gift wrap subscription
if gift_wrap_sub_id() == &subscription_id {
registry.update(cx, |this, cx| {
this.load_rooms(window, cx);
});
}
}
// Add the new metadata to the registry or update the existing one
NostrSignal::Metadata(event) => {
registry.update(cx, |this, cx| {
@@ -284,12 +275,17 @@ fn main() {
}
// Convert the gift wrapped message to a message
NostrSignal::GiftWrap(event) => {
if let Some(public_key) = identity.public_key() {
if let Some(public_key) = identity.read(cx).public_key() {
registry.update(cx, |this, cx| {
this.event_to_message(public_key, event, window, cx);
});
}
}
NostrSignal::DmRelaysFound => {
identity.update(cx, |this, cx| {
this.set_has_dm_relays(cx);
});
}
NostrSignal::Notice(_msg) => {
// window.push_notification(msg, cx);
}
@@ -356,67 +352,116 @@ async fn connect(client: &Client) -> Result<(), Error> {
}
async fn handle_nostr_notifications(
client: &Client,
signal_tx: &Sender<NostrSignal>,
mta_tx: &Sender<PublicKey>,
event_tx: &Sender<Event>,
) -> Result<(), Error> {
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let client = nostr_client();
let auto_close = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let mut notifications = client.notifications();
let mut processed_events: BTreeSet<EventId> = BTreeSet::new();
let mut processed_dm_relays: BTreeSet<PublicKey> = BTreeSet::new();
let mut processed_relay_list = false;
let mut processed_inbox_relay = false;
while let Ok(notification) = notifications.recv().await {
let RelayPoolNotification::Message { message, .. } = notification else {
continue;
};
match message {
RelayMessage::Event { event, .. } => {
if processed_events.contains(&event.id) {
continue;
}
// Skip events that have already been processed
processed_events.insert(event.id);
let RelayMessage::Event { event, .. } = message else {
continue;
};
match event.kind {
Kind::GiftWrap => {
event_tx.send(event.into_owned()).await.ok();
}
Kind::Metadata => {
signal_tx
.send(NostrSignal::Metadata(event.into_owned()))
.await
.ok();
}
Kind::ContactList => {
if let Ok(true) = check_author(client, &event).await {
for public_key in event.tags.public_keys().copied() {
mta_tx.send(public_key).await.ok();
}
}
}
Kind::RelayList => {
if processed_dm_relays.contains(&event.pubkey) {
continue;
}
// Skip public keys that have already been processed
processed_dm_relays.insert(event.pubkey);
// Skip events that have already been processed
if !processed_events().write().await.insert(event.id) {
continue;
}
let filter = Filter::new()
.author(event.pubkey)
.kind(Kind::InboxRelays)
.limit(1);
client.subscribe(filter, Some(opts)).await.ok();
match event.kind {
Kind::RelayList => {
// Get metadata for event's pubkey that matches the current user's pubkey
if let Ok(true) = is_from_current_user(&event).await {
match processed_relay_list {
true => continue,
false => processed_relay_list = true,
}
_ => {}
let sub_id = SubscriptionId::new("metadata");
let filter = Filter::new()
.kinds(vec![Kind::Metadata, Kind::ContactList, Kind::InboxRelays])
.author(event.pubkey)
.limit(10);
client
.subscribe_with_id(sub_id, filter, Some(auto_close))
.await
.ok();
}
}
RelayMessage::EndOfStoredEvents(subscription_id) => {
Kind::InboxRelays => {
if let Ok(true) = is_from_current_user(&event).await {
match processed_inbox_relay {
true => continue,
false => processed_inbox_relay = true,
}
// Get all inbox relays
let relays = event
.tags
.filter_standardized(TagKind::Relay)
.filter_map(|t| {
if let TagStandard::Relay(url) = t {
Some(url.to_owned())
} else {
None
}
})
.collect_vec();
if !relays.is_empty() {
// Add relays to nostr client
for relay in relays.iter() {
_ = client.add_relay(relay).await;
_ = client.connect_relay(relay).await;
}
log::info!("Connected to messaging relays");
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(event.pubkey);
let sub_id = SubscriptionId::new("gift-wrap");
// Notify the UI that the current user has set up the DM relays
signal_tx.send(NostrSignal::DmRelaysFound).await.ok();
if client
.subscribe_with_id_to(relays.clone(), sub_id, filter, None)
.await
.is_ok()
{
log::info!("Subscribing to gift wrap events in: {relays:?}");
}
}
}
}
Kind::ContactList => {
if let Ok(true) = is_from_current_user(&event).await {
let public_keys: Vec<PublicKey> = event.tags.public_keys().copied().collect();
let kinds = vec![Kind::Metadata, Kind::ContactList];
let lens = public_keys.len() * kinds.len();
let filter = Filter::new().limit(lens).authors(public_keys).kinds(kinds);
client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close))
.await
.ok();
}
}
Kind::Metadata => {
signal_tx
.send(NostrSignal::Eose(subscription_id.into_owned()))
.await?;
.send(NostrSignal::Metadata(event.into_owned()))
.await
.ok();
}
Kind::GiftWrap => {
event_tx.send(event.into_owned()).await.ok();
}
_ => {}
}
@@ -425,28 +470,28 @@ async fn handle_nostr_notifications(
Ok(())
}
async fn check_author(client: &Client, event: &Event) -> Result<bool, Error> {
async fn is_from_current_user(event: &Event) -> Result<bool, Error> {
let client = nostr_client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
Ok(public_key == event.pubkey)
}
async fn sync_data_for_pubkeys(client: &Client, public_keys: BTreeSet<PublicKey>) {
async fn sync_data_for_pubkeys(public_keys: BTreeSet<PublicKey>) {
let client = nostr_client();
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
let kinds = vec![Kind::Metadata, Kind::ContactList];
let filter = Filter::new()
.limit(public_keys.len() * kinds.len())
.authors(public_keys)
.kinds(kinds);
if let Err(e) = client
client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
.await
{
log::error!("Failed to sync metadata: {e}");
}
.ok();
}
/// Stores an unwrapped event in local database with reference to original

View File

@@ -1,7 +1,7 @@
use std::time::Duration;
use anyhow::{anyhow, Error};
use global::constants::{GIFT_WRAP_SUB_ID, NIP17_RELAYS};
use global::constants::NIP17_RELAYS;
use global::nostr_client;
use gpui::prelude::FluentBuilder;
use gpui::{
@@ -10,8 +10,6 @@ use gpui::{
TextAlign, UniformList, Window,
};
use i18n::{shared_t, t};
use identity::Identity;
use itertools::Itertools;
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme;
@@ -189,15 +187,12 @@ impl MessagingRelays {
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let client = nostr_client();
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
let tags: Vec<Tag> = relays
.iter()
.map(|relay| Tag::relay(relay.clone()))
.collect();
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(
relays
.iter()
.map(|relay| Tag::relay(relay.clone()))
.collect_vec(),
);
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(tags);
// Set messaging relays
client.send_event_builder(builder).await?;
@@ -208,15 +203,6 @@ impl MessagingRelays {
_ = client.connect_relay(&relay).await;
}
let id = SubscriptionId::new(GIFT_WRAP_SUB_ID);
let new_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
// Close old subscriptions
client.unsubscribe(&id).await;
// Subscribe to new messages
client.subscribe_with_id(id, new_messages, None).await?;
Ok(())
});
@@ -224,10 +210,6 @@ impl MessagingRelays {
match task.await {
Ok(_) => {
cx.update(|window, cx| {
Identity::global(cx).update(cx, |this, cx| {
this.verify_dm_relays(window, cx);
});
// Close the current modal
window.close_modal(cx);
})
.ok();

View File

@@ -580,6 +580,7 @@ impl Sidebar {
});
}
#[allow(dead_code)]
fn skeletons(&self, total: i32) -> impl IntoIterator<Item = impl IntoElement> {
(0..total).map(|_| {
div()
@@ -788,16 +789,6 @@ impl Render for Sidebar {
),
),
)
.when(registry.loading, |this| {
this.child(
div()
.flex_1()
.flex()
.flex_col()
.gap_1()
.children(self.skeletons(1)),
)
})
.child(
uniform_list(
"rooms",