chore: improve nip4e implementation (#204)
* patch * update ui * add load response * fix * . * wip: rewrite gossip * new gossip implementation * clean up * . * debug * . * . * update * . * fix * fix
This commit is contained in:
61
Cargo.lock
generated
61
Cargo.lock
generated
@@ -1205,7 +1205,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -1628,7 +1628,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1989,9 +1989,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exr"
|
name = "exr"
|
||||||
version = "1.73.0"
|
version = "1.74.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
|
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit_field",
|
"bit_field",
|
||||||
"half",
|
"half",
|
||||||
@@ -2568,7 +2568,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2665,7 +2665,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2676,7 +2676,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2905,7 +2905,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -2920,6 +2920,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"url",
|
"url",
|
||||||
@@ -2930,7 +2931,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -2944,9 +2945,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.7.0"
|
version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e"
|
checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -3735,7 +3736,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
@@ -3985,7 +3986,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.44.1"
|
version = "0.44.1"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -4009,7 +4010,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -4021,7 +4022,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -4032,7 +4033,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-gossip"
|
name = "nostr-gossip"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nostr",
|
"nostr",
|
||||||
]
|
]
|
||||||
@@ -4040,7 +4041,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-gossip-memory"
|
name = "nostr-gossip-memory"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -4052,7 +4053,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"flume",
|
"flume",
|
||||||
@@ -4066,7 +4067,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -4083,7 +4084,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.44.1"
|
version = "0.44.1"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#5508a1a28fa984e577d98238c237dfd2e4dc148c"
|
source = "git+https://github.com/rust-nostr/nostr#641867215b235694353c52af2c1debe920440050"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -4594,7 +4595,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5220,7 +5221,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
]
|
]
|
||||||
@@ -5318,7 +5319,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5372,7 +5373,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rope"
|
name = "rope"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -5838,7 +5839,7 @@ checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -6286,7 +6287,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -7273,7 +7274,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -7309,7 +7310,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#378b30eba5da7b9131b4a1d5bcee5bf09ad567ef"
|
source = "git+https://github.com/zed-industries/zed#cf6ae01d07b5fd02629535250ebddc65f9d0d9ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7721,9 +7722,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.11"
|
version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "009936b22a61d342859b5f0ea64681cbb35a358ab548e2a44a8cf0dac2d980b8"
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
@@ -21,10 +20,10 @@ pub struct Account {
|
|||||||
public_key: Option<PublicKey>,
|
public_key: Option<PublicKey>,
|
||||||
|
|
||||||
/// Status of the current user NIP-65 relays
|
/// Status of the current user NIP-65 relays
|
||||||
pub nip65_status: RelayStatus,
|
pub nip65_status: Entity<RelayStatus>,
|
||||||
|
|
||||||
/// Status of the current user NIP-17 relays
|
/// Status of the current user NIP-17 relays
|
||||||
pub nip17_status: RelayStatus,
|
pub nip17_status: Entity<RelayStatus>,
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
/// Tasks for asynchronous operations
|
||||||
_tasks: SmallVec<[Task<()>; 2]>,
|
_tasks: SmallVec<[Task<()>; 2]>,
|
||||||
@@ -64,14 +63,14 @@ impl Account {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
|
let nip65_status = cx.new(|_| RelayStatus::default());
|
||||||
|
let nip17_status = cx.new(|_| RelayStatus::default());
|
||||||
|
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Observe the nostr signer and set the public key when it sets
|
// Observe the nostr signer and set the public key when it sets
|
||||||
cx.spawn({
|
cx.spawn(async move |this, cx| {
|
||||||
let client = Arc::clone(&client);
|
|
||||||
|
|
||||||
async move |this, cx| {
|
|
||||||
let result = cx
|
let result = cx
|
||||||
.background_spawn(async move { Self::observe_signer(&client).await })
|
.background_spawn(async move { Self::observe_signer(&client).await })
|
||||||
.await;
|
.await;
|
||||||
@@ -82,14 +81,13 @@ impl Account {
|
|||||||
})
|
})
|
||||||
.expect("Entity has been released")
|
.expect("Entity has been released")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
public_key: None,
|
public_key: None,
|
||||||
nip65_status: RelayStatus::default(),
|
nip65_status,
|
||||||
nip17_status: RelayStatus::default(),
|
nip17_status,
|
||||||
_tasks: tasks,
|
_tasks: tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,8 +123,6 @@ impl Account {
|
|||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
log::info!("Getting user's gossip relays...");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,23 +164,29 @@ impl Account {
|
|||||||
self._tasks.push(
|
self._tasks.push(
|
||||||
// Verify user's nip65 and nip17 relays
|
// Verify user's nip65 and nip17 relays
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
cx.background_executor()
|
cx.background_executor().timer(Duration::from_secs(5)).await;
|
||||||
.timer(Duration::from_secs(10))
|
|
||||||
.await;
|
|
||||||
|
|
||||||
|
// Fetch the NIP-65 relays event in the local database
|
||||||
let ensure_nip65 = Self::ensure_nip65_relays(&client, public_key).await;
|
let ensure_nip65 = Self::ensure_nip65_relays(&client, public_key).await;
|
||||||
|
|
||||||
|
// Fetch the NIP-17 relays event in the local database
|
||||||
let ensure_nip17 = Self::ensure_nip17_relays(&client, public_key).await;
|
let ensure_nip17 = Self::ensure_nip17_relays(&client, public_key).await;
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.nip65_status = match ensure_nip65 {
|
this.nip65_status.update(cx, |this, cx| {
|
||||||
Ok(true) => RelayStatus::Set,
|
*this = match ensure_nip65 {
|
||||||
_ => RelayStatus::NotSet,
|
|
||||||
};
|
|
||||||
this.nip17_status = match ensure_nip17 {
|
|
||||||
Ok(true) => RelayStatus::Set,
|
Ok(true) => RelayStatus::Set,
|
||||||
_ => RelayStatus::NotSet,
|
_ => RelayStatus::NotSet,
|
||||||
};
|
};
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
});
|
||||||
|
this.nip17_status.update(cx, |this, cx| {
|
||||||
|
*this = match ensure_nip17 {
|
||||||
|
Ok(true) => RelayStatus::Set,
|
||||||
|
_ => RelayStatus::NotSet,
|
||||||
|
};
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.expect("Entity has been released")
|
.expect("Entity has been released")
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ impl AutoUpdater {
|
|||||||
Err(anyhow!("No update available"))
|
Err(anyhow!("No update available"))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Not found"))
|
Err(anyhow!("No update available"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ use nostr_sdk::prelude::*;
|
|||||||
pub use room::*;
|
pub use room::*;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::lock::RwLock;
|
use state::{initialized_at, NostrRegistry, GIFTWRAP_SUBSCRIPTION};
|
||||||
use state::{initialized_at, EventTracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION};
|
|
||||||
|
|
||||||
mod message;
|
mod message;
|
||||||
mod room;
|
mod room;
|
||||||
@@ -41,6 +40,9 @@ pub struct ChatRegistry {
|
|||||||
/// Loading status of the registry
|
/// Loading status of the registry
|
||||||
pub loading: bool,
|
pub loading: bool,
|
||||||
|
|
||||||
|
/// Async task for handling notifications
|
||||||
|
handle_notifications: Task<()>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
|
|
||||||
@@ -82,11 +84,19 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let tracker = nostr.read(cx).tracker();
|
|
||||||
|
|
||||||
let status = Arc::new(AtomicBool::new(true));
|
let status = Arc::new(AtomicBool::new(true));
|
||||||
let (tx, rx) = flume::bounded::<Signal>(2048);
|
let (tx, rx) = flume::bounded::<Signal>(2048);
|
||||||
|
|
||||||
|
let handle_notifications = cx.background_spawn({
|
||||||
|
let client = nostr.read(cx).client();
|
||||||
|
let status = Arc::clone(&status);
|
||||||
|
let tx = tx.clone();
|
||||||
|
let signer: Option<Arc<dyn NostrSigner>> = None;
|
||||||
|
|
||||||
|
async move { Self::handle_notifications(&client, &signer, &tx, &status).await }
|
||||||
|
});
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
@@ -98,29 +108,27 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
move |this, state, cx| {
|
move |this, state, cx| {
|
||||||
if let Some(signer) = state.read(cx).clone() {
|
if let Some(signer) = state.read(cx).clone() {
|
||||||
this.retry_failed_events(&signer, &tx, &status, cx);
|
this.handle_notifications = cx.background_spawn({
|
||||||
}
|
let client = nostr.read(cx).client();
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
// Handle notifications
|
|
||||||
cx.background_spawn({
|
|
||||||
let client = Arc::clone(&client);
|
|
||||||
let status = Arc::clone(&status);
|
let status = Arc::clone(&status);
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
|
let signer = Some(signer);
|
||||||
|
|
||||||
async move { Self::handle_notifications(&client, &tracker, &tx, &status).await }
|
async move {
|
||||||
|
Self::handle_notifications(&client, &signer, &tx, &status).await
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
// Handle unwrapping status
|
// Handle unwrapping status
|
||||||
cx.background_spawn({
|
cx.background_spawn(
|
||||||
let client = Arc::clone(&client);
|
async move { Self::handle_unwrapping(&client, &status, &tx).await },
|
||||||
async move { Self::handle_unwrapping(&client, &status, &tx).await }
|
),
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
@@ -155,23 +163,24 @@ impl ChatRegistry {
|
|||||||
Self {
|
Self {
|
||||||
rooms: vec![],
|
rooms: vec![],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
handle_notifications,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
_tasks: tasks,
|
_tasks: tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_notifications(
|
async fn handle_notifications<T>(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
tracker: &Arc<RwLock<EventTracker>>,
|
signer: &Option<T>,
|
||||||
tx: &Sender<Signal>,
|
tx: &Sender<Signal>,
|
||||||
status: &Arc<AtomicBool>,
|
status: &Arc<AtomicBool>,
|
||||||
) {
|
) where
|
||||||
let mut notifications = client.notifications();
|
T: NostrSigner,
|
||||||
log::info!("Listening for notifications");
|
{
|
||||||
|
|
||||||
let initialized_at = initialized_at();
|
let initialized_at = initialized_at();
|
||||||
let subscription_id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
let subscription_id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
||||||
|
|
||||||
|
let mut notifications = client.notifications();
|
||||||
let mut public_keys = HashSet::new();
|
let mut public_keys = HashSet::new();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
@@ -194,7 +203,7 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract the rumor from the gift wrap event
|
// Extract the rumor from the gift wrap event
|
||||||
match Self::extract_rumor(client, event.as_ref()).await {
|
match Self::extract_rumor(client, signer, event.as_ref()).await {
|
||||||
Ok(rumor) => {
|
Ok(rumor) => {
|
||||||
// Get all public keys
|
// Get all public keys
|
||||||
public_keys.extend(rumor.all_pubkeys());
|
public_keys.extend(rumor.all_pubkeys());
|
||||||
@@ -209,7 +218,7 @@ impl ChatRegistry {
|
|||||||
Self::get_metadata(client, public_keys).await.ok();
|
Self::get_metadata(client, public_keys).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
match &event.created_at >= initialized_at {
|
match &rumor.created_at >= initialized_at {
|
||||||
true => {
|
true => {
|
||||||
let new_message = NewMessage::new(event.id, rumor);
|
let new_message = NewMessage::new(event.id, rumor);
|
||||||
let signal = Signal::Message(new_message);
|
let signal = Signal::Message(new_message);
|
||||||
@@ -223,11 +232,8 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
let mut tracker = tracker.write().await;
|
log::warn!("Failed to unwrap gift wrap event: {}", e);
|
||||||
tracker.failed_unwrap_events.push(event.as_ref().clone());
|
|
||||||
|
|
||||||
drop(tracker);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,46 +286,6 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn retry_failed_events(
|
|
||||||
&mut self,
|
|
||||||
signer: &Arc<dyn NostrSigner>,
|
|
||||||
tx: &Sender<Signal>,
|
|
||||||
status: &Arc<AtomicBool>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
let tracker = nostr.read(cx).tracker();
|
|
||||||
|
|
||||||
let signer = Arc::clone(signer);
|
|
||||||
let status = Arc::clone(status);
|
|
||||||
|
|
||||||
let tx = tx.clone();
|
|
||||||
let initialized_at = initialized_at();
|
|
||||||
|
|
||||||
self._tasks.push(cx.background_spawn(async move {
|
|
||||||
let tracker = tracker.read().await;
|
|
||||||
|
|
||||||
for event in tracker.failed_unwrap_events.iter() {
|
|
||||||
if let Ok(rumor) = Self::try_unwrap_custom(&client, &signer, event).await {
|
|
||||||
match &event.created_at >= initialized_at {
|
|
||||||
true => {
|
|
||||||
let new_message = NewMessage::new(event.id, rumor);
|
|
||||||
let signal = Signal::Message(new_message);
|
|
||||||
|
|
||||||
if let Err(e) = tx.send_async(signal).await {
|
|
||||||
log::error!("Failed to send signal: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false => {
|
|
||||||
status.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the loading status of the chat registry
|
/// Set the loading status of the chat registry
|
||||||
pub fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) {
|
pub fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) {
|
||||||
self.loading = loading;
|
self.loading = loading;
|
||||||
@@ -600,14 +566,21 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwraps a gift-wrapped event and processes its contents.
|
// Unwraps a gift-wrapped event and processes its contents.
|
||||||
async fn extract_rumor(client: &Client, gift_wrap: &Event) -> Result<UnsignedEvent, Error> {
|
async fn extract_rumor<T>(
|
||||||
|
client: &Client,
|
||||||
|
signer: &Option<T>,
|
||||||
|
gift_wrap: &Event,
|
||||||
|
) -> Result<UnsignedEvent, Error>
|
||||||
|
where
|
||||||
|
T: NostrSigner,
|
||||||
|
{
|
||||||
// Try to get cached rumor first
|
// Try to get cached rumor first
|
||||||
if let Ok(event) = Self::get_rumor(client, gift_wrap.id).await {
|
if let Ok(event) = Self::get_rumor(client, gift_wrap.id).await {
|
||||||
return Ok(event);
|
return Ok(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to unwrap with the available signer
|
// Try to unwrap with the available signer
|
||||||
let unwrapped = Self::try_unwrap(client, gift_wrap).await?;
|
let unwrapped = Self::try_unwrap(client, signer, gift_wrap).await?;
|
||||||
let mut rumor_unsigned = unwrapped.rumor;
|
let mut rumor_unsigned = unwrapped.rumor;
|
||||||
|
|
||||||
// Generate event id for the rumor if it doesn't have one
|
// Generate event id for the rumor if it doesn't have one
|
||||||
@@ -620,34 +593,45 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to try unwrapping with different signers
|
// Helper method to try unwrapping with different signers
|
||||||
async fn try_unwrap(client: &Client, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
|
async fn try_unwrap<T>(
|
||||||
|
client: &Client,
|
||||||
|
signer: &Option<T>,
|
||||||
|
gift_wrap: &Event,
|
||||||
|
) -> Result<UnwrappedGift, Error>
|
||||||
|
where
|
||||||
|
T: NostrSigner,
|
||||||
|
{
|
||||||
|
if let Some(custom_signer) = signer.as_ref() {
|
||||||
|
if let Ok(seal) = custom_signer
|
||||||
|
.nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let seal: Event = Event::from_json(seal)?;
|
||||||
|
seal.verify_with_ctx(SECP256K1)?;
|
||||||
|
|
||||||
|
// Decrypt the rumor
|
||||||
|
// TODO: verify the sender
|
||||||
|
let rumor = custom_signer
|
||||||
|
.nip44_decrypt(&seal.pubkey, &seal.content)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Construct the unsigned event
|
||||||
|
let rumor = UnsignedEvent::from_json(rumor)?;
|
||||||
|
|
||||||
|
// Return the unwrapped gift
|
||||||
|
return Ok(UnwrappedGift {
|
||||||
|
sender: rumor.pubkey,
|
||||||
|
rumor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;
|
let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;
|
||||||
|
|
||||||
Ok(unwrapped)
|
Ok(unwrapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper method to try unwrapping with a custom signer
|
|
||||||
async fn try_unwrap_custom<T>(
|
|
||||||
client: &Client,
|
|
||||||
signer: &T,
|
|
||||||
gift_wrap: &Event,
|
|
||||||
) -> Result<UnsignedEvent, Error>
|
|
||||||
where
|
|
||||||
T: NostrSigner,
|
|
||||||
{
|
|
||||||
let unwrapped = UnwrappedGift::from_gift_wrap(signer, gift_wrap).await?;
|
|
||||||
let mut rumor_unsigned = unwrapped.rumor;
|
|
||||||
|
|
||||||
// Generate event id for the rumor if it doesn't have one
|
|
||||||
rumor_unsigned.ensure_id();
|
|
||||||
|
|
||||||
// Cache the rumor
|
|
||||||
Self::set_rumor(client, gift_wrap.id, &rumor_unsigned).await?;
|
|
||||||
|
|
||||||
Ok(rumor_unsigned)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores an unwrapped event in local database with reference to original
|
/// Stores an unwrapped event in local database with reference to original
|
||||||
async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Result<(), Error> {
|
async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Result<(), Error> {
|
||||||
let rumor_id = rumor.id.context("Rumor is missing an event id")?;
|
let rumor_id = rumor.id.context("Rumor is missing an event id")?;
|
||||||
@@ -718,7 +702,7 @@ impl ChatRegistry {
|
|||||||
{
|
{
|
||||||
let authors: Vec<PublicKey> = public_keys.into_iter().collect();
|
let authors: Vec<PublicKey> = public_keys.into_iter().collect();
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
||||||
|
|
||||||
// Return if the list is empty
|
// Return if the list is empty
|
||||||
if authors.is_empty() {
|
if authors.is_empty() {
|
||||||
@@ -726,7 +710,7 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.limit(authors.len() * kinds.len() + 10)
|
.limit(authors.len() * kinds.len())
|
||||||
.authors(authors)
|
.authors(authors)
|
||||||
.kinds(kinds);
|
.kinds(kinds);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use anyhow::{anyhow, Error};
|
|||||||
use common::{EventUtils, RenderedProfile};
|
use common::{EventUtils, RenderedProfile};
|
||||||
use encryption::{Encryption, SignerKind};
|
use encryption::{Encryption, SignerKind};
|
||||||
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
|
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
|
||||||
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use state::NostrRegistry;
|
use state::NostrRegistry;
|
||||||
@@ -326,7 +327,7 @@ impl Room {
|
|||||||
cx.emit(RoomSignal::Refresh);
|
cx.emit(RoomSignal::Refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get messaging relays and encryption keys announcement for each member
|
/// Get gossip relays for each member
|
||||||
pub fn connect(&self, cx: &App) -> Task<Result<(), Error>> {
|
pub fn connect(&self, cx: &App) -> Task<Result<(), Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
@@ -342,22 +343,10 @@ impl Room {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Construct a filter for messaging relays
|
// Construct a filter for gossip relays
|
||||||
let filter = Filter::new()
|
let filter = Filter::new().kind(Kind::RelayList).author(member).limit(1);
|
||||||
.kind(Kind::InboxRelays)
|
|
||||||
.author(member)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
// Subscribe to get members messaging relays
|
// Subscribe to get member's gossip relays
|
||||||
client.subscribe(filter, Some(opts)).await?;
|
|
||||||
|
|
||||||
// Construct a filter for encryption keys announcement
|
|
||||||
let filter = Filter::new()
|
|
||||||
.kind(Kind::Custom(10044))
|
|
||||||
.author(member)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
// Subscribe to get members encryption keys announcement
|
|
||||||
client.subscribe(filter, Some(opts)).await?;
|
client.subscribe(filter, Some(opts)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,15 +365,15 @@ impl Room {
|
|||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.custom_tag(SingleLetterTag::lowercase(Alphabet::C), conversation_id);
|
.custom_tag(SingleLetterTag::lowercase(Alphabet::C), conversation_id);
|
||||||
|
|
||||||
let stored = client.database().query(filter).await?;
|
let messages = client
|
||||||
|
.database()
|
||||||
let mut messages: Vec<UnsignedEvent> = stored
|
.query(filter)
|
||||||
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|event| UnsignedEvent::from_json(&event.content).ok())
|
.filter_map(|event| UnsignedEvent::from_json(&event.content).ok())
|
||||||
|
.sorted_by_key(|message| message.created_at)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
messages.sort_by_key(|message| message.created_at);
|
|
||||||
|
|
||||||
Ok(messages)
|
Ok(messages)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -392,8 +381,8 @@ impl Room {
|
|||||||
/// Create a new message event (unsigned)
|
/// Create a new message event (unsigned)
|
||||||
pub fn create_message(&self, content: &str, replies: &[EventId], cx: &App) -> UnsignedEvent {
|
pub fn create_message(&self, content: &str, replies: &[EventId], cx: &App) -> UnsignedEvent {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let cache = nostr.read(cx).cache_manager();
|
let gossip = nostr.read(cx).gossip();
|
||||||
let cache = cache.read_blocking();
|
let read_gossip = gossip.read_blocking();
|
||||||
|
|
||||||
// Get current user
|
// Get current user
|
||||||
let account = Account::global(cx);
|
let account = Account::global(cx);
|
||||||
@@ -409,9 +398,7 @@ impl Room {
|
|||||||
// NOTE: current user will be removed from the list of receivers
|
// NOTE: current user will be removed from the list of receivers
|
||||||
for member in self.members.iter() {
|
for member in self.members.iter() {
|
||||||
// Get relay hint if available
|
// Get relay hint if available
|
||||||
let relay_url = cache
|
let relay_url = read_gossip.messaging_relays(member).first().cloned();
|
||||||
.relay(member)
|
|
||||||
.and_then(|urls| urls.iter().nth(0).cloned());
|
|
||||||
|
|
||||||
// Construct a public key tag with relay hint
|
// Construct a public key tag with relay hint
|
||||||
let tag = TagStandard::PublicKey {
|
let tag = TagStandard::PublicKey {
|
||||||
@@ -470,7 +457,7 @@ impl Room {
|
|||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let cache = nostr.read(cx).cache_manager();
|
let gossip = nostr.read(cx).gossip();
|
||||||
let tracker = nostr.read(cx).tracker();
|
let tracker = nostr.read(cx).tracker();
|
||||||
|
|
||||||
let rumor = rumor.to_owned();
|
let rumor = rumor.to_owned();
|
||||||
@@ -481,8 +468,11 @@ impl Room {
|
|||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let signer_kind = opts.signer_kind;
|
let signer_kind = opts.signer_kind;
|
||||||
let cache = cache.read().await;
|
let gossip = gossip.read().await;
|
||||||
let tracker = tracker.read().await;
|
|
||||||
|
// Get current user's signer and public key
|
||||||
|
let user_signer = client.signer().await?;
|
||||||
|
let user_pubkey = user_signer.get_public_key().await?;
|
||||||
|
|
||||||
// Get the encryption public key
|
// Get the encryption public key
|
||||||
let encryption_pubkey = if let Some(signer) = encryption_key.as_ref() {
|
let encryption_pubkey = if let Some(signer) = encryption_key.as_ref() {
|
||||||
@@ -491,9 +481,6 @@ impl Room {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_signer = client.signer().await?;
|
|
||||||
let user_pubkey = user_signer.get_public_key().await?;
|
|
||||||
|
|
||||||
// Remove the current user's public key from the list of receivers
|
// Remove the current user's public key from the list of receivers
|
||||||
// the current user will be handled separately
|
// the current user will be handled separately
|
||||||
members.retain(|&pk| pk != user_pubkey);
|
members.retain(|&pk| pk != user_pubkey);
|
||||||
@@ -506,7 +493,9 @@ impl Room {
|
|||||||
|
|
||||||
for member in members.into_iter() {
|
for member in members.into_iter() {
|
||||||
// Get user's messaging relays
|
// Get user's messaging relays
|
||||||
let urls = cache.relay(&member).cloned().unwrap_or_default();
|
let urls = gossip.messaging_relays(&member);
|
||||||
|
// Get user's encryption public key if available
|
||||||
|
let encryption = gossip.announcement(&member).map(|a| a.public_key());
|
||||||
|
|
||||||
// Check if there are any relays to send the message to
|
// Check if there are any relays to send the message to
|
||||||
if urls.is_empty() {
|
if urls.is_empty() {
|
||||||
@@ -514,35 +503,26 @@ impl Room {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure connection to all messaging relays
|
|
||||||
for url in urls.iter() {
|
|
||||||
client.add_relay(url).await?;
|
|
||||||
client.connect_relay(url).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user's encryption public key if available
|
|
||||||
let encryption = cache
|
|
||||||
.announcement(&member)
|
|
||||||
.and_then(|a| a.to_owned().map(|a| a.public_key()));
|
|
||||||
|
|
||||||
// Skip sending if using encryption signer but receiver's encryption keys not found
|
// Skip sending if using encryption signer but receiver's encryption keys not found
|
||||||
if encryption.is_none() && matches!(signer_kind, SignerKind::Encryption) {
|
if encryption.is_none() && matches!(signer_kind, SignerKind::Encryption) {
|
||||||
reports.push(SendReport::new(member).device_not_found());
|
reports.push(SendReport::new(member).device_not_found());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let receiver = Self::select_receiver(&signer_kind, member, encryption)?;
|
// Ensure connections to the relays
|
||||||
let rumor = rumor.clone();
|
gossip.ensure_connections(&client, &urls).await;
|
||||||
|
|
||||||
// Construct the sealed event
|
// Determine the receiver based on the signer kind
|
||||||
let seal = EventBuilder::seal(&signer, &receiver, rumor.clone())
|
let receiver = Self::select_receiver(&signer_kind, member, encryption)?;
|
||||||
.await?
|
|
||||||
.build(member)
|
|
||||||
.sign(&signer)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Construct the gift wrap event
|
// Construct the gift wrap event
|
||||||
let event = EventBuilder::gift_wrap_from_seal(&member, &seal, vec![])?;
|
let event = EventBuilder::gift_wrap(
|
||||||
|
&signer,
|
||||||
|
&receiver,
|
||||||
|
rumor.clone(),
|
||||||
|
vec![Tag::public_key(member)],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Send the gift wrap event to the messaging relays
|
// Send the gift wrap event to the messaging relays
|
||||||
match client.send_event_to(urls, &event).await {
|
match client.send_event_to(urls, &event).await {
|
||||||
@@ -554,6 +534,7 @@ impl Room {
|
|||||||
if auth {
|
if auth {
|
||||||
// Wait for authenticated and resent event successfully
|
// Wait for authenticated and resent event successfully
|
||||||
for attempt in 0..=SEND_RETRY {
|
for attempt in 0..=SEND_RETRY {
|
||||||
|
let tracker = tracker.read().await;
|
||||||
let ids = tracker.resent_ids();
|
let ids = tracker.resent_ids();
|
||||||
|
|
||||||
// Check if event was successfully resent
|
// Check if event was successfully resent
|
||||||
@@ -581,22 +562,34 @@ impl Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let receiver = Self::select_receiver(&signer_kind, user_pubkey, encryption_pubkey)?;
|
// Return early if the user disabled backup.
|
||||||
let rumor = rumor.clone();
|
//
|
||||||
|
// Coop will not send a gift wrap event to the current user.
|
||||||
|
if !opts.backup() {
|
||||||
|
return Ok(reports);
|
||||||
|
}
|
||||||
|
|
||||||
// Construct the sealed event
|
// Skip sending if using encryption signer but receiver's encryption keys not found
|
||||||
let seal = EventBuilder::seal(&signer, &receiver, rumor.clone())
|
if encryption_pubkey.is_none() && matches!(signer_kind, SignerKind::Encryption) {
|
||||||
.await?
|
reports.push(SendReport::new(user_pubkey).device_not_found());
|
||||||
.build(user_pubkey)
|
return Ok(reports);
|
||||||
.sign(&signer)
|
}
|
||||||
.await?;
|
|
||||||
|
// Determine the receiver based on the signer kind
|
||||||
|
let receiver = Self::select_receiver(&signer_kind, user_pubkey, encryption_pubkey)?;
|
||||||
|
|
||||||
// Construct the gift-wrapped event
|
// Construct the gift-wrapped event
|
||||||
let event = EventBuilder::gift_wrap_from_seal(&receiver, &seal, vec![])?;
|
let event = EventBuilder::gift_wrap(
|
||||||
|
&signer,
|
||||||
|
&receiver,
|
||||||
|
rumor.clone(),
|
||||||
|
vec![Tag::public_key(user_pubkey)],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Only send a backup message to current user if sent successfully to others
|
// Only send a backup message to current user if sent successfully to others
|
||||||
if opts.backup() && reports.iter().all(|r| r.is_sent_success()) {
|
if reports.iter().all(|r| r.is_sent_success()) {
|
||||||
let urls = cache.relay(&user_pubkey).cloned().unwrap_or_default();
|
let urls = gossip.messaging_relays(&user_pubkey);
|
||||||
|
|
||||||
// Check if there are any relays to send the event to
|
// Check if there are any relays to send the event to
|
||||||
if urls.is_empty() {
|
if urls.is_empty() {
|
||||||
@@ -604,11 +597,8 @@ impl Room {
|
|||||||
return Ok(reports);
|
return Ok(reports);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure connection to all messaging relays
|
// Ensure connections to the relays
|
||||||
for url in urls.iter() {
|
gossip.ensure_connections(&client, &urls).await;
|
||||||
client.add_relay(url).await?;
|
|
||||||
client.connect_relay(url).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the event to the messaging relays
|
// Send the event to the messaging relays
|
||||||
match client.send_event_to(urls, &event).await {
|
match client.send_event_to(urls, &event).await {
|
||||||
@@ -635,10 +625,10 @@ impl Room {
|
|||||||
) -> Task<Result<Vec<SendReport>, Error>> {
|
) -> Task<Result<Vec<SendReport>, Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let cache_manager = nostr.read(cx).cache_manager();
|
let gossip = nostr.read(cx).gossip();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let cache = cache_manager.read().await;
|
let gossip = gossip.read().await;
|
||||||
let mut resend_reports = vec![];
|
let mut resend_reports = vec![];
|
||||||
|
|
||||||
for report in reports.into_iter() {
|
for report in reports.into_iter() {
|
||||||
@@ -667,12 +657,15 @@ impl Room {
|
|||||||
|
|
||||||
// Process the on hold event if it exists
|
// Process the on hold event if it exists
|
||||||
if let Some(event) = report.on_hold {
|
if let Some(event) = report.on_hold {
|
||||||
let urls = cache.relay(&receiver).cloned().unwrap_or_default();
|
let urls = gossip.messaging_relays(&receiver);
|
||||||
|
|
||||||
// Check if there are any relays to send the event to
|
// Check if there are any relays to send the event to
|
||||||
if urls.is_empty() {
|
if urls.is_empty() {
|
||||||
resend_reports.push(SendReport::new(receiver).relays_not_found());
|
resend_reports.push(SendReport::new(receiver).relays_not_found());
|
||||||
} else {
|
} else {
|
||||||
|
// Ensure connections to the relays
|
||||||
|
gossip.ensure_connections(&client, &urls).await;
|
||||||
|
|
||||||
// Send the event to the messaging relays
|
// Send the event to the messaging relays
|
||||||
match client.send_event_to(urls, &event).await {
|
match client.send_event_to(urls, &event).await {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
@@ -705,15 +698,15 @@ impl Room {
|
|||||||
|
|
||||||
fn select_receiver(
|
fn select_receiver(
|
||||||
kind: &SignerKind,
|
kind: &SignerKind,
|
||||||
members: PublicKey,
|
member: PublicKey,
|
||||||
encryption: Option<PublicKey>,
|
encryption: Option<PublicKey>,
|
||||||
) -> Result<PublicKey, Error> {
|
) -> Result<PublicKey, Error> {
|
||||||
match kind {
|
match kind {
|
||||||
SignerKind::Encryption => {
|
SignerKind::Encryption => {
|
||||||
Ok(encryption.ok_or_else(|| anyhow!("Receiver's encryption key not found"))?)
|
Ok(encryption.ok_or_else(|| anyhow!("Receiver's encryption key not found"))?)
|
||||||
}
|
}
|
||||||
SignerKind::User => Ok(members),
|
SignerKind::User => Ok(member),
|
||||||
SignerKind::Auto => Ok(encryption.unwrap_or(members)),
|
SignerKind::Auto => Ok(encryption.unwrap_or(member)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,13 +272,13 @@ impl ChatPanel {
|
|||||||
|
|
||||||
// Get the current room entity
|
// Get the current room entity
|
||||||
let room = self.room.read(cx);
|
let room = self.room.read(cx);
|
||||||
|
let opts = self.options.read(cx);
|
||||||
|
|
||||||
// Create a temporary message for optimistic update
|
// Create a temporary message for optimistic update
|
||||||
let rumor = room.create_message(&content, replies.as_ref(), cx);
|
let rumor = room.create_message(&content, replies.as_ref(), cx);
|
||||||
let rumor_id = rumor.id.unwrap();
|
let rumor_id = rumor.id.unwrap();
|
||||||
|
|
||||||
// Create a task for sending the message in the background
|
// Create a task for sending the message in the background
|
||||||
let opts = self.options.read(cx);
|
|
||||||
let send_message = room.send_message(&rumor, opts, cx);
|
let send_message = room.send_message(&rumor, opts, cx);
|
||||||
|
|
||||||
// Optimistically update message list
|
// Optimistically update message list
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ impl Compose {
|
|||||||
|
|
||||||
async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
||||||
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
||||||
|
|
||||||
client
|
client
|
||||||
|
|||||||
@@ -175,9 +175,6 @@ impl EditProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_metadata(&mut self, cx: &mut Context<Self>) -> Task<Result<Profile, Error>> {
|
pub fn set_metadata(&mut self, cx: &mut Context<Self>) -> Task<Result<Profile, Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
|
|
||||||
let avatar = self.avatar_input.read(cx).value().to_string();
|
let avatar = self.avatar_input.read(cx).value().to_string();
|
||||||
let name = self.name_input.read(cx).value().to_string();
|
let name = self.name_input.read(cx).value().to_string();
|
||||||
let bio = self.bio_input.read(cx).value().to_string();
|
let bio = self.bio_input.read(cx).value().to_string();
|
||||||
@@ -199,14 +196,24 @@ impl EditProfile {
|
|||||||
new_metadata = new_metadata.website(url);
|
new_metadata = new_metadata.website(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let client = nostr.read(cx).client();
|
||||||
|
let gossip = nostr.read(cx).gossip();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
let gossip = gossip.read().await;
|
||||||
|
let write_relays = gossip.inbox_relays(&public_key);
|
||||||
|
|
||||||
|
// Ensure connections to the write relays
|
||||||
|
gossip.ensure_connections(&client, &write_relays).await;
|
||||||
|
|
||||||
// Sign the new metadata event
|
// Sign the new metadata event
|
||||||
let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?;
|
let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?;
|
||||||
|
|
||||||
// Send event to user's write relayss
|
// Send event to user's write relayss
|
||||||
client.send_event(&event).await?;
|
client.send_event_to(write_relays, &event).await?;
|
||||||
|
|
||||||
// Return the updated profile
|
// Return the updated profile
|
||||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||||
|
|||||||
@@ -137,11 +137,12 @@ impl NewAccount {
|
|||||||
|
|
||||||
// Verify the signer
|
// Verify the signer
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
|
let nip65_relays = default_nip65_relays();
|
||||||
|
|
||||||
// Construct a NIP-65 event
|
// Construct a NIP-65 event
|
||||||
let event = EventBuilder::new(Kind::RelayList, "")
|
let event = EventBuilder::new(Kind::RelayList, "")
|
||||||
.tags(
|
.tags(
|
||||||
default_nip65_relays()
|
nip65_relays
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|(url, metadata)| Tag::relay_metadata(url, metadata)),
|
.map(|(url, metadata)| Tag::relay_metadata(url, metadata)),
|
||||||
@@ -152,6 +153,25 @@ impl NewAccount {
|
|||||||
// Set NIP-65 relays
|
// Set NIP-65 relays
|
||||||
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
|
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
|
||||||
|
|
||||||
|
// Ensure relays are connected
|
||||||
|
for (url, _metadata) in nip65_relays.iter() {
|
||||||
|
client.add_relay(url).await?;
|
||||||
|
client.connect_relay(url).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract only write relays
|
||||||
|
let write_relays: Vec<RelayUrl> = nip65_relays
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(url, metadata)| {
|
||||||
|
if metadata.is_none() || metadata == &Some(RelayMetadata::Write) {
|
||||||
|
Some(url.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(3)
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Construct a NIP-17 event
|
// Construct a NIP-17 event
|
||||||
let event = EventBuilder::new(Kind::InboxRelays, "")
|
let event = EventBuilder::new(Kind::InboxRelays, "")
|
||||||
.tags(default_nip17_relays().iter().cloned().map(Tag::relay))
|
.tags(default_nip17_relays().iter().cloned().map(Tag::relay))
|
||||||
@@ -159,13 +179,13 @@ impl NewAccount {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Set NIP-17 relays
|
// Set NIP-17 relays
|
||||||
client.send_event(&event).await?;
|
client.send_event_to(&write_relays, &event).await?;
|
||||||
|
|
||||||
// Construct a metadata event
|
// Construct a metadata event
|
||||||
let event = EventBuilder::metadata(&metadata).sign(&signer).await?;
|
let event = EventBuilder::metadata(&metadata).sign(&signer).await?;
|
||||||
|
|
||||||
// Set metadata
|
// Set metadata
|
||||||
client.send_event(&event).await?;
|
client.send_event_to(&write_relays, &event).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use common::{nip05_verify, shorten_pubkey, RenderedProfile, RenderedTimestamp, BOOTSTRAP_RELAYS};
|
use common::{nip05_verify, shorten_pubkey, RenderedProfile, RenderedTimestamp, BOOTSTRAP_RELAYS};
|
||||||
@@ -44,7 +43,7 @@ impl Screening {
|
|||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
let contact_check: Task<Result<(bool, Vec<Profile>), Error>> = cx.background_spawn({
|
let contact_check: Task<Result<(bool, Vec<Profile>), Error>> = cx.background_spawn({
|
||||||
let client = Arc::clone(&client);
|
let client = nostr.read(cx).client();
|
||||||
async move {
|
async move {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let signer_pubkey = signer.get_public_key().await?;
|
let signer_pubkey = signer.get_public_key().await?;
|
||||||
|
|||||||
@@ -155,10 +155,17 @@ impl SetupRelay {
|
|||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
let gossip = nostr.read(cx).gossip();
|
||||||
let relays = self.relays.clone();
|
let relays = self.relays.clone();
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
let gossip = gossip.read().await;
|
||||||
|
let write_relays = gossip.inbox_relays(&public_key);
|
||||||
|
|
||||||
|
// Ensure connections to the write relays
|
||||||
|
gossip.ensure_connections(&client, &write_relays).await;
|
||||||
|
|
||||||
let tags: Vec<Tag> = relays
|
let tags: Vec<Tag> = relays
|
||||||
.iter()
|
.iter()
|
||||||
@@ -171,7 +178,7 @@ impl SetupRelay {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Set messaging relays
|
// Set messaging relays
|
||||||
client.send_event(&event).await?;
|
client.send_event_to(write_relays, &event).await?;
|
||||||
|
|
||||||
// Connect to messaging relays
|
// Connect to messaging relays
|
||||||
for relay in relays.iter() {
|
for relay in relays.iter() {
|
||||||
|
|||||||
@@ -137,15 +137,13 @@ impl Sidebar {
|
|||||||
|
|
||||||
async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
async fn request_metadata(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
||||||
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
let filter = Filter::new().author(public_key).kinds(kinds).limit(10);
|
||||||
|
|
||||||
client
|
client
|
||||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
log::info!("Subscribe to get metadata for: {public_key}");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ pub struct Encryption {
|
|||||||
handle_requests: Option<Task<()>>,
|
handle_requests: Option<Task<()>>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
_subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
/// Tasks for asynchronous operations
|
||||||
_tasks: SmallVec<[Task<()>; 2]>,
|
_tasks: SmallVec<[Task<()>; 1]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encryption {
|
impl Encryption {
|
||||||
@@ -69,48 +69,36 @@ impl Encryption {
|
|||||||
/// Create a new encryption instance
|
/// Create a new encryption instance
|
||||||
fn new(cx: &mut Context<Self>) -> Self {
|
fn new(cx: &mut Context<Self>) -> Self {
|
||||||
let account = Account::global(cx);
|
let account = Account::global(cx);
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
|
|
||||||
let requests = cx.new(|_| HashSet::default());
|
let requests = cx.new(|_| HashSet::default());
|
||||||
let encryption = cx.new(|_| None);
|
let encryption = cx.new(|_| None);
|
||||||
let client_signer = cx.new(|_| None);
|
let client_signer = cx.new(|_| None);
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
let mut tasks = smallvec![];
|
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Observe the account state
|
// Observe the account state
|
||||||
cx.observe(&account, |this, state, cx| {
|
cx.observe(&account, |this, state, cx| {
|
||||||
if state.read(cx).has_account() && !this.has_encryption(cx) {
|
if state.read(cx).has_account() && this.client_signer.read(cx).is_none() {
|
||||||
|
this.get_client(cx);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
subscriptions.push(
|
||||||
|
// Observe the client signer state
|
||||||
|
cx.observe(&client_signer, |this, state, cx| {
|
||||||
|
if state.read(cx).is_some() {
|
||||||
this.get_announcement(cx);
|
this.get_announcement(cx);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
tasks.push(
|
subscriptions.push(
|
||||||
// Get the client key
|
// Observe the encryption signer state
|
||||||
cx.spawn(async move |this, cx| {
|
cx.observe(&encryption, |this, state, cx| {
|
||||||
match Self::get_keys(&client, "client").await {
|
if state.read(cx).is_some() {
|
||||||
Ok(keys) => {
|
this._tasks.push(this.resubscribe_messages(cx));
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_client(Arc::new(keys), cx);
|
|
||||||
})
|
|
||||||
.expect("Entity has been released");
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
let keys = Keys::generate();
|
|
||||||
let secret = keys.secret_key().to_secret_hex();
|
|
||||||
|
|
||||||
// Store the key in the database for future use
|
|
||||||
Self::set_keys(&client, "client", secret).await.ok();
|
|
||||||
|
|
||||||
// Update global state
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_client(Arc::new(keys), cx);
|
|
||||||
})
|
|
||||||
.expect("Entity has been released");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -123,7 +111,7 @@ impl Encryption {
|
|||||||
handle_notifications: None,
|
handle_notifications: None,
|
||||||
handle_requests: None,
|
handle_requests: None,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
_tasks: tasks,
|
_tasks: smallvec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,14 +162,50 @@ impl Encryption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the client keys from the database
|
||||||
|
fn get_client(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
|
self._tasks.push(
|
||||||
|
// Run in the main thread
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
match Self::get_keys(&client, "client").await {
|
||||||
|
Ok(keys) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_client(Arc::new(keys), cx);
|
||||||
|
})
|
||||||
|
.expect("Entity has been released");
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let keys = Keys::generate();
|
||||||
|
let secret = keys.secret_key().to_secret_hex();
|
||||||
|
|
||||||
|
// Store the key in the database for future use
|
||||||
|
Self::set_keys(&client, "client", secret).await.ok();
|
||||||
|
|
||||||
|
// Update global state
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_client(Arc::new(keys), cx);
|
||||||
|
})
|
||||||
|
.expect("Entity has been released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the announcement from the database
|
||||||
fn get_announcement(&mut self, cx: &mut Context<Self>) {
|
fn get_announcement(&mut self, cx: &mut Context<Self>) {
|
||||||
let task = self._get_announcement(cx);
|
let task = self._get_announcement(cx);
|
||||||
|
let delay = Duration::from_secs(10);
|
||||||
|
|
||||||
self._tasks.push(cx.spawn(async move |this, cx| {
|
self._tasks.push(
|
||||||
cx.background_executor().timer(Duration::from_secs(5)).await;
|
// Run task in the background
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
cx.background_executor().timer(delay).await;
|
||||||
|
|
||||||
match task.await {
|
if let Ok(announcement) = task.await {
|
||||||
Ok(announcement) => {
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.load_encryption(&announcement, cx);
|
this.load_encryption(&announcement, cx);
|
||||||
// Set the announcement
|
// Set the announcement
|
||||||
@@ -190,11 +214,8 @@ impl Encryption {
|
|||||||
})
|
})
|
||||||
.expect("Entity has been released");
|
.expect("Entity has been released");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
}),
|
||||||
log::error!("Failed to get announcement: {}", err);
|
);
|
||||||
}
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _get_announcement(&self, cx: &App) -> Task<Result<Announcement, Error>> {
|
fn _get_announcement(&self, cx: &App) -> Task<Result<Announcement, Error>> {
|
||||||
@@ -236,12 +257,65 @@ impl Encryption {
|
|||||||
this.listen_request(cx);
|
this.listen_request(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.load_response(cx);
|
||||||
})
|
})
|
||||||
.expect("Entity has been released");
|
.expect("Entity has been released");
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn load_response(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
|
// Get the client signer
|
||||||
|
let Some(client_signer) = self.client_signer.read(cx).clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.author(public_key)
|
||||||
|
.kind(Kind::Custom(4455))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||||
|
let response = NostrRegistry::extract_response(&client, &event).await?;
|
||||||
|
|
||||||
|
// Decrypt the payload using the client signer
|
||||||
|
let decrypted = client_signer
|
||||||
|
.nip44_decrypt(&response.public_key(), response.payload())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Construct the encryption keys
|
||||||
|
let secret = SecretKey::parse(&decrypted)?;
|
||||||
|
let keys = Keys::new(secret);
|
||||||
|
|
||||||
|
return Ok(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow!("not found"))
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
match task.await {
|
||||||
|
Ok(keys) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_encryption(Arc::new(keys), cx);
|
||||||
|
})
|
||||||
|
.expect("Entity has been released");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to load encryption response: {e}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
/// Listen for the encryption key request from other devices
|
/// Listen for the encryption key request from other devices
|
||||||
///
|
///
|
||||||
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||||
@@ -252,7 +326,7 @@ impl Encryption {
|
|||||||
let (tx, rx) = flume::bounded::<Announcement>(50);
|
let (tx, rx) = flume::bounded::<Announcement>(50);
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn({
|
let task: Task<Result<(), Error>> = cx.background_spawn({
|
||||||
let client = Arc::clone(&client);
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
@@ -325,6 +399,7 @@ impl Encryption {
|
|||||||
pub fn new_encryption(&self, cx: &App) -> Task<Result<Keys, Error>> {
|
pub fn new_encryption(&self, cx: &App) -> Task<Result<Keys, Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
let gossip = nostr.read(cx).gossip();
|
||||||
|
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let public_key = keys.public_key();
|
let public_key = keys.public_key();
|
||||||
@@ -336,6 +411,12 @@ impl Encryption {
|
|||||||
Self::set_keys(&client, "encryption", secret).await?;
|
Self::set_keys(&client, "encryption", secret).await?;
|
||||||
|
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
|
let signer_pubkey = signer.get_public_key().await?;
|
||||||
|
let gossip = gossip.read().await;
|
||||||
|
let write_relays = gossip.inbox_relays(&signer_pubkey);
|
||||||
|
|
||||||
|
// Ensure connections to the write relays
|
||||||
|
gossip.ensure_connections(&client, &write_relays).await;
|
||||||
|
|
||||||
// Construct the announcement event
|
// Construct the announcement event
|
||||||
let event = EventBuilder::new(Kind::Custom(10044), "")
|
let event = EventBuilder::new(Kind::Custom(10044), "")
|
||||||
@@ -343,11 +424,12 @@ impl Encryption {
|
|||||||
Tag::client(app_name()),
|
Tag::client(app_name()),
|
||||||
Tag::custom(TagKind::custom("n"), vec![public_key]),
|
Tag::custom(TagKind::custom("n"), vec![public_key]),
|
||||||
])
|
])
|
||||||
|
.build(signer_pubkey)
|
||||||
.sign(&signer)
|
.sign(&signer)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Send the announcement event to user's relays
|
// Send the announcement event to user's relays
|
||||||
client.send_event(&event).await?;
|
client.send_event_to(write_relays, &event).await?;
|
||||||
|
|
||||||
Ok(keys)
|
Ok(keys)
|
||||||
})
|
})
|
||||||
@@ -359,6 +441,7 @@ impl Encryption {
|
|||||||
pub fn send_request(&self, cx: &App) -> Task<Result<Option<Keys>, Error>> {
|
pub fn send_request(&self, cx: &App) -> Task<Result<Option<Keys>, Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
let gossip = nostr.read(cx).gossip();
|
||||||
|
|
||||||
// Get the client signer
|
// Get the client signer
|
||||||
let Some(client_signer) = self.client_signer.read(cx).clone() else {
|
let Some(client_signer) = self.client_signer.read(cx).clone() else {
|
||||||
@@ -395,6 +478,12 @@ impl Encryption {
|
|||||||
Ok(Some(keys))
|
Ok(Some(keys))
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
let gossip = gossip.read().await;
|
||||||
|
let write_relays = gossip.inbox_relays(&public_key);
|
||||||
|
|
||||||
|
// Ensure connections to the write relays
|
||||||
|
gossip.ensure_connections(&client, &write_relays).await;
|
||||||
|
|
||||||
// Construct encryption keys request event
|
// Construct encryption keys request event
|
||||||
let event = EventBuilder::new(Kind::Custom(4454), "")
|
let event = EventBuilder::new(Kind::Custom(4454), "")
|
||||||
.tags(vec![
|
.tags(vec![
|
||||||
@@ -405,7 +494,7 @@ impl Encryption {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Send a request for encryption keys from other devices
|
// Send a request for encryption keys from other devices
|
||||||
client.send_event(&event).await?;
|
client.send_event_to(&write_relays, &event).await?;
|
||||||
|
|
||||||
// Create a unique ID to control the subscription later
|
// Create a unique ID to control the subscription later
|
||||||
let subscription_id = SubscriptionId::new("listen-response");
|
let subscription_id = SubscriptionId::new("listen-response");
|
||||||
@@ -413,12 +502,11 @@ impl Encryption {
|
|||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::Custom(4455))
|
.kind(Kind::Custom(4455))
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.pubkey(client_pubkey)
|
|
||||||
.since(Timestamp::now());
|
.since(Timestamp::now());
|
||||||
|
|
||||||
// Subscribe to the approval response event
|
// Subscribe to the approval response event
|
||||||
client
|
client
|
||||||
.subscribe_with_id(subscription_id, filter, None)
|
.subscribe_with_id_to(&write_relays, subscription_id, filter, None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -433,6 +521,7 @@ impl Encryption {
|
|||||||
pub fn send_response(&self, target: PublicKey, cx: &App) -> Task<Result<(), Error>> {
|
pub fn send_response(&self, target: PublicKey, cx: &App) -> Task<Result<(), Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
let gossip = nostr.read(cx).gossip();
|
||||||
|
|
||||||
// Get the client signer
|
// Get the client signer
|
||||||
let Some(client_signer) = self.client_signer.read(cx).clone() else {
|
let Some(client_signer) = self.client_signer.read(cx).clone() else {
|
||||||
@@ -440,6 +529,14 @@ impl Encryption {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
let gossip = gossip.read().await;
|
||||||
|
let write_relays = gossip.inbox_relays(&public_key);
|
||||||
|
|
||||||
|
// Ensure connections to the write relays
|
||||||
|
gossip.ensure_connections(&client, &write_relays).await;
|
||||||
|
|
||||||
let encryption = Self::get_keys(&client, "encryption").await?;
|
let encryption = Self::get_keys(&client, "encryption").await?;
|
||||||
let client_pubkey = client_signer.get_public_key().await?;
|
let client_pubkey = client_signer.get_public_key().await?;
|
||||||
|
|
||||||
@@ -457,30 +554,12 @@ impl Encryption {
|
|||||||
Tag::custom(TagKind::custom("P"), vec![client_pubkey]),
|
Tag::custom(TagKind::custom("P"), vec![client_pubkey]),
|
||||||
Tag::public_key(target),
|
Tag::public_key(target),
|
||||||
])
|
])
|
||||||
.sign(&client_signer)
|
.build(public_key)
|
||||||
|
.sign(&signer)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Get the current user's signer and public key
|
|
||||||
let signer = client.signer().await?;
|
|
||||||
let public_key = signer.get_public_key().await?;
|
|
||||||
|
|
||||||
// Get the current user's relay list
|
|
||||||
let urls: Vec<RelayUrl> = client
|
|
||||||
.database()
|
|
||||||
.relay_list(public_key)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(url, metadata)| {
|
|
||||||
if metadata.is_none() || metadata == Some(RelayMetadata::Read) {
|
|
||||||
Some(url)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Send the response event to the user's relay list
|
// Send the response event to the user's relay list
|
||||||
client.send_event_to(urls, &event).await?;
|
client.send_event_to(write_relays, &event).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -493,12 +572,14 @@ impl Encryption {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
let client_signer = self.client_signer.read(cx).clone().unwrap();
|
// Get the client signer
|
||||||
let mut processed_events = HashSet::new();
|
let Some(client_signer) = self.client_signer.read(cx).clone() else {
|
||||||
|
return Task::ready(Err(anyhow!("Client Signer is required")));
|
||||||
|
};
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
log::info!("Listening for notifications");
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
while let Ok(notification) = notifications.recv().await {
|
while let Ok(notification) = notifications.recv().await {
|
||||||
let RelayPoolNotification::Message { message, .. } = notification else {
|
let RelayPoolNotification::Message { message, .. } = notification else {
|
||||||
@@ -513,7 +594,7 @@ impl Encryption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if event.kind != Kind::Custom(4455) {
|
if event.kind != Kind::Custom(4455) {
|
||||||
// Skip non-gift wrap events
|
// Skip non-response events
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,6 +609,8 @@ impl Encryption {
|
|||||||
let keys = Keys::new(secret);
|
let keys = Keys::new(secret);
|
||||||
|
|
||||||
return Ok(keys);
|
return Ok(keys);
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to extract response from event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,4 +662,21 @@ impl Encryption {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resubscribe to gift wrap events
|
||||||
|
fn resubscribe_messages(&self, cx: &App) -> Task<()> {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let client = nostr.read(cx).client();
|
||||||
|
let gossip = nostr.read(cx).gossip();
|
||||||
|
|
||||||
|
let account = Account::global(cx);
|
||||||
|
let public_key = account.read(cx).public_key();
|
||||||
|
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let gossip = gossip.read().await;
|
||||||
|
let relays = gossip.messaging_relays(&public_key);
|
||||||
|
|
||||||
|
NostrRegistry::get_messages(&client, public_key, &relays).await;
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::cell::Cell;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -181,6 +183,7 @@ impl EncryptionPanel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
this.set_requesting(false, cx);
|
||||||
this.set_error(e.to_string(), cx);
|
this.set_error(e.to_string(), cx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -193,11 +196,13 @@ impl EncryptionPanel {
|
|||||||
fn ask_for_approval(&mut self, req: Announcement, window: &mut Window, cx: &mut Context<Self>) {
|
fn ask_for_approval(&mut self, req: Announcement, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let client_name = SharedString::from(req.client().to_string());
|
let client_name = SharedString::from(req.client().to_string());
|
||||||
let target = req.public_key();
|
let target = req.public_key();
|
||||||
|
let id = SharedString::from(req.id().to_hex());
|
||||||
|
let loading = Rc::new(Cell::new(false));
|
||||||
|
|
||||||
let note = Notification::new()
|
let note = Notification::new()
|
||||||
.custom_id(SharedString::from(req.id().to_hex()))
|
.custom_id(id.clone())
|
||||||
.autohide(false)
|
.autohide(false)
|
||||||
.icon(IconName::Info)
|
.icon(IconName::Encryption)
|
||||||
.title(SharedString::from("Encryption Key Request"))
|
.title(SharedString::from("Encryption Key Request"))
|
||||||
.content(move |_window, cx| {
|
.content(move |_window, cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -208,14 +213,24 @@ impl EncryptionPanel {
|
|||||||
))
|
))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.py_1()
|
.h_12()
|
||||||
.px_1p5()
|
.items_center()
|
||||||
.rounded_sm()
|
.justify_center()
|
||||||
.text_xs()
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
.bg(cx.theme().warning_background)
|
.bg(cx.theme().warning_background)
|
||||||
.text_color(cx.theme().warning_foreground)
|
.text_color(cx.theme().warning_foreground)
|
||||||
.child(client_name.clone()),
|
.child(client_name.clone()),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.h_7()
|
||||||
|
.w_full()
|
||||||
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.bg(cx.theme().elevated_surface_background)
|
||||||
|
.child(SharedString::from(target.to_hex())),
|
||||||
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
})
|
})
|
||||||
.action(move |_window, _cx| {
|
.action(move |_window, _cx| {
|
||||||
@@ -223,13 +238,38 @@ impl EncryptionPanel {
|
|||||||
.label("Approve")
|
.label("Approve")
|
||||||
.small()
|
.small()
|
||||||
.primary()
|
.primary()
|
||||||
.loading(false)
|
.loading(loading.get())
|
||||||
.disabled(false)
|
.disabled(loading.get())
|
||||||
.on_click(move |_ev, _window, cx| {
|
.on_click({
|
||||||
|
let loading = Rc::clone(&loading);
|
||||||
|
let id = id.clone();
|
||||||
|
|
||||||
|
move |_ev, window, cx| {
|
||||||
|
// Set loading state to true
|
||||||
|
loading.set(true);
|
||||||
|
|
||||||
let encryption = Encryption::global(cx);
|
let encryption = Encryption::global(cx);
|
||||||
let send_response = encryption.read(cx).send_response(target, cx);
|
let send_response = encryption.read(cx).send_response(target, cx);
|
||||||
|
let id = id.clone();
|
||||||
|
|
||||||
send_response.detach();
|
window
|
||||||
|
.spawn(cx, async move |cx| {
|
||||||
|
let result = send_response.await;
|
||||||
|
|
||||||
|
cx.update(|window, cx| {
|
||||||
|
match result {
|
||||||
|
Ok(_) => {
|
||||||
|
window.clear_notification_by_id(id, cx);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
window.push_notification(e.to_string(), cx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.expect("Entity has been released");
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -252,6 +292,7 @@ impl Render for EncryptionPanel {
|
|||||||
const WARNING: &str = "Encryption Key is still in the alpha stage. Please be cautious.";
|
const WARNING: &str = "Encryption Key is still in the alpha stage. Please be cautious.";
|
||||||
|
|
||||||
let encryption = Encryption::global(cx);
|
let encryption = Encryption::global(cx);
|
||||||
|
let announcement = encryption.read(cx).announcement();
|
||||||
let has_encryption = encryption.read(cx).has_encryption(cx);
|
let has_encryption = encryption.read(cx).has_encryption(cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -259,39 +300,49 @@ impl Render for EncryptionPanel {
|
|||||||
.max_w(px(340.))
|
.max_w(px(340.))
|
||||||
.w(px(340.))
|
.w(px(340.))
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.when(has_encryption, |this| {
|
.when_some(announcement.as_ref(), |this, announcement| {
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.w_full()
|
|
||||||
.text_xs()
|
|
||||||
.font_semibold()
|
|
||||||
.child(
|
|
||||||
Icon::new(IconName::CheckCircleFill)
|
|
||||||
.small()
|
|
||||||
.text_color(cx.theme().element_active),
|
|
||||||
)
|
|
||||||
.child(SharedString::from("Encryption Key has been set")),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(!has_encryption, |this| {
|
|
||||||
if let Some(announcement) = encryption.read(cx).announcement().as_ref() {
|
|
||||||
let pubkey = shorten_pubkey(announcement.public_key(), 16);
|
let pubkey = shorten_pubkey(announcement.public_key(), 16);
|
||||||
let name = announcement.client();
|
let name = announcement.client();
|
||||||
|
|
||||||
this.child(
|
this.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(div().font_semibold().child(SharedString::from(NOTICE)))
|
.when(has_encryption, |this| {
|
||||||
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1p5()
|
||||||
|
.text_sm()
|
||||||
|
.font_semibold()
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::CheckCircle)
|
||||||
|
.text_color(cx.theme().element_foreground)
|
||||||
|
.small(),
|
||||||
|
)
|
||||||
|
.child(SharedString::from("Encryption Key has been set")),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(!has_encryption, |this| {
|
||||||
|
this.child(div().font_semibold().child(SharedString::from(NOTICE)))
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from("Client Name:")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
.h_12()
|
.h_12()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.rounded(cx.theme().radius)
|
.rounded(cx.theme().radius)
|
||||||
.bg(cx.theme().warning_background)
|
.bg(cx.theme().elevated_surface_background)
|
||||||
.text_color(cx.theme().warning_foreground)
|
|
||||||
.child(name),
|
.child(name),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -313,6 +364,10 @@ impl Render for EncryptionPanel {
|
|||||||
.child(SharedString::from(pubkey)),
|
.child(SharedString::from(pubkey)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.when(!has_encryption, |this| {
|
||||||
|
this.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
@@ -361,18 +416,12 @@ impl Render for EncryptionPanel {
|
|||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
),
|
||||||
.when_some(self.error.read(cx).as_ref(), |this, error| {
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.text_center()
|
|
||||||
.text_color(cx.theme().danger_foreground)
|
|
||||||
.child(error.clone()),
|
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else {
|
})
|
||||||
|
.when_none(&announcement, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
@@ -401,7 +450,15 @@ impl Render for EncryptionPanel {
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
.when_some(self.error.read(cx).as_ref(), |this, error| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_center()
|
||||||
|
.text_color(cx.theme().danger_foreground)
|
||||||
|
.child(error.clone()),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Task};
|
use gpui::{App, AppContext, Context, Entity, Global, Task};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
@@ -44,12 +43,10 @@ impl PersonRegistry {
|
|||||||
tasks.push(
|
tasks.push(
|
||||||
// Handle notifications
|
// Handle notifications
|
||||||
cx.spawn({
|
cx.spawn({
|
||||||
let client = Arc::clone(&client);
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
async move |this, cx| {
|
async move |this, cx| {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
log::info!("Listening for notifications");
|
|
||||||
|
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
while let Ok(notification) = notifications.recv().await {
|
while let Ok(notification) = notifications.recv().await {
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ pub use tracker::*;
|
|||||||
mod storage;
|
mod storage;
|
||||||
mod tracker;
|
mod tracker;
|
||||||
|
|
||||||
pub const GIFTWRAP_SUBSCRIPTION: &str = "default-inbox";
|
pub const GIFTWRAP_SUBSCRIPTION: &str = "gift-wrap-events";
|
||||||
pub const ENCRYPTION_GIFTWARP_SUBSCRIPTION: &str = "encryption-inbox";
|
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
NostrRegistry::set_global(cx.new(NostrRegistry::new), cx);
|
NostrRegistry::set_global(cx.new(NostrRegistry::new), cx);
|
||||||
@@ -30,15 +29,15 @@ impl Global for GlobalNostrRegistry {}
|
|||||||
/// Nostr Registry
|
/// Nostr Registry
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NostrRegistry {
|
pub struct NostrRegistry {
|
||||||
/// Nostr client instance
|
/// Nostr Client
|
||||||
client: Arc<Client>,
|
client: Client,
|
||||||
|
|
||||||
|
/// Custom gossip implementation
|
||||||
|
gossip: Arc<RwLock<Gossip>>,
|
||||||
|
|
||||||
/// Tracks activity related to Nostr events
|
/// Tracks activity related to Nostr events
|
||||||
tracker: Arc<RwLock<EventTracker>>,
|
tracker: Arc<RwLock<EventTracker>>,
|
||||||
|
|
||||||
/// Manages caching of nostr events
|
|
||||||
cache_manager: Arc<RwLock<CacheManager>>,
|
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
/// Tasks for asynchronous operations
|
||||||
_tasks: SmallVec<[Task<()>; 1]>,
|
_tasks: SmallVec<[Task<()>; 1]>,
|
||||||
}
|
}
|
||||||
@@ -63,11 +62,7 @@ impl NostrRegistry {
|
|||||||
.install_default()
|
.install_default()
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
let path = config_dir().join("nostr");
|
// Construct the nostr client options
|
||||||
let lmdb = NostrLMDB::open(path).expect("Failed to initialize database");
|
|
||||||
let gossip = NostrGossipMemory::unbounded();
|
|
||||||
|
|
||||||
// Nostr client options
|
|
||||||
let opts = ClientOptions::new()
|
let opts = ClientOptions::new()
|
||||||
.automatic_authentication(false)
|
.automatic_authentication(false)
|
||||||
.verify_subscriptions(false)
|
.verify_subscriptions(false)
|
||||||
@@ -76,16 +71,12 @@ impl NostrRegistry {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Construct the nostr client
|
// Construct the nostr client
|
||||||
let client = Arc::new(
|
let path = config_dir().join("nostr");
|
||||||
ClientBuilder::default()
|
let lmdb = NostrLMDB::open(path).expect("Failed to initialize database");
|
||||||
.gossip(gossip)
|
let client = ClientBuilder::default().database(lmdb).opts(opts).build();
|
||||||
.database(lmdb)
|
|
||||||
.opts(opts)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let tracker = Arc::new(RwLock::new(EventTracker::default()));
|
let tracker = Arc::new(RwLock::new(EventTracker::default()));
|
||||||
let cache_manager = Arc::new(RwLock::new(CacheManager::default()));
|
let gossip = Arc::new(RwLock::new(Gossip::default()));
|
||||||
|
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
@@ -94,8 +85,8 @@ impl NostrRegistry {
|
|||||||
//
|
//
|
||||||
// And handle notifications from the nostr relay pool channel
|
// And handle notifications from the nostr relay pool channel
|
||||||
cx.background_spawn({
|
cx.background_spawn({
|
||||||
let client = Arc::clone(&client);
|
let client = client.clone();
|
||||||
let cache_manager = Arc::clone(&cache_manager);
|
let gossip = Arc::clone(&gossip);
|
||||||
let tracker = Arc::clone(&tracker);
|
let tracker = Arc::clone(&tracker);
|
||||||
let _ = initialized_at();
|
let _ = initialized_at();
|
||||||
|
|
||||||
@@ -104,7 +95,7 @@ impl NostrRegistry {
|
|||||||
Self::connect(&client).await;
|
Self::connect(&client).await;
|
||||||
|
|
||||||
// Handle notifications from the relay pool
|
// Handle notifications from the relay pool
|
||||||
Self::handle_notifications(&client, &cache_manager, &tracker).await;
|
Self::handle_notifications(&client, &gossip, &tracker).await;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -112,7 +103,7 @@ impl NostrRegistry {
|
|||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
tracker,
|
tracker,
|
||||||
cache_manager,
|
gossip,
|
||||||
_tasks: tasks,
|
_tasks: tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,12 +126,10 @@ impl NostrRegistry {
|
|||||||
|
|
||||||
async fn handle_notifications(
|
async fn handle_notifications(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
cache: &Arc<RwLock<CacheManager>>,
|
gossip: &Arc<RwLock<Gossip>>,
|
||||||
tracker: &Arc<RwLock<EventTracker>>,
|
tracker: &Arc<RwLock<EventTracker>>,
|
||||||
) {
|
) {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
log::info!("Listening for notifications");
|
|
||||||
|
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
while let Ok(notification) = notifications.recv().await {
|
while let Ok(notification) = notifications.recv().await {
|
||||||
@@ -158,53 +147,50 @@ impl NostrRegistry {
|
|||||||
|
|
||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::RelayList => {
|
Kind::RelayList => {
|
||||||
if Self::is_self_authored(client, &event).await {
|
let mut gossip = gossip.write().await;
|
||||||
log::info!("Found relay list event for the current user");
|
gossip.insert_relays(&event);
|
||||||
let author = event.pubkey;
|
|
||||||
let announcement = Kind::Custom(10044);
|
let urls: Vec<RelayUrl> = Self::extract_write_relays(&event);
|
||||||
|
let author = event.pubkey;
|
||||||
|
|
||||||
// Fetch user's messaging relays event
|
|
||||||
_ = Self::subscribe(client, author, Kind::InboxRelays).await;
|
|
||||||
// Fetch user's encryption announcement event
|
// Fetch user's encryption announcement event
|
||||||
_ = Self::subscribe(client, author, announcement).await;
|
Self::get(client, &urls, author, Kind::Custom(10044)).await;
|
||||||
|
// Fetch user's messaging relays event
|
||||||
|
Self::get(client, &urls, author, Kind::InboxRelays).await;
|
||||||
|
|
||||||
|
// Verify if the event is belonging to the current user
|
||||||
|
if Self::is_self_authored(client, &event).await {
|
||||||
// Fetch user's metadata event
|
// Fetch user's metadata event
|
||||||
_ = Self::subscribe(client, author, Kind::Metadata).await;
|
Self::get(client, &urls, author, Kind::Metadata).await;
|
||||||
// Fetch user's contact list event
|
// Fetch user's contact list event
|
||||||
_ = Self::subscribe(client, author, Kind::ContactList).await;
|
Self::get(client, &urls, author, Kind::ContactList).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kind::InboxRelays => {
|
Kind::InboxRelays => {
|
||||||
// Extract up to 3 messaging relays
|
let mut gossip = gossip.write().await;
|
||||||
let urls: Vec<RelayUrl> =
|
gossip.insert_messaging_relays(&event);
|
||||||
nip17::extract_relay_list(&event).take(3).cloned().collect();
|
|
||||||
|
|
||||||
// Subscribe to gift wrap events if event is from current user
|
|
||||||
if Self::is_self_authored(client, &event).await {
|
if Self::is_self_authored(client, &event).await {
|
||||||
log::info!("Found messaging list event for the current user");
|
// Extract user's messaging relays
|
||||||
|
let urls: Vec<RelayUrl> =
|
||||||
|
nip17::extract_relay_list(&event).cloned().collect();
|
||||||
|
|
||||||
if let Err(e) =
|
// Fetch user's inbox messages in the extracted relays
|
||||||
Self::get_messages(client, &urls, event.pubkey).await
|
Self::get_messages(client, event.pubkey, &urls).await;
|
||||||
{
|
|
||||||
log::error!("Failed to subscribe to gift wrap events: {e}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the messaging relays
|
|
||||||
let mut cache = cache.write().await;
|
|
||||||
cache.insert_relay(event.pubkey, urls);
|
|
||||||
}
|
|
||||||
Kind::Custom(10044) => {
|
Kind::Custom(10044) => {
|
||||||
// Cache the announcement event
|
let mut gossip = gossip.write().await;
|
||||||
if let Ok(announcement) = Self::extract_announcement(&event) {
|
gossip.insert_announcement(&event);
|
||||||
let mut cache = cache.write().await;
|
|
||||||
cache.insert_announcement(event.pubkey, Some(announcement));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Kind::ContactList => {
|
Kind::ContactList => {
|
||||||
if Self::is_self_authored(client, &event).await {
|
if Self::is_self_authored(client, &event).await {
|
||||||
let pubkeys: Vec<_> = event.tags.public_keys().copied().collect();
|
let public_keys: Vec<PublicKey> =
|
||||||
|
event.tags.public_keys().copied().collect();
|
||||||
|
|
||||||
if let Err(e) = Self::get_metadata_for_list(client, pubkeys).await {
|
if let Err(e) =
|
||||||
|
Self::get_metadata_for_list(client, public_keys).await
|
||||||
|
{
|
||||||
log::error!("Failed to get metadata for list: {e}");
|
log::error!("Failed to get metadata for list: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,47 +228,59 @@ impl NostrRegistry {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribe for events that match the given kind for a given author
|
/// Get event that match the given kind for a given author
|
||||||
async fn subscribe(client: &Client, author: PublicKey, kind: Kind) -> Result<(), Error> {
|
async fn get(client: &Client, urls: &[RelayUrl], author: PublicKey, kind: Kind) {
|
||||||
|
// Skip if no relays are provided
|
||||||
|
if urls.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure relay connections
|
||||||
|
for url in urls.iter() {
|
||||||
|
client.add_relay(url).await.ok();
|
||||||
|
client.connect_relay(url).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let filter = Filter::new().author(author).kind(kind).limit(1);
|
let filter = Filter::new().author(author).kind(kind).limit(1);
|
||||||
|
|
||||||
// Subscribe to filters from the user's write relays
|
// Subscribe to filters from the user's write relays
|
||||||
client.subscribe(filter, Some(opts)).await?;
|
if let Err(e) = client.subscribe_to(urls, filter, Some(opts)).await {
|
||||||
|
log::error!("Failed to subscribe: {}", e);
|
||||||
Ok(())
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all gift wrap events in the messaging relays for a given public key
|
/// Get all gift wrap events in the messaging relays for a given public key
|
||||||
async fn get_messages(
|
pub async fn get_messages(client: &Client, public_key: PublicKey, urls: &[RelayUrl]) {
|
||||||
client: &Client,
|
// Verify that there are relays provided
|
||||||
urls: &[RelayUrl],
|
if urls.is_empty() {
|
||||||
public_key: PublicKey,
|
return;
|
||||||
) -> Result<(), Error> {
|
}
|
||||||
|
|
||||||
|
// Ensure relay connection
|
||||||
|
for url in urls.iter() {
|
||||||
|
client.add_relay(url).await.ok();
|
||||||
|
client.connect_relay(url).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
||||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||||
|
|
||||||
// Verify that there are relays provided
|
// Unsubscribe from the previous subscription
|
||||||
if urls.is_empty() {
|
client.unsubscribe(&id).await;
|
||||||
return Err(anyhow!("No relays provided"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add and connect relays
|
|
||||||
for url in urls {
|
|
||||||
client.add_relay(url).await?;
|
|
||||||
client.connect_relay(url).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe to filters to user's messaging relays
|
// Subscribe to filters to user's messaging relays
|
||||||
client.subscribe_with_id_to(urls, id, filter, None).await?;
|
if let Err(e) = client.subscribe_with_id_to(urls, id, filter, None).await {
|
||||||
|
log::error!("Failed to subscribe: {}", e);
|
||||||
Ok(())
|
} else {
|
||||||
|
log::info!("Subscribed to gift wrap events for public key {public_key}",);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get metadata for a list of public keys
|
/// Get metadata for a list of public keys
|
||||||
async fn get_metadata_for_list(client: &Client, pubkeys: Vec<PublicKey>) -> Result<(), Error> {
|
async fn get_metadata_for_list(client: &Client, pubkeys: Vec<PublicKey>) -> Result<(), Error> {
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
||||||
|
|
||||||
// Return if the list is empty
|
// Return if the list is empty
|
||||||
if pubkeys.is_empty() {
|
if pubkeys.is_empty() {
|
||||||
@@ -290,7 +288,7 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.limit(pubkeys.len() * kinds.len() + 10)
|
.limit(pubkeys.len() * kinds.len())
|
||||||
.authors(pubkeys)
|
.authors(pubkeys)
|
||||||
.kinds(kinds);
|
.kinds(kinds);
|
||||||
|
|
||||||
@@ -302,6 +300,19 @@ impl NostrRegistry {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_write_relays(event: &Event) -> Vec<RelayUrl> {
|
||||||
|
nip65::extract_relay_list(event)
|
||||||
|
.filter_map(|(url, metadata)| {
|
||||||
|
if metadata.is_none() || metadata == &Some(RelayMetadata::Write) {
|
||||||
|
Some(url.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(3)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract an encryption keys announcement from an event.
|
/// Extract an encryption keys announcement from an event.
|
||||||
pub fn extract_announcement(event: &Event) -> Result<Announcement, Error> {
|
pub fn extract_announcement(event: &Event) -> Result<Announcement, Error> {
|
||||||
let public_key = event
|
let public_key = event
|
||||||
@@ -342,8 +353,8 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the nostr client.
|
/// Returns a reference to the nostr client.
|
||||||
pub fn client(&self) -> Arc<Client> {
|
pub fn client(&self) -> Client {
|
||||||
Arc::clone(&self.client)
|
self.client.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the event tracker.
|
/// Returns a reference to the event tracker.
|
||||||
@@ -352,7 +363,7 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the cache manager.
|
/// Returns a reference to the cache manager.
|
||||||
pub fn cache_manager(&self) -> Arc<RwLock<CacheManager>> {
|
pub fn gossip(&self) -> Arc<RwLock<Gossip>> {
|
||||||
Arc::clone(&self.cache_manager)
|
Arc::clone(&self.gossip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use std::collections::{HashMap, HashSet};
|
|||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
use crate::NostrRegistry;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Announcement {
|
pub struct Announcement {
|
||||||
id: EventId,
|
id: EventId,
|
||||||
@@ -56,32 +58,129 @@ impl Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct CacheManager {
|
pub struct Gossip {
|
||||||
/// Cache of messaging relays for each public key
|
/// Gossip relays for each public key
|
||||||
relay: HashMap<PublicKey, HashSet<RelayUrl>>,
|
relays: HashMap<PublicKey, HashSet<(RelayUrl, Option<RelayMetadata>)>>,
|
||||||
|
|
||||||
/// Cache of device announcement for each public key
|
/// Messaging relays for each public key
|
||||||
announcement: HashMap<PublicKey, Option<Announcement>>,
|
messaging_relays: HashMap<PublicKey, HashSet<RelayUrl>>,
|
||||||
|
|
||||||
|
/// Encryption announcement for each public key
|
||||||
|
announcements: HashMap<PublicKey, Option<Announcement>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CacheManager {
|
impl Gossip {
|
||||||
pub fn relay(&self, public_key: &PublicKey) -> Option<&HashSet<RelayUrl>> {
|
/// Get inbox relays for a public key
|
||||||
self.relay.get(public_key)
|
pub fn inbox_relays(&self, public_key: &PublicKey) -> Vec<RelayUrl> {
|
||||||
|
self.relays
|
||||||
|
.get(public_key)
|
||||||
|
.map(|relays| {
|
||||||
|
relays
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(url, metadata)| {
|
||||||
|
if metadata.is_none() || metadata == &Some(RelayMetadata::Write) {
|
||||||
|
Some(url.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_relay(&mut self, public_key: PublicKey, urls: Vec<RelayUrl>) {
|
/// Get outbox relays for a public key
|
||||||
self.relay.entry(public_key).or_default().extend(urls);
|
pub fn outbox_relays(&self, public_key: &PublicKey) -> Vec<RelayUrl> {
|
||||||
|
self.relays
|
||||||
|
.get(public_key)
|
||||||
|
.map(|relays| {
|
||||||
|
relays
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(url, metadata)| {
|
||||||
|
if metadata.is_none() || metadata == &Some(RelayMetadata::Read) {
|
||||||
|
Some(url.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn announcement(&self, public_key: &PublicKey) -> Option<&Option<Announcement>> {
|
/// Insert gossip relays for a public key
|
||||||
self.announcement.get(public_key)
|
pub fn insert_relays(&mut self, event: &Event) {
|
||||||
|
self.relays.entry(event.pubkey).or_default().extend(
|
||||||
|
event
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.filter_map(|tag| {
|
||||||
|
if let Some(TagStandard::RelayMetadata {
|
||||||
|
relay_url,
|
||||||
|
metadata,
|
||||||
|
}) = tag.clone().to_standardized()
|
||||||
|
{
|
||||||
|
Some((relay_url, metadata))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(3),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_announcement(
|
/// Get messaging relays for a public key
|
||||||
&mut self,
|
pub fn messaging_relays(&self, public_key: &PublicKey) -> Vec<RelayUrl> {
|
||||||
public_key: PublicKey,
|
self.messaging_relays
|
||||||
announcement: Option<Announcement>,
|
.get(public_key)
|
||||||
) {
|
.cloned()
|
||||||
self.announcement.insert(public_key, announcement);
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert messaging relays for a public key
|
||||||
|
pub fn insert_messaging_relays(&mut self, event: &Event) {
|
||||||
|
self.messaging_relays
|
||||||
|
.entry(event.pubkey)
|
||||||
|
.or_default()
|
||||||
|
.extend(
|
||||||
|
event
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.filter_map(|tag| {
|
||||||
|
if let Some(TagStandard::Relay(url)) = tag.as_standardized() {
|
||||||
|
Some(url.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(3),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure connections for the given relay list
|
||||||
|
pub async fn ensure_connections(&self, client: &Client, urls: &[RelayUrl]) {
|
||||||
|
for url in urls {
|
||||||
|
client.add_relay(url).await.ok();
|
||||||
|
client.connect_relay(url).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get announcement for a public key
|
||||||
|
pub fn announcement(&self, public_key: &PublicKey) -> Option<Announcement> {
|
||||||
|
self.announcements
|
||||||
|
.get(public_key)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert announcement for a public key
|
||||||
|
pub fn insert_announcement(&mut self, event: &Event) {
|
||||||
|
let announcement = NostrRegistry::extract_announcement(event).ok();
|
||||||
|
|
||||||
|
self.announcements
|
||||||
|
.entry(event.pubkey)
|
||||||
|
.or_insert(announcement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ pub fn initialized_at() -> &'static Timestamp {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct EventTracker {
|
pub struct EventTracker {
|
||||||
/// Tracking events that have failed to unwrap
|
|
||||||
pub failed_unwrap_events: Vec<Event>,
|
|
||||||
|
|
||||||
/// Tracking events that have been resent by Coop in the current session
|
/// Tracking events that have been resent by Coop in the current session
|
||||||
pub resent_ids: Vec<Output<EventId>>,
|
pub resent_ids: Vec<Output<EventId>>,
|
||||||
|
|
||||||
@@ -28,10 +25,6 @@ pub struct EventTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventTracker {
|
impl EventTracker {
|
||||||
pub fn failed_unwrap_events(&self) -> &Vec<Event> {
|
|
||||||
&self.failed_unwrap_events
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resent_ids(&self) -> &Vec<Output<EventId>> {
|
pub fn resent_ids(&self) -> &Vec<Output<EventId>> {
|
||||||
&self.resent_ids
|
&self.resent_ids
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user