chore: simplify codebase
This commit is contained in:
557
Cargo.lock
generated
557
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@ pub fn nostr_client() -> &'static Client {
|
||||
let lmdb = NostrLMDB::open(nostr_file()).expect("Database is NOT initialized");
|
||||
|
||||
let opts = ClientOptions::new()
|
||||
.gossip(false)
|
||||
.gossip(true)
|
||||
.automatic_authentication(false)
|
||||
.verify_subscriptions(false)
|
||||
.sleep_when_idle(SleepWhenIdle::Enabled {
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
use crate::constants::BOOTSTRAP_RELAYS;
|
||||
use crate::state::SignalKind;
|
||||
use crate::{app_state, nostr_client};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Gossip {
|
||||
pub nip17: HashMap<PublicKey, HashSet<RelayUrl>>,
|
||||
pub nip65: HashMap<PublicKey, HashSet<(RelayUrl, Option<RelayMetadata>)>>,
|
||||
}
|
||||
|
||||
impl Gossip {
|
||||
/// Parse and insert NIP-65 or NIP-17 relays into the gossip state.
|
||||
pub fn insert(&mut self, event: &Event) {
|
||||
match event.kind {
|
||||
Kind::InboxRelays => {
|
||||
let urls: Vec<RelayUrl> = nip17::extract_relay_list(event).cloned().collect();
|
||||
|
||||
if !urls.is_empty() {
|
||||
self.nip17.entry(event.pubkey).or_default().extend(urls);
|
||||
}
|
||||
}
|
||||
Kind::RelayList => {
|
||||
let urls: Vec<(RelayUrl, Option<RelayMetadata>)> = nip65::extract_relay_list(event)
|
||||
.map(|(url, metadata)| (url.to_owned(), metadata.to_owned()))
|
||||
.collect();
|
||||
|
||||
if !urls.is_empty() {
|
||||
self.nip65.entry(event.pubkey).or_default().extend(urls);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all write relays for a given public key
|
||||
pub fn write_relays(&self, public_key: &PublicKey) -> Vec<&RelayUrl> {
|
||||
self.nip65
|
||||
.get(public_key)
|
||||
.map(|relays| {
|
||||
relays
|
||||
.iter()
|
||||
.filter(|(_, metadata)| metadata.as_ref() == Some(&RelayMetadata::Write))
|
||||
.map(|(url, _)| url)
|
||||
.take(3)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get all read relays for a given public key
|
||||
pub fn read_relays(&self, public_key: &PublicKey) -> Vec<&RelayUrl> {
|
||||
self.nip65
|
||||
.get(public_key)
|
||||
.map(|relays| {
|
||||
relays
|
||||
.iter()
|
||||
.filter(|(_, metadata)| metadata.as_ref() == Some(&RelayMetadata::Read))
|
||||
.map(|(url, _)| url)
|
||||
.take(3)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get all messaging relays for a given public key
|
||||
pub fn messaging_relays(&self, public_key: &PublicKey) -> Vec<&RelayUrl> {
|
||||
self.nip17
|
||||
.get(public_key)
|
||||
.map(|relays| relays.iter().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get and verify NIP-65 relays for a given public key
|
||||
///
|
||||
/// Only fetch from the public relays
|
||||
pub async fn get_nip65(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::RelayList)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to events from the bootstrapping relays
|
||||
client
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter.clone(), Some(opts))
|
||||
.await?;
|
||||
|
||||
// Verify the received data after a timeout
|
||||
smol::spawn(async move {
|
||||
smol::Timer::after(timeout).await;
|
||||
|
||||
if client.database().count(filter).await.unwrap_or(0) < 1 {
|
||||
app_state()
|
||||
.signal
|
||||
.send(SignalKind::GossipRelaysNotFound)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set NIP-65 relays for a current user
|
||||
pub async fn set_nip65(
|
||||
&mut self,
|
||||
relays: &[(RelayUrl, Option<RelayMetadata>)],
|
||||
) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
|
||||
let tags: Vec<Tag> = relays
|
||||
.iter()
|
||||
.map(|(url, metadata)| Tag::relay_metadata(url.to_owned(), metadata.to_owned()))
|
||||
.collect();
|
||||
|
||||
let event = EventBuilder::new(Kind::RelayList, "")
|
||||
.tags(tags)
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Send event to the public relays
|
||||
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
|
||||
|
||||
// Update gossip data
|
||||
for relay in relays {
|
||||
self.nip65
|
||||
.entry(event.pubkey)
|
||||
.or_default()
|
||||
.insert(relay.to_owned());
|
||||
}
|
||||
|
||||
// Get NIP-17 relays
|
||||
self.get_nip17(event.pubkey).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get and verify NIP-17 relays for a given public key
|
||||
///
|
||||
/// Only fetch from public key's write relays
|
||||
pub async fn get_nip17(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let urls = self.write_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one write relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Write relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter().cloned() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to events from the bootstrapping relays
|
||||
client
|
||||
.subscribe_to(urls, filter.clone(), Some(opts))
|
||||
.await?;
|
||||
|
||||
// Verify the received data after a timeout
|
||||
smol::spawn(async move {
|
||||
smol::Timer::after(timeout).await;
|
||||
|
||||
if client.database().count(filter).await.unwrap_or(0) < 1 {
|
||||
app_state()
|
||||
.signal
|
||||
.send(SignalKind::MessagingRelaysNotFound)
|
||||
.await;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set NIP-17 relays for a current user
|
||||
pub async fn set_nip17(&mut self, relays: &[RelayUrl]) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
let urls = self.write_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter().cloned() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
let event = EventBuilder::new(Kind::InboxRelays, "")
|
||||
.tags(relays.iter().map(|relay| Tag::relay(relay.to_owned())))
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Send event to the public relays
|
||||
client.send_event_to(urls, &event).await?;
|
||||
|
||||
// Update gossip data
|
||||
for relay in relays {
|
||||
self.nip17
|
||||
.entry(event.pubkey)
|
||||
.or_default()
|
||||
.insert(relay.to_owned());
|
||||
}
|
||||
|
||||
// Run inbox monitor
|
||||
self.monitor_inbox(event.pubkey).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Subscribe for events that match the given kind for a given author
|
||||
///
|
||||
/// Only fetch from author's write relays
|
||||
pub async fn subscribe(&self, public_key: PublicKey, kind: Kind) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let filter = Filter::new().author(public_key).kind(kind).limit(1);
|
||||
let urls = self.write_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one write relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Write relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter().cloned() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
// Subscribe to filters to user's write relays
|
||||
client.subscribe_to(urls, filter, Some(opts)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bulk subscribe to metadata events for a list of public keys
|
||||
///
|
||||
/// Only fetch from the public relays
|
||||
pub async fn bulk_subscribe(&self, public_keys: HashSet<PublicKey>) -> Result<(), Error> {
|
||||
if public_keys.is_empty() {
|
||||
return Err(anyhow!("You need at least one public key"));
|
||||
}
|
||||
|
||||
let client = nostr_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
||||
let limit = public_keys.len() * kinds.len() + 20;
|
||||
|
||||
let filter = Filter::new().authors(public_keys).kinds(kinds).limit(limit);
|
||||
let urls = BOOTSTRAP_RELAYS;
|
||||
|
||||
// Subscribe to filters to the bootstrap relays
|
||||
client.subscribe_to(urls, filter, Some(opts)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Monitor all gift wrap events in the messaging relays for a given public key
|
||||
pub async fn monitor_inbox(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let id = SubscriptionId::new("inbox");
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
let urls = self.messaging_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one messaging relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Messaging relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter().cloned() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
// Subscribe to filters to user's messaging relays
|
||||
client.subscribe_with_id_to(urls, id, filter, None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send an event to author's write relays
|
||||
pub async fn send_event_to_write_relays(&self, event: &Event) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let public_key = event.pubkey;
|
||||
let urls = self.write_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter().cloned() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
// Send event to relays
|
||||
client.send_event(event).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,6 @@ use crate::constants::{
|
||||
};
|
||||
use crate::nostr_client;
|
||||
use crate::paths::support_dir;
|
||||
use crate::state::gossip::Gossip;
|
||||
|
||||
mod gossip;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct AuthRequest {
|
||||
@@ -95,6 +92,10 @@ impl Signal {
|
||||
&self.rx
|
||||
}
|
||||
|
||||
pub fn sender(&self) -> &Sender<SignalKind> {
|
||||
&self.tx
|
||||
}
|
||||
|
||||
pub async fn send(&self, kind: SignalKind) {
|
||||
if let Err(e) = self.tx.send_async(kind).await {
|
||||
log::error!("Failed to send signal: {e}");
|
||||
@@ -182,9 +183,6 @@ pub struct AppState {
|
||||
/// Auto-close options for relay subscriptions
|
||||
pub auto_close_opts: Option<SubscribeAutoCloseOptions>,
|
||||
|
||||
/// NIP-65: https://github.com/nostr-protocol/nips/blob/master/65.md
|
||||
pub gossip: RwLock<Gossip>,
|
||||
|
||||
/// Tracks activity related to Nostr events
|
||||
pub event_tracker: RwLock<EventTracker>,
|
||||
|
||||
@@ -218,7 +216,6 @@ impl AppState {
|
||||
gift_wrap_sub_id: SubscriptionId::new("inbox"),
|
||||
gift_wrap_processing: AtomicBool::new(false),
|
||||
auto_close_opts: Some(opts),
|
||||
gossip: RwLock::new(Gossip::default()),
|
||||
event_tracker: RwLock::new(EventTracker::default()),
|
||||
}
|
||||
}
|
||||
@@ -267,59 +264,45 @@ impl AppState {
|
||||
|
||||
match event.kind {
|
||||
Kind::RelayList => {
|
||||
// Update NIP-65 relays for event's public key
|
||||
{
|
||||
let mut gossip = self.gossip.write().await;
|
||||
gossip.insert(&event);
|
||||
}
|
||||
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
// Get events if relay list belongs to current user
|
||||
if is_self_authored {
|
||||
let gossip = self.gossip.read().await;
|
||||
if let Ok(true) = Self::is_self_authored(&event).await {
|
||||
let author = event.pubkey;
|
||||
|
||||
// Fetch user's metadata event
|
||||
gossip.subscribe(event.pubkey, Kind::Metadata).await.ok();
|
||||
if let Err(e) = self.subscribe(author, Kind::Metadata).await {
|
||||
log::error!("Failed to subscribe to metadata event: {e}");
|
||||
}
|
||||
|
||||
// Fetch user's contact list event
|
||||
gossip.subscribe(event.pubkey, Kind::ContactList).await.ok();
|
||||
if let Err(e) = self.subscribe(author, Kind::ContactList).await {
|
||||
log::error!("Failed to subscribe to contact list event: {e}");
|
||||
}
|
||||
|
||||
// Fetch user's messaging relays event
|
||||
gossip.get_nip17(event.pubkey).await.ok();
|
||||
if let Err(e) = self.get_nip17(author).await {
|
||||
log::error!("Failed to fetch messaging relays event: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::InboxRelays => {
|
||||
// Update NIP-17 relays for event's public key
|
||||
{
|
||||
let mut gossip = self.gossip.write().await;
|
||||
gossip.insert(&event);
|
||||
}
|
||||
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
// Subscribe to gift wrap events if messaging relays belong to the current user
|
||||
if is_self_authored {
|
||||
let gossip = self.gossip.read().await;
|
||||
if let Ok(true) = Self::is_self_authored(&event).await {
|
||||
let urls: Vec<RelayUrl> =
|
||||
nip17::extract_relay_list(event.as_ref()).cloned().collect();
|
||||
|
||||
if gossip.monitor_inbox(event.pubkey).await.is_err() {
|
||||
self.signal.send(SignalKind::MessagingRelaysNotFound).await;
|
||||
if let Err(e) = self.get_messages(event.pubkey, &urls).await {
|
||||
log::error!("Failed to fetch messages: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::ContactList => {
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
if is_self_authored {
|
||||
if let Ok(true) = Self::is_self_authored(&event).await {
|
||||
let public_keys: HashSet<PublicKey> =
|
||||
event.tags.public_keys().copied().collect();
|
||||
|
||||
self.gossip
|
||||
.read()
|
||||
.await
|
||||
.bulk_subscribe(public_keys)
|
||||
.await
|
||||
.ok();
|
||||
if let Err(e) = self.get_metadata_for_list(public_keys).await {
|
||||
log::error!("Failed to get metadata for list: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::Metadata => {
|
||||
@@ -406,17 +389,20 @@ impl AppState {
|
||||
|
||||
// Process the batch if it's full
|
||||
if batch.len() >= METADATA_BATCH_LIMIT {
|
||||
let gossip = self.gossip.read().await;
|
||||
gossip.bulk_subscribe(std::mem::take(&mut batch)).await.ok();
|
||||
self.get_metadata_for_list(std::mem::take(&mut batch))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
BatchEvent::Timeout => {
|
||||
let gossip = self.gossip.read().await;
|
||||
gossip.bulk_subscribe(std::mem::take(&mut batch)).await.ok();
|
||||
self.get_metadata_for_list(std::mem::take(&mut batch))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
BatchEvent::Closed => {
|
||||
let gossip = self.gossip.read().await;
|
||||
gossip.bulk_subscribe(std::mem::take(&mut batch)).await.ok();
|
||||
self.get_metadata_for_list(std::mem::take(&mut batch))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
// Exit the current loop
|
||||
break;
|
||||
@@ -425,18 +411,174 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_self_authored(event: &Event) -> bool {
|
||||
async fn is_self_authored(event: &Event) -> Result<bool, Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
let Ok(signer) = client.signer().await else {
|
||||
return false;
|
||||
};
|
||||
Ok(public_key == event.pubkey)
|
||||
}
|
||||
|
||||
let Ok(public_key) = signer.get_public_key().await else {
|
||||
return false;
|
||||
};
|
||||
/// Subscribe for events that match the given kind for a given author
|
||||
async fn subscribe(&self, author: PublicKey, kind: Kind) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let filter = Filter::new().author(author).kind(kind).limit(1);
|
||||
|
||||
public_key == event.pubkey
|
||||
// Subscribe to filters from the user's write relays
|
||||
client.subscribe(filter, Some(opts)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get metadata for a list of public keys
|
||||
async fn get_metadata_for_list(&self, public_keys: HashSet<PublicKey>) -> Result<(), Error> {
|
||||
if public_keys.is_empty() {
|
||||
return Err(anyhow!("You need at least one public key"));
|
||||
}
|
||||
|
||||
let client = nostr_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
||||
let limit = public_keys.len() * kinds.len() + 20;
|
||||
let filter = Filter::new().authors(public_keys).kinds(kinds).limit(limit);
|
||||
|
||||
// Subscribe to filters to the bootstrap relays
|
||||
client
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get and verify NIP-65 relays for a given public key
|
||||
pub async fn get_nip65(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let tx = self.signal.sender().clone();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::RelayList)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to events from the bootstrapping relays
|
||||
client
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter.clone(), Some(opts))
|
||||
.await?;
|
||||
|
||||
// Verify the received data after a timeout
|
||||
smol::spawn(async move {
|
||||
smol::Timer::after(timeout).await;
|
||||
|
||||
if client.database().count(filter).await.unwrap_or(0) < 1 {
|
||||
tx.send_async(SignalKind::GossipRelaysNotFound).await.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set NIP-65 relays for a current user
|
||||
pub async fn set_nip65(
|
||||
&self,
|
||||
relays: &[(RelayUrl, Option<RelayMetadata>)],
|
||||
) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
|
||||
let tags: Vec<Tag> = relays
|
||||
.iter()
|
||||
.map(|(url, metadata)| Tag::relay_metadata(url.to_owned(), metadata.to_owned()))
|
||||
.collect();
|
||||
|
||||
let event = EventBuilder::new(Kind::RelayList, "")
|
||||
.tags(tags)
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Send event to the public relays
|
||||
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
|
||||
|
||||
// Get NIP-17 relays
|
||||
self.get_nip17(event.pubkey).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get and verify NIP-17 relays for a given public key
|
||||
pub async fn get_nip17(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let tx = self.signal.sender().clone();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to events from the bootstrapping relays
|
||||
client.subscribe(filter.clone(), Some(opts)).await?;
|
||||
|
||||
// Verify the received data after a timeout
|
||||
smol::spawn(async move {
|
||||
smol::Timer::after(timeout).await;
|
||||
|
||||
if client.database().count(filter).await.unwrap_or(0) < 1 {
|
||||
tx.send_async(SignalKind::MessagingRelaysNotFound)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set NIP-17 relays for a current user
|
||||
pub async fn set_nip17(&self, relays: &[RelayUrl]) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
|
||||
let event = EventBuilder::new(Kind::InboxRelays, "")
|
||||
.tags(relays.iter().map(|relay| Tag::relay(relay.to_owned())))
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Send event to the public relays
|
||||
client.send_event(&event).await?;
|
||||
|
||||
// Run inbox monitor
|
||||
self.get_messages(event.pubkey, relays).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all gift wrap events in the messaging relays for a given public key
|
||||
async fn get_messages(&self, public_key: PublicKey, urls: &[RelayUrl]) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let id = SubscriptionId::new("inbox");
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
|
||||
// Ensure user's have at least one relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
// Subscribe to filters to user's messaging relays
|
||||
client.subscribe_with_id_to(urls, id, filter, None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores an unwrapped event in local database with reference to original
|
||||
|
||||
@@ -225,7 +225,7 @@ impl ChatSpace {
|
||||
app_state.signal.send(SignalKind::SignerSet(pk)).await;
|
||||
|
||||
// Get user's gossip relays
|
||||
app_state.gossip.write().await.get_nip65(pk).await.ok();
|
||||
app_state.get_nip65(pk).await.ok();
|
||||
|
||||
// Exit the current loop
|
||||
break;
|
||||
@@ -877,9 +877,7 @@ impl ChatSpace {
|
||||
.spawn(cx, async move |cx| {
|
||||
let app_state = app_state();
|
||||
let relays = default_nip65_relays();
|
||||
|
||||
let mut gossip = app_state.gossip.write().await;
|
||||
let result = gossip.set_nip65(relays).await;
|
||||
let result = app_state.set_nip65(relays).await;
|
||||
|
||||
cx.update(|window, cx| {
|
||||
match result {
|
||||
@@ -981,9 +979,7 @@ impl ChatSpace {
|
||||
.spawn(cx, async move |cx| {
|
||||
let app_state = app_state();
|
||||
let relays = default_nip17_relays();
|
||||
|
||||
let mut gossip = app_state.gossip.write().await;
|
||||
let result = gossip.set_nip17(relays).await;
|
||||
let result = app_state.set_nip17(relays).await;
|
||||
|
||||
cx.update(|window, cx| {
|
||||
match result {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use app_state::{app_state, nostr_client};
|
||||
use app_state::nostr_client;
|
||||
use common::nip96::nip96_upload;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -188,9 +188,6 @@ impl EditProfile {
|
||||
}
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let app_state = app_state();
|
||||
let gossip = app_state.gossip.read().await;
|
||||
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
|
||||
@@ -198,7 +195,7 @@ impl EditProfile {
|
||||
let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?;
|
||||
|
||||
// Send event to user's write relayss
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
client.send_event(&event).await?;
|
||||
|
||||
// Return the updated profile
|
||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{anyhow, Error};
|
||||
use app_state::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS};
|
||||
use app_state::{app_state, default_nip17_relays, default_nip65_relays, nostr_client};
|
||||
use app_state::{default_nip17_relays, default_nip65_relays, nostr_client};
|
||||
use common::nip96::nip96_upload;
|
||||
use gpui::{
|
||||
div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity,
|
||||
@@ -125,8 +125,6 @@ impl NewAccount {
|
||||
// Set the client's signer with the current keys
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let app_state = app_state();
|
||||
let gossip = app_state.gossip.read().await;
|
||||
|
||||
// Set the client's signer with the current keys
|
||||
client.set_signer(keys).await;
|
||||
@@ -156,13 +154,13 @@ impl NewAccount {
|
||||
.await?;
|
||||
|
||||
// Set NIP-17 relays
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
client.send_event(&event).await?;
|
||||
|
||||
// Construct a metadata event
|
||||
let event = EventBuilder::metadata(&metadata).sign(&signer).await?;
|
||||
|
||||
// Set metadata
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
client.send_event(&event).await?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
@@ -152,13 +152,11 @@ impl SetupRelay {
|
||||
let relays = self.relays.clone();
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let app_state = app_state();
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
let app_state = app_state();
|
||||
let gossip = app_state.gossip.read().await;
|
||||
|
||||
let tags: Vec<Tag> = relays
|
||||
.iter()
|
||||
.map(|relay| Tag::relay(relay.clone()))
|
||||
@@ -170,7 +168,7 @@ impl SetupRelay {
|
||||
.await?;
|
||||
|
||||
// Set messaging relays
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
client.send_event(&event).await?;
|
||||
|
||||
// Connect to messaging relays
|
||||
for relay in relays.iter() {
|
||||
|
||||
@@ -9,6 +9,7 @@ use app_state::{app_state, nostr_client};
|
||||
use common::display::RenderedProfile;
|
||||
use common::event::EventUtils;
|
||||
use gpui::{App, AppContext, Context, EventEmitter, SharedString, SharedUri, Task};
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
use crate::Registry;
|
||||
@@ -495,9 +496,7 @@ impl Room {
|
||||
for receiver in members.into_iter() {
|
||||
let rumor = rumor.clone();
|
||||
let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, vec![]).await?;
|
||||
|
||||
let gossip = app_state.gossip.read().await;
|
||||
let urls = gossip.messaging_relays(&receiver);
|
||||
let urls = Self::messaging_relays(receiver).await;
|
||||
|
||||
// Check if there are any relays to send the event to
|
||||
if urls.is_empty() {
|
||||
@@ -549,8 +548,7 @@ impl Room {
|
||||
|
||||
// Only send a backup message to current user if sent successfully to others
|
||||
if reports.iter().all(|r| r.is_sent_success()) && backup {
|
||||
let gossip = app_state.gossip.read().await;
|
||||
let urls = gossip.messaging_relays(&public_key);
|
||||
let urls = Self::messaging_relays(public_key).await;
|
||||
|
||||
// Check if there are any relays to send the event to
|
||||
if urls.is_empty() {
|
||||
@@ -582,8 +580,6 @@ impl Room {
|
||||
) -> Task<Result<Vec<SendReport>, Error>> {
|
||||
cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let app_state = app_state();
|
||||
|
||||
let mut resend_reports = vec![];
|
||||
|
||||
for report in reports.into_iter() {
|
||||
@@ -612,8 +608,7 @@ impl Room {
|
||||
|
||||
// Process the on hold event if it exists
|
||||
if let Some(event) = report.on_hold {
|
||||
let gossip = app_state.gossip.read().await;
|
||||
let urls = gossip.messaging_relays(&receiver);
|
||||
let urls = Self::messaging_relays(receiver).await;
|
||||
|
||||
// Check if there are any relays to send the event to
|
||||
if urls.is_empty() {
|
||||
@@ -635,4 +630,31 @@ impl Room {
|
||||
Ok(resend_reports)
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets messaging relays for public key
|
||||
async fn messaging_relays(public_key: PublicKey) -> Vec<RelayUrl> {
|
||||
let client = nostr_client();
|
||||
let mut relay_urls = vec![];
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
if let Ok(events) = client.database().query(filter).await {
|
||||
if let Some(event) = events.first_owned() {
|
||||
let urls: Vec<RelayUrl> = nip17::extract_owned_relay_list(event).collect();
|
||||
|
||||
// Connect to relays
|
||||
for url in urls.iter() {
|
||||
client.add_relay(url).await.ok();
|
||||
client.connect_relay(url).await.ok();
|
||||
}
|
||||
|
||||
relay_urls.extend(urls.into_iter().take(3).unique());
|
||||
}
|
||||
}
|
||||
|
||||
relay_urls
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,4 @@ image = "0.25.1"
|
||||
linkify = "0.10.0"
|
||||
lsp-types = "0.97.0"
|
||||
rope = { git = "https://github.com/zed-industries/zed" }
|
||||
zed-sum-tree = { git = "https://github.com/zed-industries/zed" }
|
||||
sum_tree = { git = "https://github.com/zed-industries/zed" }
|
||||
|
||||
@@ -128,7 +128,7 @@ impl RopeExt for Rope {
|
||||
fn offset_to_position(&self, offset: usize) -> Position {
|
||||
let point = self.offset_to_point(offset);
|
||||
let line = self.line(point.row as usize);
|
||||
let column = line.clip_offset(point.column as usize, zed_sum_tree::Bias::Left);
|
||||
let column = line.clip_offset(point.column as usize, sum_tree::Bias::Left);
|
||||
let character = line.slice(0..column).chars().count();
|
||||
Position::new(point.row, character as u32)
|
||||
}
|
||||
@@ -162,7 +162,7 @@ impl RopeExt for Rope {
|
||||
return None;
|
||||
}
|
||||
|
||||
let offset = self.clip_offset(offset, zed_sum_tree::Bias::Left);
|
||||
let offset = self.clip_offset(offset, sum_tree::Bias::Left);
|
||||
self.slice(offset..self.len()).chars().next()
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ impl RopeExt for Rope {
|
||||
return None;
|
||||
}
|
||||
|
||||
let offset = self.clip_offset(offset, zed_sum_tree::Bias::Left);
|
||||
let offset = self.clip_offset(offset, sum_tree::Bias::Left);
|
||||
|
||||
let mut left = String::new();
|
||||
for c in self.reversed_chars_at(offset) {
|
||||
|
||||
@@ -14,8 +14,8 @@ use lsp_types::Position;
|
||||
use rope::{OffsetUtf16, Rope};
|
||||
use serde::Deserialize;
|
||||
use smallvec::SmallVec;
|
||||
use sum_tree::Bias;
|
||||
use unicode_segmentation::*;
|
||||
use zed_sum_tree::Bias;
|
||||
|
||||
use super::blink_cursor::BlinkCursor;
|
||||
use super::change::Change;
|
||||
|
||||
Reference in New Issue
Block a user