diff --git a/Cargo.lock b/Cargo.lock index a5e8e7c..3ebe66f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1644,7 +1644,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "proc-macro2", "quote", @@ -2599,7 +2599,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "async-channel 2.5.0", @@ -2678,7 +2678,7 @@ dependencies = [ [[package]] name = "gpui_linux" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2726,7 +2726,7 @@ dependencies = [ [[package]] name = "gpui_macos" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "async-task", @@ -2768,7 +2768,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2779,7 +2779,7 @@ dependencies = [ [[package]] name = "gpui_platform" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "console_error_panic_hook", "gpui", @@ -2792,7 +2792,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "gpui", @@ -2803,7 +2803,7 @@ dependencies = [ [[package]] name = "gpui_util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "log", @@ -2812,7 +2812,7 @@ dependencies = [ [[package]] name = "gpui_web" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "console_error_panic_hook", @@ -2835,7 +2835,7 @@ dependencies = [ [[package]] name = "gpui_wgpu" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "bytemuck", @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "gpui_windows" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "collections", @@ -3107,7 +3107,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "async-compression", @@ -3132,7 +3132,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3668,12 +3668,13 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ "bitflags 2.11.0", "libc", + "plain", "redox_syscall 0.7.3", ] @@ -3893,7 +3894,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "bindgen", @@ -4120,7 +4121,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr" version = "0.44.1" -source = "git+https://github.com/rust-nostr/nostr#860e239ff894b8471b37c84dcac12b9572b8f064" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" dependencies = [ "aes", "base64", @@ -4144,7 +4145,7 @@ dependencies = [ [[package]] name = "nostr-blossom" version = "0.44.0" -source = "git+https://github.com/rust-nostr/nostr#860e239ff894b8471b37c84dcac12b9572b8f064" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" dependencies = [ "base64", "nostr", @@ -4155,7 +4156,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.44.0" -source = "git+https://github.com/rust-nostr/nostr#860e239ff894b8471b37c84dcac12b9572b8f064" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" dependencies = [ "async-utility", "futures-core", @@ -4168,7 +4169,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.44.0" -source = "git+https://github.com/rust-nostr/nostr#860e239ff894b8471b37c84dcac12b9572b8f064" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" dependencies = [ "btreecap", "flatbuffers", @@ -4178,7 +4179,7 @@ dependencies = [ [[package]] name = "nostr-gossip" version = "0.44.0" -source = "git+https://github.com/rust-nostr/nostr#860e239ff894b8471b37c84dcac12b9572b8f064" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" dependencies = [ "nostr", ] @@ -4186,7 +4187,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.44.0" -source = "git+https://github.com/rust-nostr/nostr#860e239ff894b8471b37c84dcac12b9572b8f064" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" dependencies = [ "async-utility", "flume", @@ -4200,7 +4201,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.44.1" -source = "git+https://github.com/rust-nostr/nostr#860e239ff894b8471b37c84dcac12b9572b8f064" +source = "git+https://github.com/rust-nostr/nostr#9bcc6cd779a7c6eb41509b37aee4575fa5ae47b9" dependencies = [ "async-utility", "async-wsocket", @@ -4637,7 +4638,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "collections", "serde", @@ -4770,6 +4771,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "png" version = "0.17.16" @@ -5318,7 +5325,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "derive_refineable", ] @@ -5417,7 +5424,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "bytes", @@ -5472,7 +5479,7 @@ dependencies = [ [[package]] name = "rope" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "arrayvec", "log", @@ -5734,7 +5741,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "async-task", "backtrace", @@ -6328,7 +6335,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "arrayvec", "log", @@ -7271,7 +7278,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "async-fs", @@ -7310,7 +7317,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "perf", "quote", @@ -9113,7 +9120,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "anyhow", "chrono", @@ -9130,7 +9137,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" dependencies = [ "tracing", "tracing-subscriber", @@ -9141,7 +9148,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#b06522e978b5ed24bcc2cf07a6de794179d69176" +source = "git+https://github.com/zed-industries/zed#4668aeb7284780cca830ba1173c4d3eb8bd11e2b" [[package]] name = "zune-core" diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index d9971c5..d6f0f45 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -262,9 +262,12 @@ impl ChatRegistry { pub fn get_contact_list(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return; + }; + let write_relays = nostr.read(cx).write_relays(&public_key, cx); let task: Task> = cx.background_spawn(async move { @@ -318,9 +321,12 @@ impl ChatRegistry { fn verify_relays(&mut self, cx: &mut Context) -> Task> { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return Task::ready(Err(anyhow!("User not found"))); + }; + let write_relays = nostr.read(cx).write_relays(&public_key, cx); cx.background_spawn(async move { diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index 97e3a55..af0fdf4 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -331,7 +331,7 @@ impl Room { let client = nostr.read(cx).client(); let signer = nostr.read(cx).signer(); - let sender = signer.public_key().unwrap(); + let sender = signer.public_key(); // Get room's id let id = self.id; @@ -340,7 +340,7 @@ impl Room { let members: Vec = self .members .iter() - .filter(|public_key| public_key != &&sender) + .filter(|public_key| Some(**public_key) != sender) .copied() .collect(); diff --git a/crates/coop/src/panels/greeter.rs b/crates/coop/src/panels/greeter.rs index 5ad7e1c..44276fb 100644 --- a/crates/coop/src/panels/greeter.rs +++ b/crates/coop/src/panels/greeter.rs @@ -11,7 +11,7 @@ use ui::dock_area::dock::DockPlacement; use ui::dock_area::panel::{Panel, PanelEvent}; use ui::{h_flex, v_flex, Icon, IconName, Sizable, StyledExt}; -use crate::panels::{connect, import, messaging_relays, profile, relay_list}; +use crate::panels::{messaging_relays, profile, relay_list}; use crate::workspace::Workspace; pub fn init(window: &mut Window, cx: &mut App) -> Entity { @@ -88,9 +88,6 @@ impl Render for GreeterPanel { let nostr = NostrRegistry::global(cx); let nip65 = nostr.read(cx).relay_list_state(); - let signer = nostr.read(cx).signer(); - let owned = signer.owned(); - let required_actions = nip65 == RelayState::NotConfigured || nip17 == InboxState::RelayNotAvailable; @@ -191,60 +188,48 @@ impl Render for GreeterPanel { ), ) }) - .when(!owned, |this| { - this.child( - v_flex() - .gap_2() - .w_full() - .child( - h_flex() - .gap_2() - .w_full() - .text_xs() - .font_semibold() - .text_color(cx.theme().text_muted) - .child(SharedString::from("Use your own identity")) - .child(div().flex_1().h_px().bg(cx.theme().border)), - ) - .child( - v_flex() - .gap_2() - .w_full() - .child( - Button::new("connect") - .icon(Icon::new(IconName::Door)) - .label("Connect account via Nostr Connect") - .ghost() - .small() - .justify_start() - .on_click(move |_ev, window, cx| { - Workspace::add_panel( - connect::init(window, cx), - DockPlacement::Center, - window, - cx, - ); - }), - ) - .child( - Button::new("import") - .icon(Icon::new(IconName::Usb)) - .label("Import a secret key or bunker") - .ghost() - .small() - .justify_start() - .on_click(move |_ev, window, cx| { - Workspace::add_panel( - import::init(window, cx), - DockPlacement::Center, - window, - cx, - ); - }), - ), - ), - ) - }) + .child( + v_flex() + .gap_2() + .w_full() + .child( + h_flex() + .gap_2() + .w_full() + .text_xs() + .font_semibold() + .text_color(cx.theme().text_muted) + .child(SharedString::from("Use your own identity")) + .child(div().flex_1().h_px().bg(cx.theme().border)), + ) + .child( + v_flex() + .gap_2() + .w_full() + .child( + Button::new("connect") + .icon(Icon::new(IconName::Door)) + .label("Connect account via Nostr Connect") + .ghost() + .small() + .justify_start() + .on_click(move |_ev, window, cx| { + // + }), + ) + .child( + Button::new("import") + .icon(Icon::new(IconName::Usb)) + .label("Import a secret key or bunker") + .ghost() + .small() + .justify_start() + .on_click(move |_ev, window, cx| { + // + }), + ), + ), + ) .child( v_flex() .gap_2() diff --git a/crates/coop/src/panels/mod.rs b/crates/coop/src/panels/mod.rs index c1425d4..bb47e07 100644 --- a/crates/coop/src/panels/mod.rs +++ b/crates/coop/src/panels/mod.rs @@ -1,8 +1,6 @@ pub mod backup; -pub mod connect; pub mod contact_list; pub mod greeter; -pub mod import; pub mod messaging_relays; pub mod profile; pub mod relay_list; diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 2d84fb5..375bb92 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -204,9 +204,11 @@ impl DeviceRegistry { fn subscribe_to_giftwrap_events(&mut self, cx: &mut Context) -> Task> { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return Task::ready(Err(anyhow!("User not found"))); + }; let persons = PersonRegistry::global(cx); let profile = persons.read(cx).get(&public_key, cx); @@ -237,9 +239,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(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return; + }; // Reset state before fetching announcement self.reset(cx); @@ -303,10 +307,11 @@ impl DeviceRegistry { pub fn create_encryption(&self, cx: &App) -> Task> { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - - // Get current user let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return Task::ready(Err(anyhow!("User not found"))); + }; // Get user's write relays let write_relays = nostr.read(cx).write_relays(&public_key, cx); @@ -398,9 +403,11 @@ impl DeviceRegistry { pub fn listen_request(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return; + }; let write_relays = nostr.read(cx).write_relays(&public_key, cx); @@ -430,9 +437,11 @@ impl DeviceRegistry { fn listen_approval(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return; + }; let write_relays = nostr.read(cx).write_relays(&public_key, cx); @@ -460,9 +469,11 @@ impl DeviceRegistry { fn request(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return; + }; let write_relays = nostr.read(cx).write_relays(&public_key, cx); @@ -573,10 +584,11 @@ impl DeviceRegistry { fn approve(&mut self, event: &Event, window: &mut Window, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - - // Get current user let signer = nostr.read(cx).signer(); - let public_key = signer.public_key().unwrap(); + + let Some(public_key) = signer.public_key() else { + return; + }; // Get user's write relays let write_relays = nostr.read(cx).write_relays(&public_key, cx); diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index e4aa402..e718f93 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -52,7 +52,7 @@ pub struct NostrRegistry { signer: Arc, /// Local public keys - npubs: Entity>, + npubs: Entity>, /// App keys /// @@ -93,7 +93,7 @@ impl NostrRegistry { let signer = Arc::new(CoopSigner::new(app_keys.clone())); // Construct the nostr npubs entity - let npubs = cx.new(|_| HashSet::new()); + let npubs = cx.new(|_| vec![]); // Construct the gossip entity let gossip = cx.new(|_| Gossip::default()); @@ -121,7 +121,6 @@ impl NostrRegistry { cx.defer_in(window, |this, _window, cx| { this.connect(cx); this.handle_notifications(cx); - this.get_npubs(cx); }); Self { @@ -137,43 +136,48 @@ impl NostrRegistry { } } - /// Get all used npubs - fn get_npubs(&mut self, cx: &mut Context) { - self.tasks.push(cx.spawn(async move |this, cx| { - let task: Result, Error> = cx - .background_executor() - .await_on_background(async move { - let dir = config_dir().join("keys"); - // Ensure keys directory exists - smol::fs::create_dir_all(&dir).await?; - - let mut files = smol::fs::read_dir(&dir).await?; - let mut npubs: HashSet = HashSet::new(); - - while let Some(Ok(entry)) = files.next().await { - let name = entry.file_name().into_string().unwrap(); - let public_key = PublicKey::parse(&name)?; - - npubs.insert(public_key); - } - - Ok(npubs) - }) - .await; - - if let Ok(npubs) = task { - this.update(cx, |this, cx| { - this.npubs.update(cx, |this, cx| { - this.extend(npubs); - cx.notify(); - }); - })?; - } - - Ok(()) - })); + /// Get the nostr client + pub fn client(&self) -> Client { + self.client.clone() } + /// Get the nostr signer + pub fn signer(&self) -> Arc { + self.signer.clone() + } + + /// Get the app keys + pub fn app_keys(&self) -> &Keys { + &self.app_keys + } + + /// Get the connected status of the client + pub fn connected(&self) -> bool { + self.connected + } + + /// Get the creating status + pub fn creating(&self) -> bool { + self.creating + } + + /// Get the relay list state + pub fn relay_list_state(&self) -> RelayState { + self.relay_list_state.clone() + } + + /// Get all relays for a given public key without ensuring connections + pub fn read_only_relays(&self, public_key: &PublicKey, cx: &App) -> Vec { + self.gossip.read(cx).read_only_relays(public_key) + } + + /// Set the connected status of the client + fn set_connected(&mut self, cx: &mut Context) { + self.connected = true; + cx.notify(); + } + + /// Connect to the bootstrapping relays fn connect(&mut self, cx: &mut Context) { let client = self.client(); @@ -198,7 +202,7 @@ impl NostrRegistry { // Update the state this.update(cx, |this, cx| { this.set_connected(cx); - this.get_signer(cx); + this.get_npubs(cx); })?; Ok(()) @@ -259,39 +263,370 @@ impl NostrRegistry { })); } - /// Get the nostr client - pub fn client(&self) -> Client { - self.client.clone() + /// Get all used npubs + fn get_npubs(&mut self, cx: &mut Context) { + let npubs = self.npubs.downgrade(); + let task: Task, Error>> = cx.background_spawn(async move { + let dir = config_dir().join("keys"); + // Ensure keys directory exists + smol::fs::create_dir_all(&dir).await?; + + let mut files = smol::fs::read_dir(&dir).await?; + let mut entries = Vec::new(); + + while let Some(Ok(entry)) = files.next().await { + let metadata = entry.metadata().await?; + let modified_time = metadata.modified()?; + let name = entry + .file_name() + .into_string() + .unwrap() + .replace(".npub", ""); + + entries.push((modified_time, name)); + } + + // Sort by modification time (most recent first) + entries.sort_by(|a, b| b.0.cmp(&a.0)); + + let mut npubs = Vec::new(); + + for (_, name) in entries { + let public_key = PublicKey::parse(&name)?; + npubs.push(public_key); + } + + Ok(npubs) + }); + + self.tasks.push(cx.spawn(async move |this, cx| { + match task.await { + Ok(public_keys) => match public_keys.is_empty() { + true => { + this.update(cx, |this, cx| { + this.create_new_signer(cx); + })?; + } + false => { + npubs.update(cx, |this, cx| { + this.extend(public_keys); + cx.notify(); + })?; + } + }, + Err(e) => { + log::error!("Failed to get npubs: {e}"); + this.update(cx, |this, cx| { + this.create_new_signer(cx); + })?; + } + } + Ok(()) + })); } - /// Get the nostr signer - pub fn signer(&self) -> Arc { - self.signer.clone() + /// Set whether Coop is creating a new signer + fn set_creating(&mut self, creating: bool, cx: &mut Context) { + self.creating = creating; + cx.notify(); } - /// Get the app keys - pub fn app_keys(&self) -> &Keys { - &self.app_keys + /// Create a new identity + pub fn create_new_signer(&mut self, cx: &mut Context) { + let client = self.client(); + let keys = Keys::generate(); + let async_keys = keys.clone(); + + let username = keys.public_key().to_bech32().unwrap(); + let secret = keys.secret_key().to_secret_bytes(); + + // Create a write credential task + let write_credential = cx.write_credentials(&username, &username, &secret); + + // Set the creating signer status + self.set_creating(true, cx); + + // Run async tasks in background + let task: Task> = cx.background_spawn(async move { + let signer = async_keys.into_nostr_signer(); + + // Get default relay list + let relay_list = default_relay_list(); + + // Publish relay list event + let event = EventBuilder::relay_list(relay_list).sign(&signer).await?; + client + .send_event(&event) + .ok_timeout(Duration::from_secs(TIMEOUT)) + .await?; + + // Construct the default metadata + let name = petname::petname(2, "-").unwrap_or("Cooper".to_string()); + let avatar = Url::parse(&format!("https://avatar.vercel.sh/{name}")).unwrap(); + let metadata = Metadata::new().display_name(&name).picture(avatar); + + // Publish metadata event + let event = EventBuilder::metadata(&metadata).sign(&signer).await?; + client + .send_event(&event) + .ack_policy(AckPolicy::none()) + .await?; + + // Construct the default contact list + let contacts = vec![Contact::new(PublicKey::parse(COOP_PUBKEY).unwrap())]; + + // Publish contact list event + let event = EventBuilder::contact_list(contacts).sign(&signer).await?; + client + .send_event(&event) + .ack_policy(AckPolicy::none()) + .await?; + + // Construct the default messaging relay list + let relays = default_messaging_relays(); + + // Publish messaging relay list event + let event = EventBuilder::nip17_relay_list(relays).sign(&signer).await?; + client + .send_event(&event) + .ack_policy(AckPolicy::none()) + .await?; + + // Write user's credentials to the system keyring + write_credential.await?; + + Ok(()) + }); + + self.tasks.push(cx.spawn(async move |this, cx| { + // Wait for the task to complete + task.await?; + + this.update(cx, |this, cx| { + this.set_creating(false, cx); + this.set_signer(keys, cx); + })?; + + Ok(()) + })); } - /// Get the connected status of the client - pub fn connected(&self) -> bool { - self.connected + // Get the signer in keyring by username + pub fn get_signer( + &mut self, + username: &str, + cx: &mut Context, + ) -> Task, Error>> { + let app_keys = self.app_keys().clone(); + let read_credential = cx.read_credentials(username); + + cx.spawn(async move |_this, _cx| { + let (_, secret) = read_credential + .await + .map_err(|_| anyhow!("Failed to get signer"))? + .ok_or_else(|| anyhow!("Failed to get signer"))?; + + // Try to parse as a direct secret key first + if let Ok(secret_key) = SecretKey::from_slice(&secret) { + return Ok(Keys::new(secret_key).into_nostr_signer()); + } + + // Convert the secret into string + let sec = String::from_utf8(secret) + .map_err(|_| anyhow!("Failed to parse secret as UTF-8"))?; + + // Try to parse as a NIP-46 URI + let uri = + NostrConnectUri::parse(&sec).map_err(|_| anyhow!("Failed to parse NIP-46 URI"))?; + + let timeout = Duration::from_secs(120); + let nip46 = NostrConnect::new(uri, app_keys, timeout, None)?; + + Ok(nip46.into_nostr_signer()) + }) } - /// Get the creating status - pub fn creating(&self) -> bool { - self.creating + /// Set the signer for the nostr client and verify the public key + pub fn set_signer(&mut self, new: T, cx: &mut Context) + where + T: NostrSigner + 'static, + { + let client = self.client(); + let signer = self.signer(); + + // Create a task to update the signer and verify the public key + let task: Task> = cx.background_spawn(async move { + // Update signer and unsubscribe + signer.switch(new).await; + client.unsubscribe_all().await?; + + // Verify and save public key + let signer = client.signer().context("Signer not found")?; + let public_key = signer.get_public_key().await?; + + let npub = public_key.to_bech32().unwrap(); + let keys_dir = config_dir().join("keys"); + + // Ensure keys directory exists + smol::fs::create_dir_all(&keys_dir).await?; + + let key_path = keys_dir.join(format!("{}.npub", npub)); + smol::fs::write(key_path, "").await?; + + log::info!("Signer's public key: {}", public_key); + Ok(public_key) + }); + + self.tasks.push(cx.spawn(async move |this, cx| { + // set signer + let public_key = task.await?; + + // Update states + this.update(cx, |this, cx| { + this.npubs.update(cx, |this, cx| { + if !this.contains(&public_key) { + this.push(public_key); + cx.notify(); + } + }); + this.ensure_relay_list(cx); + })?; + + Ok(()) + })); } - /// Get the relay list state - pub fn relay_list_state(&self) -> RelayState { - self.relay_list_state.clone() + /// Add a key signer to keyring + pub fn add_key_signer( + &mut self, + keys: &Keys, + cx: &mut Context, + ) -> Task> { + let keys = keys.clone(); + let username = keys.public_key().to_bech32().unwrap(); + let secret = keys.secret_key().to_secret_bytes(); + + // Write the credential to the keyring + let write_credential = cx.write_credentials(&username, &username, &secret); + + cx.spawn(async move |this, cx| { + match write_credential.await { + Ok(_) => { + this.update(cx, |this, cx| { + this.set_signer(keys, cx); + })?; + } + Err(e) => return Err(anyhow!("Failed to write credential: {e}")), + } + Ok(()) + }) } - /// Get all relays for a given public key without ensuring connections - pub fn read_only_relays(&self, public_key: &PublicKey, cx: &App) -> Vec { - self.gossip.read(cx).read_only_relays(public_key) + /// Add a nostr connect signer to keyring + pub fn add_nip46_signer( + &mut self, + nip46: &NostrConnect, + cx: &mut Context, + ) -> Task> { + let nip46 = nip46.clone(); + let async_nip46 = nip46.clone(); + + // Connect and verify the remote signer + let task: Task> = + cx.background_spawn(async move { + let public_key = async_nip46.get_public_key().await?; + let uri = async_nip46.bunker_uri().await?; + + Ok((public_key, uri)) + }); + + cx.spawn(async move |this, cx| { + match task.await { + Ok((public_key, uri)) => { + let username = public_key.to_bech32().unwrap(); + let write_credential = this.read_with(cx, |_this, cx| { + cx.write_credentials(&username, &username, uri.to_string().as_bytes()) + })?; + + match write_credential.await { + Ok(_) => { + this.update(cx, |this, cx| { + this.set_signer(nip46, cx); + })?; + } + Err(e) => return Err(anyhow!("Failed to write credential: {e}")), + } + } + Err(e) => return Err(anyhow!("Failed to connect to the remote signer: {e}")), + } + Ok(()) + }) + } + + /// Set the state of the relay list + fn set_relay_state(&mut self, state: RelayState, cx: &mut Context) { + self.relay_list_state = state; + cx.notify(); + } + + pub fn ensure_relay_list(&mut self, cx: &mut Context) { + let task = self.verify_relay_list(cx); + + // Set the state to idle before starting the task + self.set_relay_state(RelayState::default(), cx); + + self.tasks.push(cx.spawn(async move |this, cx| { + let result = task.await?; + + // Update state + this.update(cx, |this, cx| { + this.relay_list_state = result; + cx.notify(); + })?; + + Ok(()) + })); + } + + // Verify relay list for current user + fn verify_relay_list(&mut self, cx: &mut Context) -> Task> { + let client = self.client(); + + cx.background_spawn(async move { + let signer = client.signer().context("Signer not found")?; + let public_key = signer.get_public_key().await?; + + let filter = Filter::new() + .kind(Kind::RelayList) + .author(public_key) + .limit(1); + + // Construct target for subscription + let target: HashMap<&str, Vec> = BOOTSTRAP_RELAYS + .into_iter() + .map(|relay| (relay, vec![filter.clone()])) + .collect(); + + // Stream events from the bootstrap relays + let mut stream = client + .stream_events(target) + .timeout(Duration::from_secs(TIMEOUT)) + .await?; + + while let Some((_url, res)) = stream.next().await { + match res { + Ok(event) => { + log::info!("Received relay list event: {event:?}"); + return Ok(RelayState::Configured); + } + Err(e) => { + log::error!("Failed to receive relay list event: {e}"); + } + } + } + + Ok(RelayState::NotConfigured) + }) } /// Ensure write relays for a given public key @@ -381,289 +716,8 @@ impl NostrRegistry { }) } - /// Set the connected status of the client - fn set_connected(&mut self, cx: &mut Context) { - self.connected = true; - cx.notify(); - } - - /// Get local stored signer - fn get_signer(&mut self, cx: &mut Context) { - let read_credential = cx.read_credentials(KEYRING); - - self.tasks.push(cx.spawn(async move |this, cx| { - match read_credential.await { - Ok(Some((_user, secret))) => { - let secret = SecretKey::from_slice(&secret)?; - let keys = Keys::new(secret); - - this.update(cx, |this, cx| { - this.set_signer(keys, false, cx); - })?; - } - _ => { - this.update(cx, |this, cx| { - this.get_bunker(cx); - })?; - } - } - - Ok(()) - })); - } - - /// Get local stored bunker connection - fn get_bunker(&mut self, cx: &mut Context) { - let client = self.client(); - let app_keys = self.app_keys().clone(); - let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT); - - let task: Task> = cx.background_spawn(async move { - log::info!("Getting bunker connection"); - - let filter = Filter::new() - .kind(Kind::ApplicationSpecificData) - .identifier("coop:account") - .limit(1); - - if let Some(event) = client.database().query(filter).await?.first_owned() { - let uri = NostrConnectUri::parse(event.content)?; - let signer = NostrConnect::new(uri.clone(), app_keys.clone(), timeout, None)?; - - Ok(signer) - } else { - Err(anyhow!("No account found")) - } - }); - - self.tasks.push(cx.spawn(async move |this, cx| { - match task.await { - Ok(signer) => { - this.update(cx, |this, cx| { - this.set_signer(signer, true, cx); - }) - .ok(); - } - Err(e) => { - log::warn!("Failed to get bunker: {e}"); - // Create a new identity if no stored bunker exists - this.update(cx, |this, cx| { - this.set_default_signer(cx); - }) - .ok(); - } - } - - Ok(()) - })); - } - - /// Set the signer for the nostr client and verify the public key - pub fn set_signer(&mut self, new: T, owned: bool, cx: &mut Context) - where - T: NostrSigner + 'static, - { - let client = self.client(); - let signer = self.signer(); - - // Create a task to update the signer and verify the public key - let task: Task> = cx.background_spawn(async move { - // Update signer and unsubscribe - signer.switch(new, owned).await; - client.unsubscribe_all().await?; - - // Verify and save public key - let signer = client.signer().context("Signer not found")?; - let public_key = signer.get_public_key().await?; - - let npub = public_key.to_bech32().unwrap(); - let keys_dir = config_dir().join("keys"); - - // Ensure keys directory exists - smol::fs::create_dir_all(&keys_dir).await?; - - let key_path = keys_dir.join(format!("{}.npub", npub)); - smol::fs::write(key_path, "").await?; - - log::info!("Signer's public key: {}", public_key); - Ok(public_key) - }); - - self.tasks.push(cx.spawn(async move |this, cx| { - // set signer - let public_key = task.await?; - - // Update states - this.update(cx, |this, cx| { - this.npubs.update(cx, |this, cx| { - this.insert(public_key); - cx.notify(); - }); - this.ensure_relay_list(cx); - })?; - - Ok(()) - })); - } - - /// Create a new identity - fn set_default_signer(&mut self, cx: &mut Context) { - let client = self.client(); - let keys = Keys::generate(); - let async_keys = keys.clone(); - - // Create a write credential task - let write_credential = cx.write_credentials( - KEYRING, - &keys.public_key().to_hex(), - &keys.secret_key().to_secret_bytes(), - ); - - // Set the creating signer status - self.set_creating_signer(true, cx); - - // Run async tasks in background - let task: Task> = cx.background_spawn(async move { - let signer = async_keys.into_nostr_signer(); - - // Get default relay list - let relay_list = default_relay_list(); - - // Publish relay list event - let event = EventBuilder::relay_list(relay_list).sign(&signer).await?; - client - .send_event(&event) - .ok_timeout(Duration::from_secs(TIMEOUT)) - .await?; - - // Construct the default metadata - let name = petname::petname(2, "-").unwrap_or("Cooper".to_string()); - let avatar = Url::parse(&format!("https://avatar.vercel.sh/{name}")).unwrap(); - let metadata = Metadata::new().display_name(&name).picture(avatar); - - // Publish metadata event - let event = EventBuilder::metadata(&metadata).sign(&signer).await?; - client - .send_event(&event) - .ok_timeout(Duration::from_secs(TIMEOUT)) - .ack_policy(AckPolicy::none()) - .await?; - - // Construct the default contact list - let contacts = vec![Contact::new(PublicKey::parse(COOP_PUBKEY).unwrap())]; - - // Publish contact list event - let event = EventBuilder::contact_list(contacts).sign(&signer).await?; - client - .send_event(&event) - .ok_timeout(Duration::from_secs(TIMEOUT)) - .ack_policy(AckPolicy::none()) - .await?; - - // Construct the default messaging relay list - let relays = default_messaging_relays(); - - // Publish messaging relay list event - let event = EventBuilder::nip17_relay_list(relays).sign(&signer).await?; - client - .send_event(&event) - .ok_timeout(Duration::from_secs(TIMEOUT)) - .ack_policy(AckPolicy::none()) - .await?; - - // Write user's credentials to the system keyring - write_credential.await?; - - Ok(()) - }); - - self.tasks.push(cx.spawn(async move |this, cx| { - // Wait for the task to complete - task.await?; - - this.update(cx, |this, cx| { - this.set_creating_signer(false, cx); - this.set_signer(keys, false, cx); - })?; - - Ok(()) - })); - } - - /// Set whether Coop is creating a new signer - fn set_creating_signer(&mut self, creating: bool, cx: &mut Context) { - self.creating = creating; - cx.notify(); - } - - /// Set the state of the relay list - fn set_relay_state(&mut self, state: RelayState, cx: &mut Context) { - self.relay_list_state = state; - cx.notify(); - } - - pub fn ensure_relay_list(&mut self, cx: &mut Context) { - let task = self.verify_relay_list(cx); - - // Set the state to idle before starting the task - self.set_relay_state(RelayState::default(), cx); - - self.tasks.push(cx.spawn(async move |this, cx| { - let result = task.await?; - - // Update state - this.update(cx, |this, cx| { - this.relay_list_state = result; - cx.notify(); - })?; - - Ok(()) - })); - } - - // Verify relay list for current user - fn verify_relay_list(&mut self, cx: &mut Context) -> Task> { - let client = self.client(); - - cx.background_spawn(async move { - let signer = client.signer().context("Signer not found")?; - let public_key = signer.get_public_key().await?; - - let filter = Filter::new() - .kind(Kind::RelayList) - .author(public_key) - .limit(1); - - // Construct target for subscription - let target: HashMap<&str, Vec> = BOOTSTRAP_RELAYS - .into_iter() - .map(|relay| (relay, vec![filter.clone()])) - .collect(); - - // Stream events from the bootstrap relays - let mut stream = client - .stream_events(target) - .timeout(Duration::from_secs(TIMEOUT)) - .await?; - - while let Some((_url, res)) = stream.next().await { - match res { - Ok(event) => { - log::info!("Received relay list event: {event:?}"); - return Ok(RelayState::Configured); - } - Err(e) => { - log::error!("Failed to receive relay list event: {e}"); - } - } - } - - Ok(RelayState::NotConfigured) - }) - } - /// Generate a direct nostr connection initiated by the client - pub fn client_connect(&self, relay: Option) -> (NostrConnect, NostrConnectUri) { + pub fn nostr_connect(&self, relay: Option) -> (NostrConnect, NostrConnectUri) { let app_keys = self.app_keys(); let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT); @@ -685,25 +739,6 @@ impl NostrRegistry { (signer, uri) } - /// Store the bunker connection for the next login - pub fn persist_bunker(&mut self, uri: NostrConnectUri, cx: &mut App) { - let client = self.client(); - let rng_keys = Keys::generate(); - - self.tasks.push(cx.background_spawn(async move { - // Construct the event for application-specific data - let event = EventBuilder::new(Kind::ApplicationSpecificData, uri.to_string()) - .tag(Tag::identifier("coop:account")) - .sign(&rng_keys) - .await?; - - // Store the event in the database - client.database().save_event(&event).await?; - - Ok(()) - })); - } - /// Get the public key of a NIP-05 address pub fn get_address(&self, addr: Nip05Address, cx: &App) -> Task> { let client = self.client(); diff --git a/crates/state/src/signer.rs b/crates/state/src/signer.rs index 4cb1691..262e611 100644 --- a/crates/state/src/signer.rs +++ b/crates/state/src/signer.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; use std::result::Result; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use nostr_sdk::prelude::*; @@ -16,11 +15,6 @@ pub struct CoopSigner { /// Specific signer for encryption purposes encryption_signer: RwLock>>, - - /// By default, Coop generates a new signer for new users. - /// - /// This flag indicates whether the signer is user-owned or Coop-generated. - owned: AtomicBool, } impl CoopSigner { @@ -32,7 +26,6 @@ impl CoopSigner { signer: RwLock::new(signer.into_nostr_signer()), signer_pkey: RwLock::new(None), encryption_signer: RwLock::new(None), - owned: AtomicBool::new(false), } } @@ -47,17 +40,15 @@ impl CoopSigner { } /// Get public key + /// + /// Ensure to call this method after the signer has been initialized. + /// Otherwise, this method will panic. pub fn public_key(&self) -> Option { - self.signer_pkey.read_blocking().to_owned() - } - - /// Get the flag indicating whether the signer is user-owned. - pub fn owned(&self) -> bool { - self.owned.load(Ordering::SeqCst) + *self.signer_pkey.read_blocking() } /// Switch the current signer to a new signer. - pub async fn switch(&self, new: T, owned: bool) + pub async fn switch(&self, new: T) where T: IntoNostrSigner, { @@ -75,9 +66,6 @@ impl CoopSigner { // Reset the encryption signer *encryption_signer = None; - - // Update the owned flag - self.owned.store(owned, Ordering::SeqCst); } /// Set the encryption signer.