store multi accounts
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m37s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m28s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m37s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m28s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Rust / build (macos-latest, stable) (pull_request) Has been cancelled
Rust / build (windows-latest, stable) (pull_request) Has been cancelled
This commit is contained in:
77
Cargo.lock
generated
77
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -262,9 +262,12 @@ impl ChatRegistry {
|
||||
pub fn get_contact_list(&mut self, cx: &mut Context<Self>) {
|
||||
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<Result<(), Error>> = cx.background_spawn(async move {
|
||||
@@ -318,9 +321,12 @@ impl ChatRegistry {
|
||||
fn verify_relays(&mut self, cx: &mut Context<Self>) -> Task<Result<InboxState, Error>> {
|
||||
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 {
|
||||
|
||||
@@ -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<PublicKey> = self
|
||||
.members
|
||||
.iter()
|
||||
.filter(|public_key| public_key != &&sender)
|
||||
.filter(|public_key| Some(**public_key) != sender)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -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<GreeterPanel> {
|
||||
@@ -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,8 +188,7 @@ impl Render for GreeterPanel {
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(!owned, |this| {
|
||||
this.child(
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.w_full()
|
||||
@@ -218,12 +214,7 @@ impl Render for GreeterPanel {
|
||||
.small()
|
||||
.justify_start()
|
||||
.on_click(move |_ev, window, cx| {
|
||||
Workspace::add_panel(
|
||||
connect::init(window, cx),
|
||||
DockPlacement::Center,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
//
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
@@ -234,17 +225,11 @@ impl Render for GreeterPanel {
|
||||
.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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -204,9 +204,11 @@ impl DeviceRegistry {
|
||||
fn subscribe_to_giftwrap_events(&mut self, cx: &mut Context<Self>) -> Task<Result<(), Error>> {
|
||||
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<Self>) {
|
||||
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<Result<Keys, Error>> {
|
||||
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<Self>) {
|
||||
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<Self>) {
|
||||
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<Self>) {
|
||||
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<Self>) {
|
||||
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);
|
||||
|
||||
@@ -52,7 +52,7 @@ pub struct NostrRegistry {
|
||||
signer: Arc<CoopSigner>,
|
||||
|
||||
/// Local public keys
|
||||
npubs: Entity<HashSet<PublicKey>>,
|
||||
npubs: Entity<Vec<PublicKey>>,
|
||||
|
||||
/// 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>) {
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let task: Result<HashSet<PublicKey>, 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<PublicKey> = 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);
|
||||
/// Get the nostr client
|
||||
pub fn client(&self) -> Client {
|
||||
self.client.clone()
|
||||
}
|
||||
|
||||
Ok(npubs)
|
||||
})
|
||||
.await;
|
||||
/// Get the nostr signer
|
||||
pub fn signer(&self) -> Arc<CoopSigner> {
|
||||
self.signer.clone()
|
||||
}
|
||||
|
||||
if let Ok(npubs) = task {
|
||||
this.update(cx, |this, cx| {
|
||||
this.npubs.update(cx, |this, cx| {
|
||||
this.extend(npubs);
|
||||
/// 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<SharedString> {
|
||||
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>) {
|
||||
self.connected = true;
|
||||
cx.notify();
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Connect to the bootstrapping relays
|
||||
fn connect(&mut self, cx: &mut Context<Self>) {
|
||||
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<Self>) {
|
||||
let npubs = self.npubs.downgrade();
|
||||
let task: Task<Result<Vec<PublicKey>, 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));
|
||||
}
|
||||
|
||||
/// Get the nostr signer
|
||||
pub fn signer(&self) -> Arc<CoopSigner> {
|
||||
self.signer.clone()
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// Get the app keys
|
||||
pub fn app_keys(&self) -> &Keys {
|
||||
&self.app_keys
|
||||
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 connected status of the client
|
||||
pub fn connected(&self) -> bool {
|
||||
self.connected
|
||||
/// Set whether Coop is creating a new signer
|
||||
fn set_creating(&mut self, creating: bool, cx: &mut Context<Self>) {
|
||||
self.creating = creating;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Get the creating status
|
||||
pub fn creating(&self) -> bool {
|
||||
self.creating
|
||||
/// Create a new identity
|
||||
pub fn create_new_signer(&mut self, cx: &mut Context<Self>) {
|
||||
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<Result<(), Error>> = 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 relay list state
|
||||
pub fn relay_list_state(&self) -> RelayState {
|
||||
self.relay_list_state.clone()
|
||||
// Get the signer in keyring by username
|
||||
pub fn get_signer(
|
||||
&mut self,
|
||||
username: &str,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Arc<dyn NostrSigner>, 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());
|
||||
}
|
||||
|
||||
/// Get all relays for a given public key without ensuring connections
|
||||
pub fn read_only_relays(&self, public_key: &PublicKey, cx: &App) -> Vec<SharedString> {
|
||||
self.gossip.read(cx).read_only_relays(public_key)
|
||||
// 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())
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the signer for the nostr client and verify the public key
|
||||
pub fn set_signer<T>(&mut self, new: T, cx: &mut Context<Self>)
|
||||
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<Result<PublicKey, Error>> = 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(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Add a key signer to keyring
|
||||
pub fn add_key_signer(
|
||||
&mut self,
|
||||
keys: &Keys,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<(), Error>> {
|
||||
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(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a nostr connect signer to keyring
|
||||
pub fn add_nip46_signer(
|
||||
&mut self,
|
||||
nip46: &NostrConnect,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<(), Error>> {
|
||||
let nip46 = nip46.clone();
|
||||
let async_nip46 = nip46.clone();
|
||||
|
||||
// Connect and verify the remote signer
|
||||
let task: Task<Result<(PublicKey, NostrConnectUri), Error>> =
|
||||
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>) {
|
||||
self.relay_list_state = state;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn ensure_relay_list(&mut self, cx: &mut Context<Self>) {
|
||||
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<Self>) -> Task<Result<RelayState, Error>> {
|
||||
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<Filter>> = 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>) {
|
||||
self.connected = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Get local stored signer
|
||||
fn get_signer(&mut self, cx: &mut Context<Self>) {
|
||||
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<Self>) {
|
||||
let client = self.client();
|
||||
let app_keys = self.app_keys().clone();
|
||||
let timeout = Duration::from_secs(NOSTR_CONNECT_TIMEOUT);
|
||||
|
||||
let task: Task<Result<NostrConnect, Error>> = 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<T>(&mut self, new: T, owned: bool, cx: &mut Context<Self>)
|
||||
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<Result<PublicKey, Error>> = 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<Self>) {
|
||||
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<Result<(), Error>> = 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>) {
|
||||
self.creating = creating;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the state of the relay list
|
||||
fn set_relay_state(&mut self, state: RelayState, cx: &mut Context<Self>) {
|
||||
self.relay_list_state = state;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn ensure_relay_list(&mut self, cx: &mut Context<Self>) {
|
||||
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<Self>) -> Task<Result<RelayState, Error>> {
|
||||
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<Filter>> = 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<RelayUrl>) -> (NostrConnect, NostrConnectUri) {
|
||||
pub fn nostr_connect(&self, relay: Option<RelayUrl>) -> (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<Result<PublicKey, Error>> {
|
||||
let client = self.client();
|
||||
|
||||
@@ -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<Option<Arc<dyn NostrSigner>>>,
|
||||
|
||||
/// 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<PublicKey> {
|
||||
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<T>(&self, new: T, owned: bool)
|
||||
pub async fn switch<T>(&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.
|
||||
|
||||
Reference in New Issue
Block a user