.
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m50s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m40s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled

This commit is contained in:
2026-02-27 08:11:40 +07:00
parent 3debfa81d7
commit 2dcf825105
6 changed files with 184 additions and 107 deletions

View File

@@ -122,6 +122,9 @@ impl ChatRegistry {
}
_ => {}
}
// Load rooms on every state change
this.get_rooms(cx);
}),
);
@@ -137,13 +140,8 @@ impl ChatRegistry {
// Run at the end of the current cycle
cx.defer_in(window, |this, _window, cx| {
// Load chat rooms
this.get_rooms(cx);
// Handle nostr notifications
this.handle_notifications(cx);
// Track unwrap gift wrap progress
this.tracking(cx);
});
@@ -248,7 +246,7 @@ impl ChatRegistry {
let status = self.tracking_flag.clone();
self.tasks.push(cx.background_spawn(async move {
let loop_duration = Duration::from_secs(10);
let loop_duration = Duration::from_secs(15);
loop {
if status.load(Ordering::Acquire) {

View File

@@ -336,68 +336,65 @@ impl Room {
}
let client = nostr.read(cx).client();
let write_relays = nostr.read(cx).write_relays(&member, cx);
let ensure_write_relays = nostr.read(cx).ensure_write_relays(&member, cx);
tasks.insert(
member,
cx.background_spawn(async move {
let urls = write_relays.await;
let task = cx.background_spawn(async move {
let mut has_inbox = false;
let mut has_announcement = false;
// Return if no relays are available
if urls.is_empty() {
return Err(anyhow!(
"User has not set up any relays. You cannot send messages to them."
));
// Get user's write relays
let urls = ensure_write_relays.await;
// Return if no relays are available
if urls.is_empty() {
return Err(anyhow!(
"User has not set up any relays. You cannot send messages to them."
));
}
// Construct filters for inbox relays
let inbox = Filter::new()
.kind(Kind::InboxRelays)
.author(member)
.limit(1);
// Construct filters for announcement
let announcement = Filter::new()
.kind(Kind::Custom(10044))
.author(member)
.limit(1);
// Create subscription targets
let target: HashMap<RelayUrl, Vec<Filter>> = urls
.into_iter()
.map(|relay| (relay, vec![inbox.clone(), announcement.clone()]))
.collect();
// Stream events from user's write relays
let mut stream = client
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
while let Some((_url, res)) = stream.next().await {
let event = res?;
match event.kind {
Kind::InboxRelays => has_inbox = true,
Kind::Custom(10044) => has_announcement = true,
_ => {}
}
// Construct filters for inbox and announcement
let inbox_filter = Filter::new()
.kind(Kind::InboxRelays)
.author(member)
.limit(1);
let announcement_filter = Filter::new()
.kind(Kind::Custom(10044))
.author(member)
.limit(1);
// Create subscription targets
let target = urls
.into_iter()
.map(|relay| {
(
relay,
vec![inbox_filter.clone(), announcement_filter.clone()],
)
})
.collect::<HashMap<_, _>>();
// Stream events from user's write relays
let mut stream = client
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
let mut has_inbox = false;
let mut has_announcement = false;
while let Some((_url, res)) = stream.next().await {
let event = res?;
match event.kind {
Kind::InboxRelays => has_inbox = true,
Kind::Custom(10044) => has_announcement = true,
_ => {}
}
// Early exit if both flags are found
if has_inbox && has_announcement {
break;
}
// Early exit if both flags are found
if has_inbox && has_announcement {
break;
}
}
Ok((has_inbox, has_announcement))
}),
);
Ok((has_inbox, has_announcement))
});
tasks.insert(member, task);
}
tasks

View File

@@ -79,16 +79,14 @@ fn main() {
// Initialize theme registry
theme::init(cx);
// Initialize settings
settings::init(window, cx);
// Initialize the nostr client
state::init(window, cx);
// Initialize device signer
//
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
device::init(window, cx);
// Initialize settings
settings::init(window, cx);
// Initialize person registry
person::init(cx);
// Initialize relay auth registry
relay_auth::init(window, cx);
@@ -96,8 +94,10 @@ fn main() {
// Initialize app registry
chat::init(window, cx);
// Initialize person registry
person::init(cx);
// Initialize device signer
//
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
device::init(window, cx);
// Initialize auto update
auto_update::init(window, cx);

View File

@@ -67,7 +67,7 @@ pub struct RelayAuth {
pending_events: HashSet<(EventId, RelayUrl)>,
/// Tasks for asynchronous operations
tasks: SmallVec<[Task<()>; 2]>,
_tasks: SmallVec<[Task<()>; 2]>,
}
impl RelayAuth {
@@ -83,26 +83,15 @@ impl RelayAuth {
/// Create a new relay auth instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
cx.defer_in(window, |this, window, cx| {
this.handle_notifications(window, cx);
});
Self {
pending_events: HashSet::default(),
tasks: smallvec![],
}
}
/// Handle nostr notifications
fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let mut tasks = smallvec![];
// Channel for communication between nostr and gpui
let (tx, rx) = flume::bounded::<Signal>(256);
self.tasks.push(cx.background_spawn(async move {
log::info!("Started handling nostr notifications");
tasks.push(cx.background_spawn(async move {
let mut notifications = client.notifications();
let mut challenges: HashSet<Cow<'_, str>> = HashSet::default();
@@ -117,6 +106,22 @@ impl RelayAuth {
tx.send_async(signal).await.ok();
}
}
RelayMessage::Closed {
subscription_id,
message,
} => {
let msg = MachineReadablePrefix::parse(&message);
if let Some(MachineReadablePrefix::AuthRequired) = msg {
if let Ok(Some(relay)) = client.relay(&relay_url).await {
// Send close message to relay
relay
.send_msg(ClientMessage::Close(subscription_id))
.await
.ok();
}
}
}
RelayMessage::Ok {
event_id, message, ..
} => {
@@ -134,7 +139,7 @@ impl RelayAuth {
}
}));
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
tasks.push(cx.spawn_in(window, async move |this, cx| {
while let Ok(signal) = rx.recv_async().await {
match signal {
Signal::Auth(req) => {
@@ -152,6 +157,11 @@ impl RelayAuth {
}
}
}));
Self {
pending_events: HashSet::default(),
_tasks: tasks,
}
}
/// Insert a pending event waiting for resend after authentication
@@ -162,15 +172,12 @@ impl RelayAuth {
/// Get all pending events for a specific relay,
fn get_pending_events(&self, relay: &RelayUrl, _cx: &App) -> Vec<EventId> {
let pending_events: Vec<EventId> = self
.pending_events
self.pending_events
.iter()
.filter(|(_, pending_relay)| pending_relay == relay)
.map(|(id, _relay)| id)
.cloned()
.collect();
pending_events
.collect()
}
/// Clear all pending events for a specific relay,
@@ -282,10 +289,12 @@ impl RelayAuth {
Ok(_) => {
// Clear pending events for the authenticated relay
this.clear_pending_events(url, cx);
// Save the authenticated relay to automatically authenticate future requests
settings.update(cx, |this, cx| {
this.add_trusted_relay(url, cx);
});
window.push_notification(format!("{} has been authenticated", url), cx);
}
Err(e) => {

View File

@@ -13,7 +13,7 @@ pub const APP_ID: &str = "su.reya.coop";
pub const KEYRING: &str = "Coop Safe Storage";
/// Default timeout for subscription
pub const TIMEOUT: u64 = 3;
pub const TIMEOUT: u64 = 2;
/// Default delay for searching
pub const FIND_DELAY: u64 = 600;
@@ -37,12 +37,13 @@ pub const USER_GIFTWRAP: &str = "user-gift-wraps";
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
/// Default search relays
pub const SEARCH_RELAYS: [&str; 1] = ["wss://antiprimal.net"];
pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://search.nos.today"];
/// Default bootstrap relays
pub const BOOTSTRAP_RELAYS: [&str; 3] = [
"wss://relay.damus.io",
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
"wss://nos.lol",
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://user.kindpag.es",
];

View File

@@ -146,10 +146,7 @@ impl NostrRegistry {
}
// Connect to all added relays
client
.connect()
.and_wait(Duration::from_secs(TIMEOUT))
.await;
client.connect().and_wait(Duration::from_secs(5)).await;
})
.await;
@@ -252,6 +249,63 @@ impl NostrRegistry {
self.gossip.read(cx).read_only_relays(public_key)
}
/// Ensure write relays for a given public key
pub fn ensure_write_relays(&self, public_key: &PublicKey, cx: &App) -> Task<Vec<RelayUrl>> {
let client = self.client();
let public_key = *public_key;
cx.background_spawn(async move {
let mut relays = vec![];
let filter = Filter::new()
.kind(Kind::RelayList)
.author(public_key)
.limit(1);
// Construct target for subscription
let target: HashMap<&str, Vec<Filter>> = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect();
if let Ok(mut stream) = client
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await
{
while let Some((_url, res)) = stream.next().await {
match res {
Ok(event) => {
// Extract relay urls
relays.extend(nip65::extract_owned_relay_list(event).filter_map(
|(url, metadata)| {
if metadata.is_none() || metadata == Some(RelayMetadata::Write)
{
Some(url)
} else {
None
}
},
));
// Ensure connections
for url in relays.iter() {
client.add_relay(url).and_connect().await.ok();
}
return relays;
}
Err(e) => {
log::error!("Failed to receive relay list event: {e}");
}
}
}
}
relays
})
}
/// Get a list of write relays for a given public key
pub fn write_relays(&self, public_key: &PublicKey, cx: &App) -> Task<Vec<RelayUrl>> {
let client = self.client();
@@ -423,7 +477,6 @@ impl NostrRegistry {
let event = EventBuilder::relay_list(relay_list).sign(&signer).await?;
client
.send_event(&event)
.broadcast()
.ok_timeout(Duration::from_secs(TIMEOUT))
.await?;
@@ -447,7 +500,6 @@ impl NostrRegistry {
let event = EventBuilder::contact_list(contacts).sign(&signer).await?;
client
.send_event(&event)
.broadcast()
.ok_timeout(Duration::from_secs(TIMEOUT))
.ack_policy(AckPolicy::none())
.await?;
@@ -488,9 +540,18 @@ impl NostrRegistry {
cx.notify();
}
/// Set the state of the relay list
fn set_relay_state(&mut self, state: RelayState, cx: &mut Context<Self>) {
self.relay_list_state = state;
cx.notify();
}
pub fn ensure_relay_list(&mut self, cx: &mut Context<Self>) {
let task = self.verify_relay_list(cx);
// Set the state to idle before starting the task
self.set_relay_state(RelayState::default(), cx);
self.tasks.push(cx.spawn(async move |this, cx| {
let result = task.await?;
@@ -517,9 +578,15 @@ impl NostrRegistry {
.author(public_key)
.limit(1);
// Construct target for subscription
let target: HashMap<&str, Vec<Filter>> = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect();
// Stream events from the bootstrap relays
let mut stream = client
.stream_events(filter)
.stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
@@ -601,10 +668,10 @@ impl NostrRegistry {
.limit(1);
// Construct target for subscription
let target = BOOTSTRAP_RELAYS
let target: HashMap<&str, Vec<Filter>> = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
.collect();
client.subscribe(target).close_on(opts).await?;
@@ -648,10 +715,10 @@ impl NostrRegistry {
.limit(FIND_LIMIT);
// Construct target for subscription
let target = SEARCH_RELAYS
let target: HashMap<&str, Vec<Filter>> = SEARCH_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
.collect();
// Stream events from the search relays
let mut stream = client
@@ -784,6 +851,10 @@ fn default_relay_list() -> Vec<(RelayUrl, Option<RelayMetadata>)> {
RelayUrl::parse("wss://nos.lol").unwrap(),
Some(RelayMetadata::Read),
),
(
RelayUrl::parse("wss://nostr.superfriends.online").unwrap(),
None,
),
]
}
@@ -791,6 +862,7 @@ fn default_messaging_relays() -> Vec<RelayUrl> {
vec![
RelayUrl::parse("wss://nos.lol").unwrap(),
RelayUrl::parse("wss://nip17.com").unwrap(),
RelayUrl::parse("wss://relay.0xchat.com").unwrap(),
]
}