wip
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
@@ -45,8 +46,6 @@ pub enum BackendEvent {
|
||||
PassphraseRequired,
|
||||
/// The signer changed on login, logout or account switch.
|
||||
SignerChanged,
|
||||
/// Relay bootstrap finished.
|
||||
Connected,
|
||||
/// A new event was received from a relay and stored in the database.
|
||||
NostrUpdate(Update),
|
||||
/// A negentropy sync completed.
|
||||
@@ -80,7 +79,6 @@ pub struct Backend {
|
||||
client: Client,
|
||||
signer: UniversalSigner,
|
||||
current_user: Option<PublicKey>,
|
||||
connected: bool,
|
||||
sync_progress: Option<(u64, u64)>,
|
||||
/// True when the stored credential is NIP-49 encrypted.
|
||||
passphrase_required: bool,
|
||||
@@ -151,7 +149,6 @@ impl Backend {
|
||||
client,
|
||||
signer,
|
||||
current_user: None,
|
||||
connected: false,
|
||||
sync_progress: None,
|
||||
passphrase_required: false,
|
||||
recent_fetches: HashMap::new(),
|
||||
@@ -186,11 +183,7 @@ impl Backend {
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
match task.await {
|
||||
Ok(()) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.connected = true;
|
||||
cx.emit(BackendEvent::Connected);
|
||||
cx.notify();
|
||||
})?;
|
||||
this.update(cx, |_this, cx| cx.notify())?;
|
||||
}
|
||||
Err(e) => {
|
||||
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||
@@ -993,22 +986,14 @@ impl Backend {
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let result = async {
|
||||
let events = client.fetch_events(filters::grasp_list(public_key)).await?;
|
||||
|
||||
let urls: Vec<String> = events
|
||||
let events: Vec<Event> = client
|
||||
.fetch_events(filters::grasp_list(public_key))
|
||||
.await?
|
||||
.into_iter()
|
||||
.max_by_key(|e| e.created_at)
|
||||
.map(|e| {
|
||||
e.tags
|
||||
.iter()
|
||||
.filter(|t| t.kind() == "g")
|
||||
.filter_map(|t| t.content().map(str::to_owned))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.collect();
|
||||
|
||||
for url in urls {
|
||||
client.add_relay(&url).await.ok();
|
||||
for url in latest_grasp_list_servers(events) {
|
||||
client.add_relay(url.as_str()).await.ok();
|
||||
}
|
||||
client.connect().await;
|
||||
|
||||
@@ -1049,11 +1034,6 @@ impl Backend {
|
||||
cx.emit(BackendEvent::error(message));
|
||||
}
|
||||
|
||||
/// Whether the relay bootstrap has completed.
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.connected
|
||||
}
|
||||
|
||||
/// Progress of the in-flight negentropy sync, if any.
|
||||
pub fn sync_progress(&self) -> Option<(u64, u64)> {
|
||||
self.sync_progress
|
||||
@@ -1106,11 +1086,7 @@ impl Backend {
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
match task.await {
|
||||
Ok(()) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.connected = true;
|
||||
cx.emit(BackendEvent::Connected);
|
||||
cx.notify();
|
||||
})?;
|
||||
this.update(cx, |_this, cx| cx.notify())?;
|
||||
}
|
||||
Err(e) => {
|
||||
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||
@@ -1120,43 +1096,6 @@ impl Backend {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Add discovery-only relays, e.g. NIP-65 indexers, and connect to them.
|
||||
pub fn add_discovery_relays(&mut self, urls: Vec<String>, cx: &mut Context<Self>) {
|
||||
let client = self.client.clone();
|
||||
|
||||
let task = cx.background_spawn(async move {
|
||||
for url in urls {
|
||||
client
|
||||
.add_relay(&url)
|
||||
.capabilities(RelayCapabilities::DISCOVERY)
|
||||
.await?;
|
||||
}
|
||||
client.connect().await;
|
||||
Ok::<(), Error>(())
|
||||
});
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = task.await {
|
||||
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Start a persistent subscription.
|
||||
pub fn subscribe(&mut self, filter: Filter, cx: &mut Context<Self>) {
|
||||
let client = self.client.clone();
|
||||
|
||||
let task = cx.background_spawn(async move { client.subscribe(filter).await.map(|_| ()) });
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
if let Err(e) = task.await {
|
||||
this.update(cx, |_this, cx| cx.emit(BackendEvent::error(e.to_string())))?;
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Whether an identical fetch started within [`FETCH_DEDUP_WINDOW`] is still recent.
|
||||
///
|
||||
/// Records the fingerprint when returning `false`, pruning expired entries first.
|
||||
@@ -1298,44 +1237,11 @@ impl Backend {
|
||||
let client = self.client.clone();
|
||||
let signer = self.signer.clone();
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
self.publish_task(cx, async move {
|
||||
// Sign with the current signer, broadcast and save locally.
|
||||
// The event is immediately visible to database queries.
|
||||
let work = cx.background_spawn(async move {
|
||||
let event = builder.finalize_async(&signer).await?;
|
||||
let output = client.send_event(&event).await?;
|
||||
|
||||
if output.success.is_empty() && !output.failed.is_empty() {
|
||||
let reasons = output
|
||||
.failed
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
return Err(anyhow!("event not accepted by any relay: {reasons}"));
|
||||
}
|
||||
|
||||
Ok(event)
|
||||
});
|
||||
|
||||
let result = work.await;
|
||||
|
||||
match &result {
|
||||
Ok(event) => {
|
||||
this.update(cx, |_this, cx| {
|
||||
cx.emit(BackendEvent::Published(Box::new(event.clone())));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
this.update(cx, |_this, cx| {
|
||||
cx.emit(BackendEvent::error(e.to_string()));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
let event = builder.finalize_async(&signer).await?;
|
||||
broadcast_event(&client, &event).await
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1346,25 +1252,17 @@ impl Backend {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Event, Error>> {
|
||||
let client = self.client.clone();
|
||||
self.publish_task(cx, async move { broadcast_event(&client, &event).await })
|
||||
}
|
||||
|
||||
/// Run `work` in the background, then emit its outcome as a [`BackendEvent`].
|
||||
fn publish_task(
|
||||
&mut self,
|
||||
cx: &mut Context<Self>,
|
||||
work: impl Future<Output = Result<Event, Error>> + 'static + Send,
|
||||
) -> Task<Result<Event, Error>> {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let work = cx.background_spawn(async move {
|
||||
let output = client.send_event(&event).await?;
|
||||
|
||||
if output.success.is_empty() && !output.failed.is_empty() {
|
||||
let reasons = output
|
||||
.failed
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
return Err(anyhow!("event not accepted by any relay: {reasons}"));
|
||||
}
|
||||
|
||||
Ok(event.clone())
|
||||
});
|
||||
|
||||
let result = work.await;
|
||||
let result = cx.background_spawn(work).await;
|
||||
|
||||
match &result {
|
||||
Ok(event) => {
|
||||
@@ -1385,15 +1283,6 @@ impl Backend {
|
||||
})
|
||||
}
|
||||
|
||||
/// Publish a NIP-34 repository announcement, kind 30617, with the current signer.
|
||||
pub fn publish_announcement(
|
||||
&mut self,
|
||||
announcement: GitRepositoryAnnouncement,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Event, Error>> {
|
||||
self.send(announcement.into_event_builder(), cx)
|
||||
}
|
||||
|
||||
/// Sign, broadcast and store an event without awaiting the result.
|
||||
fn send_fire_and_forget(&mut self, builder: EventBuilder, cx: &mut Context<Self>) {
|
||||
let task = self.send(builder, cx);
|
||||
@@ -1433,6 +1322,25 @@ impl Backend {
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast an event and fail when no relay accepted it.
|
||||
///
|
||||
/// The client stores accepted events locally, visible to database queries.
|
||||
async fn broadcast_event(client: &Client, event: &Event) -> Result<Event, Error> {
|
||||
let output = client.send_event(event).await?;
|
||||
|
||||
if output.success.is_empty() && !output.failed.is_empty() {
|
||||
let reasons = output
|
||||
.failed
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
return Err(anyhow!("event not accepted by any relay: {reasons}"));
|
||||
}
|
||||
|
||||
Ok(event.clone())
|
||||
}
|
||||
|
||||
/// Fingerprint of a relay and filter set, for fetch dedup.
|
||||
///
|
||||
/// Relays and filters are sorted first, so the fingerprint is order-independent.
|
||||
@@ -1603,8 +1511,8 @@ fn latest_grasp_list_servers(events: Vec<Event>) -> Vec<RelayUrl> {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Resolve the user's published grasp servers.
|
||||
pub(crate) async fn user_grasp_list_servers(
|
||||
/// Resolve the user's published grasp servers from the local database.
|
||||
pub async fn user_grasp_list_servers(
|
||||
client: Client,
|
||||
user: PublicKey,
|
||||
) -> Result<Vec<RelayUrl>, Error> {
|
||||
|
||||
Reference in New Issue
Block a user