This commit is contained in:
2026-08-06 16:00:00 +07:00
parent 0c6d700395
commit 640549a2c5
16 changed files with 426 additions and 438 deletions
+63 -40
View File
@@ -6,8 +6,8 @@ use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task};
use nostr_connect::prelude::*;
use nostr_sdk::client::SyncSummary;
use nostr_sdk::prelude::*;
use signed_core::{builders, filters};
use signed_nostr::{NostrBackend, SignedAuthUrlHandler, UniversalSigner, Update};
use signed_core::filters;
use signed_nostr::{SignedAuthUrlHandler, UniversalSigner, Update};
/// Keyring entry holding the user credential (`nsec1...` or `bunker://...`
/// with an embedded `?master=<nsec>` NIP-46 session key).
@@ -71,7 +71,8 @@ impl BackendEvent {
/// notification pump. Stores subscribe to [`BackendEvent`] and re-query the
/// local database when relevant updates arrive.
pub struct Backend {
inner: NostrBackend,
client: Client,
signer: UniversalSigner,
current_user: Option<PublicKey>,
connected: bool,
sync_progress: Option<(u64, u64)>,
@@ -94,11 +95,11 @@ impl Backend {
cx.set_global(GlobalBackend(entity));
}
pub(crate) fn new(inner: NostrBackend, cx: &mut Context<Self>) -> Self {
let client = inner.client();
pub(crate) fn new(client: Client, signer: UniversalSigner, cx: &mut Context<Self>) -> Self {
let pump_client = client.clone();
let pump = cx.spawn(async move |this, cx| {
let mut notifications = client.notifications();
let mut notifications = pump_client.notifications();
while let Some(notification) = notifications.next().await {
let ClientNotification::Event { event, .. } = notification else {
@@ -119,7 +120,8 @@ impl Backend {
});
let mut this = Self {
inner,
client,
signer,
current_user: None,
connected: false,
sync_progress: None,
@@ -133,16 +135,19 @@ impl Backend {
/// Bootstrap the client: connect to the default relays (indexers as
/// discovery-only) and restore the saved session, if any.
fn bootstrap(&mut self, cx: &mut Context<Self>) {
let backend = self.inner.clone();
let client = self.client.clone();
let task = cx.background_spawn(async move {
for url in BOOTSTRAP_RELAYS {
backend.add_relay(url).await?;
client.add_relay(url).await?;
}
for url in INDEXER_RELAYS {
backend.add_discovery_relay(url).await?;
client
.add_relay(url)
.capabilities(RelayCapabilities::DISCOVERY)
.await?;
}
backend.connect().await;
client.connect().await;
Ok::<(), Error>(())
});
@@ -276,7 +281,7 @@ impl Backend {
this.update(cx, |this, cx| {
// Become the new identity, so the publishes below are
// signed with the new keys.
this.inner.signer().swap_inner(keys);
this.signer.swap_inner(keys);
this.current_user = Some(public_key);
this.bootstrap_user(public_key, cx);
cx.emit(BackendEvent::SignerChanged);
@@ -317,7 +322,7 @@ impl Backend {
.map(|url| RelayUrl::parse(url).expect("valid relay URL"))
.collect();
this.send(builders::grasp_list(grasp_servers), cx);
this.send(GitUserGraspList { grasp_servers }.into_event_builder(), cx);
})?;
Ok(public_key)
@@ -441,7 +446,7 @@ impl Backend {
delete.await.ok();
this.update(cx, |this, cx| {
this.inner.signer().swap_inner(Keys::generate());
this.signer.swap_inner(Keys::generate());
this.current_user = None;
cx.emit(BackendEvent::SignerChanged);
cx.emit(BackendEvent::SignerRequired);
@@ -455,14 +460,11 @@ impl Backend {
/// Fetch the user's grasp list (kind `10317`) and add the listed grasp
/// servers as relays.
fn bootstrap_user(&mut self, public_key: PublicKey, cx: &mut Context<Self>) {
let backend = self.inner.clone();
let client = self.client.clone();
self.tasks.push(cx.spawn(async move |this, cx| {
let result = async {
let events = backend
.client()
.fetch_events(filters::grasp_list(public_key))
.await?;
let events = client.fetch_events(filters::grasp_list(public_key)).await?;
let urls: Vec<String> = events
.into_iter()
@@ -477,9 +479,9 @@ impl Backend {
.unwrap_or_default();
for url in urls {
backend.add_relay(&url).await.ok();
client.add_relay(&url).await.ok();
}
backend.connect().await;
client.connect().await;
Ok::<_, Error>(())
}
@@ -495,12 +497,12 @@ impl Backend {
/// Get the nostr client.
pub fn client(&self) -> Client {
self.inner.client()
self.client.clone()
}
/// Get the current signer.
pub fn signer(&self) -> UniversalSigner {
self.inner.signer()
self.signer.clone()
}
/// Get the current user's public key.
@@ -536,7 +538,7 @@ impl Backend {
match new_signer.get_public_key_async().await {
Ok(public_key) => {
this.update(cx, |this, cx| {
this.inner.signer().swap_inner(new_signer);
this.signer.swap_inner(new_signer);
this.current_user = Some(public_key);
this.bootstrap_user(public_key, cx);
cx.emit(BackendEvent::SignerChanged);
@@ -557,13 +559,13 @@ impl Backend {
/// Add relays and connect to them.
pub fn add_relays(&mut self, urls: Vec<String>, cx: &mut Context<Self>) {
let backend = self.inner.clone();
let client = self.client.clone();
let task = cx.background_spawn(async move {
for url in urls {
backend.add_relay(&url).await?;
client.add_relay(&url).await?;
}
backend.connect().await;
client.connect().await;
Ok::<(), Error>(())
});
@@ -587,13 +589,16 @@ impl Backend {
/// Add relays used only for discovery (e.g. NIP-65 indexers) and
/// connect to them. No subscriptions or writes are routed through them.
pub fn add_discovery_relays(&mut self, urls: Vec<String>, cx: &mut Context<Self>) {
let backend = self.inner.clone();
let client = self.client.clone();
let task = cx.background_spawn(async move {
for url in urls {
backend.add_discovery_relay(&url).await?;
client
.add_relay(&url)
.capabilities(RelayCapabilities::DISCOVERY)
.await?;
}
backend.connect().await;
client.connect().await;
Ok::<(), Error>(())
});
@@ -608,9 +613,9 @@ impl Backend {
/// Start a persistent subscription. Matching events are stored in the
/// database automatically and surface as [`BackendEvent::NostrUpdate`].
pub fn subscribe(&mut self, filter: Filter, cx: &mut Context<Self>) {
let backend = self.inner.clone();
let client = self.client.clone();
let task = cx.background_spawn(async move { backend.subscribe(filter).await.map(|_| ()) });
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 {
@@ -625,11 +630,10 @@ impl Backend {
/// in the database and surface as [`BackendEvent::NostrUpdate`] while the
/// subscription is open.
pub fn subscribe_bootstrap(&mut self, filters: Vec<Filter>, cx: &mut Context<Self>) {
let backend = self.inner.clone();
let client = self.client.clone();
let task = cx.background_spawn(async move {
subscribe_bootstrap_only(&backend.client(), filters).await
});
let task =
cx.background_spawn(async move { subscribe_bootstrap_only(&client, filters).await });
self.tasks.push(cx.spawn(async move |this, cx| {
if let Err(e) = task.await {
@@ -644,7 +648,7 @@ impl Backend {
/// Emits [`BackendEvent::SyncProgress`] while running (throttled to
/// whole-percent changes) and [`BackendEvent::Synced`] on completion.
pub fn sync_bootstrap(&mut self, filter: Filter, cx: &mut Context<Self>) {
let backend = self.inner.clone();
let client = self.client.clone();
self.sync_progress = Some((0, 0));
cx.notify();
@@ -681,7 +685,7 @@ impl Backend {
let task = cx.background_spawn(async move {
let opts = SyncOptions::default().progress(tx);
sync_bootstrap_only(&backend.client(), filter, opts).await
sync_bootstrap_only(&client, filter, opts).await
});
self.tasks.push(cx.spawn(async move |this, cx| {
@@ -721,8 +725,27 @@ impl Backend {
cx: &mut Context<Self>,
) -> flume::Receiver<Result<Event, Error>> {
let (tx, rx) = flume::bounded(1);
let backend = self.inner.clone();
let task = cx.background_spawn(async move { backend.send(builder).await });
let client = self.client.clone();
let signer = self.signer.clone();
let task = cx.background_spawn(async move {
// Sign with the current signer, broadcast, and save locally so
// the event is immediately visible to database queries.
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)
});
self.tasks.push(cx.spawn(async move |this, cx| {
let result = task.await;