diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 21cfd8f..d3dda01 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -2,6 +2,8 @@ use std::cell::Cell; use std::collections::HashSet; use std::path::PathBuf; use std::rc::Rc; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; use anyhow::{Context as AnyhowContext, Error, anyhow}; @@ -13,7 +15,7 @@ use nostr_sdk::prelude::*; use person::PersonRegistry; use settings::AppSettings; use smallvec::{SmallVec, smallvec}; -use state::{Announcement, CLIENT_NAME, NostrRegistry, StateEvent, TIMEOUT}; +use state::{Announcement, CLIENT_NAME, NostrRegistry, StateEvent}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::Button; @@ -35,10 +37,10 @@ impl Global for GlobalDeviceRegistry {} pub enum DeviceEvent { /// A new encryption signer has been set Set, + /// User have not setup encryption key + NotSet, /// The device is requesting an encryption key Requesting, - /// The device is creating a new encryption key - Creating, /// An error occurred Error(SharedString), } @@ -57,12 +59,12 @@ impl DeviceEvent { /// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md #[derive(Debug)] pub struct DeviceRegistry { - /// Whether the registry is currently initializing - pub initializing: bool, - /// Whether there is a pending request for encryption key approval pub pending_request: bool, + /// Whether an announcement has been made for this device + pub announcement_existed: Arc, + /// Async tasks tasks: Vec>>, @@ -114,8 +116,8 @@ impl DeviceRegistry { }); Self { - initializing: true, pending_request: false, + announcement_existed: Arc::new(AtomicBool::new(false)), tasks: vec![], _subscriptions: subscriptions, } @@ -124,9 +126,13 @@ impl DeviceRegistry { fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); + let signer = nostr.read(cx).signer(); + + let announcement_existed = self.announcement_existed.clone(); let (tx, rx) = flume::bounded::(100); self.tasks.push(cx.background_spawn(async move { + let current_user = signer.get_public_key().await?; let mut notifications = client.notifications(); let mut processed_events = HashSet::new(); @@ -140,13 +146,19 @@ impl DeviceRegistry { } match event.kind { + Kind::Custom(10044) => { + if current_user == event.pubkey { + announcement_existed.store(true, Ordering::Relaxed); + tx.send_async(event.into_owned()).await?; + } + } Kind::Custom(4454) => { - if verify_author(&client, event.as_ref()).await { + if current_user == event.pubkey { tx.send_async(event.into_owned()).await?; } } Kind::Custom(4455) => { - if verify_author(&client, event.as_ref()).await { + if current_user == event.pubkey { tx.send_async(event.into_owned()).await?; } } @@ -161,6 +173,11 @@ impl DeviceRegistry { self.tasks.push(cx.spawn_in(window, async move |this, cx| { while let Ok(event) = rx.recv_async().await { match event.kind { + Kind::Custom(10044) => { + this.update_in(cx, |this, _window, cx| { + this.set_encryption(&event, cx); + })?; + } // New request event from other device Kind::Custom(4454) => { this.update_in(cx, |this, window, cx| { @@ -180,12 +197,6 @@ impl DeviceRegistry { })); } - /// Set whether the registry is currently initializing - fn set_initializing(&mut self, initializing: bool, cx: &mut Context) { - self.initializing = initializing; - cx.notify(); - } - /// Set whether there is a pending request for encryption key approval fn set_pending_request(&mut self, pending: bool, cx: &mut Context) { self.pending_request = pending; @@ -203,9 +214,8 @@ impl DeviceRegistry { self.tasks.push(cx.spawn(async move |this, cx| { signer.set_encryption_signer(new).await; - // Update state - this.update(cx, |this, cx| { - this.set_initializing(false, cx); + // Notify the UI via event + this.update(cx, |_this, cx| { cx.emit(DeviceEvent::Set); })?; @@ -232,13 +242,11 @@ impl DeviceRegistry { pub fn get_announcement(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); + let signer = nostr.read(cx).signer(); - // Show the loading state - self.set_initializing(true, cx); - - let task: Task> = cx.background_spawn(async move { - let signer = client.signer().context("Signer not found")?; + self.tasks.push(cx.background_spawn(async move { let public_key = signer.get_public_key().await?; + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); // Construct the filter for the device announcement event let filter = Filter::new() @@ -246,35 +254,35 @@ impl DeviceRegistry { .author(public_key) .limit(1); - // Stream events from user's write relays - let mut stream = client - .stream_events(filter) - .timeout(Duration::from_secs(TIMEOUT)) + client + .subscribe(filter) + .close_on(opts) + .with_id(SubscriptionId::new("nip4e")) .await?; - while let Some((_url, res)) = stream.next().await { - if let Ok(event) = res { - return Ok(event); - } - } + Ok(()) + })); - Err(anyhow!("Announcement not found")) - }); + let announcement_existed = self.announcement_existed.clone(); self.tasks.push(cx.spawn(async move |this, cx| { - match task.await { - Ok(event) => { - // Set encryption key from the announcement event - this.update(cx, |this, cx| { - this.set_encryption(&event, cx); - })?; - } - Err(_) => { - // User has no announcement, create a new one - this.update(cx, |this, cx| { - this.set_announcement(Keys::generate(), cx); - })?; - } + if !cx + .background_spawn(async move { + // Wait for 5 seconds + smol::Timer::after(Duration::from_secs(5)).await; + + // Then check if the msg relays have been found + if !announcement_existed.load(Ordering::Acquire) { + return true; + } + + false + }) + .await + { + this.update(cx, |_this, cx| { + cx.emit(DeviceEvent::NotSet); + })?; } Ok(()) @@ -285,9 +293,6 @@ impl DeviceRegistry { pub fn set_announcement(&mut self, keys: Keys, cx: &mut Context) { let task = self.create_encryption(keys, cx); - // Notify that we're creating a new encryption key - cx.emit(DeviceEvent::Creating); - self.tasks.push(cx.spawn(async move |this, cx| { match task.await { Ok(keys) => { @@ -446,10 +451,7 @@ impl DeviceRegistry { } Ok(None) => { this.update(cx, |this, cx| { - this.set_initializing(false, cx); this.wait_for_approval(cx); - - cx.emit(DeviceEvent::Requesting); })?; } Err(e) => { @@ -468,6 +470,8 @@ impl DeviceRegistry { let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); + cx.emit(DeviceEvent::Requesting); + self.tasks.push(cx.background_spawn(async move { let public_key = signer.get_public_key().await?; @@ -708,16 +712,6 @@ impl DeviceRegistry { struct DeviceNotification; -/// Verify the author of an event -async fn verify_author(client: &Client, event: &Event) -> bool { - if let Some(signer) = client.signer() - && let Ok(public_key) = signer.get_public_key().await - { - return public_key == event.pubkey; - } - false -} - /// Encrypt and store device keys in the local database. async fn set_keys(client: &Client, secret: &str) -> Result<(), Error> { let signer = client.signer().context("Signer not found")?; diff --git a/desktop/src/workspace.rs b/desktop/src/workspace.rs index 0a001f4..0ef6318 100644 --- a/desktop/src/workspace.rs +++ b/desktop/src/workspace.rs @@ -146,7 +146,7 @@ impl Workspace { match event { DeviceEvent::Requesting => { const MSG: &str = - "Coop has sent a request for an encryption key. Please open the other client then approve the request."; + "Please open other client and approve the request for encryption key."; let note = Notification::new() .id::() @@ -157,12 +157,25 @@ impl Workspace { window.push_notification(note, cx); } - DeviceEvent::Creating => { + DeviceEvent::NotSet => { + const MSG: &str = + "User're not setup encryption key yet. Do you want to create one?"; + let note = Notification::new() .id::() - .autohide(false) - .message("Creating encryption key") - .with_kind(NotificationKind::Info); + .message(MSG) + .with_kind(NotificationKind::Info) + .action(|_this, _window, _cx| { + Button::new("retry").label("Retry").on_click( + move |_this, window, cx| { + let device = DeviceRegistry::global(cx); + device.update(cx, |this, cx| { + this.set_announcement(Keys::generate(), cx); + }); + window.clear_notification::(cx); + }, + ) + }); window.push_notification(note, cx); } @@ -664,9 +677,6 @@ impl Workspace { let trash_messages = chat.read(cx).count_trash_messages(cx); let is_nip4e_enabled = AppSettings::get_encryption_key(cx); - let device = DeviceRegistry::global(cx); - let device_initializing = device.read(cx).initializing; - let nostr = NostrRegistry::global(cx); let signer = nostr.read(cx).signer(); @@ -720,12 +730,6 @@ impl Workspace { .tooltip("Decoupled encryption key") .small() .ghost() - .loading(device_initializing) - .when(device_initializing, |this| { - this.label("Dekey") - .xsmall() - .tooltip("Loading decoupled encryption key...") - }) .dropdown_menu(move |this, _window, _cx| { this.min_w(px(260.)) .label("Encryption Key")