This commit is contained in:
2026-03-09 13:58:49 +07:00
parent aec32e450a
commit 39c04cabad
16 changed files with 475 additions and 554 deletions

View File

@@ -5,15 +5,12 @@ use std::time::Duration;
use anyhow::{Context as AnyhowContext, Error, anyhow};
use gpui::{
App, AppContext, Context, Entity, Global, IntoElement, ParentElement, SharedString, Styled,
Subscription, Task, Window, div,
App, AppContext, Context, Entity, EventEmitter, Global, IntoElement, ParentElement,
SharedString, Styled, Task, Window, div,
};
use nostr_sdk::prelude::*;
use person::PersonRegistry;
use smallvec::{SmallVec, smallvec};
use state::{
Announcement, DEVICE_GIFTWRAP, DeviceState, NostrRegistry, RelayState, TIMEOUT, app_name,
};
use state::{Announcement, DEVICE_GIFTWRAP, DeviceState, NostrRegistry, TIMEOUT, app_name};
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
@@ -32,6 +29,15 @@ struct GlobalDeviceRegistry(Entity<DeviceRegistry>);
impl Global for GlobalDeviceRegistry {}
/// Device event.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum DeviceEvent {
/// A new encryption signer has been set
Set,
/// An error occurred
Error(SharedString),
}
/// Device Registry
///
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
@@ -42,11 +48,10 @@ pub struct DeviceRegistry {
/// Async tasks
tasks: Vec<Task<Result<(), Error>>>,
/// Subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
}
impl EventEmitter<DeviceEvent> for DeviceRegistry {}
impl DeviceRegistry {
/// Retrieve the global device registry state
pub fn global(cx: &App) -> Entity<Self> {
@@ -60,27 +65,16 @@ impl DeviceRegistry {
/// Create a new device registry instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let mut subscriptions = smallvec![];
let state = DeviceState::default();
subscriptions.push(
// Observe the NIP-65 state
cx.observe(&nostr, |this, state, cx| {
if state.read(cx).relay_list_state == RelayState::Configured {
this.get_announcement(cx);
};
}),
);
// Run at the end of current cycle
cx.defer_in(window, |this, window, cx| {
this.handle_notifications(window, cx);
this.get_announcement(cx);
});
Self {
state: DeviceState::default(),
state,
tasks: vec![],
_subscriptions: subscriptions,
}
}
@@ -123,30 +117,27 @@ impl DeviceRegistry {
Ok(())
}));
self.tasks.push(
// Update GPUI states
cx.spawn_in(window, async move |this, cx| {
while let Ok(event) = rx.recv_async().await {
match event.kind {
// New request event
Kind::Custom(4454) => {
this.update_in(cx, |this, window, cx| {
this.ask_for_approval(event, window, cx);
})?;
}
// New response event
Kind::Custom(4455) => {
this.update(cx, |this, cx| {
this.extract_encryption(event, cx);
})?;
}
_ => {}
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
while let Ok(event) = rx.recv_async().await {
match event.kind {
// New request event
Kind::Custom(4454) => {
this.update_in(cx, |this, window, cx| {
this.ask_for_approval(event, window, cx);
})?;
}
// New response event
Kind::Custom(4455) => {
this.update(cx, |this, cx| {
this.extract_encryption(event, cx);
})?;
}
_ => {}
}
}
Ok(())
}),
);
Ok(())
}));
}
/// Get the device state
@@ -191,45 +182,68 @@ impl DeviceRegistry {
fn get_messages(&mut self, cx: &mut Context<Self>) {
let task = self.subscribe_to_giftwrap_events(cx);
self.tasks.push(cx.spawn(async move |_this, _cx| {
task.await?;
// Update state
self.tasks.push(cx.spawn(async move |this, cx| {
if let Err(e) = task.await {
this.update(cx, |_this, cx| {
cx.emit(DeviceEvent::Error(SharedString::from(e.to_string())));
})?;
}
Ok(())
}));
}
/// Continuously get gift wrap events for the current user in their messaging relays
fn subscribe_to_giftwrap_events(&mut self, cx: &mut Context<Self>) -> Task<Result<(), Error>> {
/// Get the messaging relays for the current user
fn get_user_messaging_relays(&self, cx: &App) -> Task<Result<Vec<RelayUrl>, Error>> {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let Some(public_key) = signer.public_key() else {
return Task::ready(Err(anyhow!("User not found")));
};
cx.background_spawn(async move {
let public_key = signer.get_public_key().await?;
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let persons = PersonRegistry::global(cx);
let profile = persons.read(cx).get(&public_key, cx);
let relay_urls = profile.messaging_relays().clone();
if let Some(event) = client.database().query(filter).await?.first_owned() {
// Extract relay URLs from the event
let urls: Vec<RelayUrl> = nip17::extract_owned_relay_list(event).collect();
// Ensure all relays are connected
for url in urls.iter() {
client.add_relay(url).and_connect().await?;
}
Ok(urls)
} else {
Err(anyhow!("Relays not found"))
}
})
}
/// Continuously get gift wrap events for the current user in their messaging relays
fn subscribe_to_giftwrap_events(&self, cx: &App) -> Task<Result<(), Error>> {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let urls = self.get_user_messaging_relays(cx);
cx.background_spawn(async move {
let urls = urls.await?;
let encryption = signer.get_encryption_signer().await.context("not found")?;
let public_key = encryption.get_public_key().await?;
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
let id = SubscriptionId::new(DEVICE_GIFTWRAP);
// Construct target for subscription
let target: HashMap<RelayUrl, Filter> = relay_urls
let target: HashMap<RelayUrl, Filter> = urls
.into_iter()
.map(|relay| (relay, filter.clone()))
.collect();
let output = client.subscribe(target).with_id(id).await?;
log::info!(
"Successfully subscribed to encryption gift-wrap messages on: {:?}",
output.success
);
// Subscribe
client.subscribe(target).with_id(id).await?;
Ok(())
})
@@ -239,16 +253,14 @@ impl DeviceRegistry {
pub fn get_announcement(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let Some(public_key) = signer.public_key() else {
return;
};
// Reset state before fetching announcement
self.reset(cx);
let task: Task<Result<Event, Error>> = cx.background_spawn(async move {
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
// Construct the filter for the device announcement event
let filter = Filter::new()
.kind(Kind::Custom(10044))
@@ -262,18 +274,12 @@ impl DeviceRegistry {
.await?;
while let Some((_url, res)) = stream.next().await {
match res {
Ok(event) => {
log::info!("Received device announcement event: {event:?}");
return Ok(event);
}
Err(e) => {
log::error!("Failed to receive device announcement event: {e}");
}
if let Ok(event) = res {
return Ok(event);
}
}
Err(anyhow!("Device announcement not found"))
Err(anyhow!("Announcement not found"))
});
self.tasks.push(cx.spawn(async move |this, cx| {
@@ -436,7 +442,7 @@ impl DeviceRegistry {
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let app_keys = nostr.read(cx).app_keys.clone();
let app_keys = nostr.read(cx).keys();
let app_pubkey = app_keys.public_key();
let task: Task<Result<Option<Keys>, Error>> = cx.background_spawn(async move {
@@ -506,7 +512,7 @@ impl DeviceRegistry {
/// Parse the response event for device keys from other devices
fn extract_encryption(&mut self, event: Event, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let app_keys = nostr.read(cx).app_keys.clone();
let app_keys = nostr.read(cx).keys();
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
let root_device = event