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