Continue redesign for the v1 stable release #5

Merged
reya merged 11 commits from redesign-2 into master 2026-02-12 08:32:17 +00:00
18 changed files with 391 additions and 252 deletions
Showing only changes of commit fce4c1bbcd - Show all commits

63
Cargo.lock generated
View File

@@ -1264,7 +1264,7 @@ dependencies = [
[[package]] [[package]]
name = "collections" name = "collections"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
@@ -1709,7 +1709,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2632,7 +2632,7 @@ dependencies = [
[[package]] [[package]]
name = "gpui" name = "gpui"
version = "0.2.2" version = "0.2.2"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"as-raw-xcb-connection", "as-raw-xcb-connection",
@@ -2734,7 +2734,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -2745,7 +2745,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"gpui", "gpui",
@@ -2967,7 +2967,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-compression", "async-compression",
@@ -2992,7 +2992,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"rustls", "rustls",
"rustls-platform-verifier", "rustls-platform-verifier",
@@ -3742,7 +3742,7 @@ dependencies = [
[[package]] [[package]]
name = "media" name = "media"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bindgen", "bindgen",
@@ -3982,7 +3982,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]] [[package]]
name = "nostr" name = "nostr"
version = "0.44.1" version = "0.44.1"
source = "git+https://github.com/rust-nostr/nostr#c25727493b52af561ed6b6aca43cf866ef7aeb16" source = "git+https://github.com/rust-nostr/nostr#8b6a6d797f0a7b7fd9147860b3cb00bdd6fc92cd"
dependencies = [ dependencies = [
"aes", "aes",
"base64", "base64",
@@ -4007,7 +4007,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#c25727493b52af561ed6b6aca43cf866ef7aeb16" source = "git+https://github.com/rust-nostr/nostr#8b6a6d797f0a7b7fd9147860b3cb00bdd6fc92cd"
dependencies = [ dependencies = [
"async-utility", "async-utility",
"futures-core", "futures-core",
@@ -4020,7 +4020,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#c25727493b52af561ed6b6aca43cf866ef7aeb16" source = "git+https://github.com/rust-nostr/nostr#8b6a6d797f0a7b7fd9147860b3cb00bdd6fc92cd"
dependencies = [ dependencies = [
"btreecap", "btreecap",
"flatbuffers", "flatbuffers",
@@ -4032,15 +4032,27 @@ 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#c25727493b52af561ed6b6aca43cf866ef7aeb16" source = "git+https://github.com/rust-nostr/nostr#8b6a6d797f0a7b7fd9147860b3cb00bdd6fc92cd"
dependencies = [ dependencies = [
"nostr", "nostr",
] ]
[[package]]
name = "nostr-gossip-memory"
version = "0.44.0"
source = "git+https://github.com/rust-nostr/nostr#8b6a6d797f0a7b7fd9147860b3cb00bdd6fc92cd"
dependencies = [
"indexmap",
"lru",
"nostr",
"nostr-gossip",
"tokio",
]
[[package]] [[package]]
name = "nostr-lmdb" name = "nostr-lmdb"
version = "0.44.0" version = "0.44.0"
source = "git+https://github.com/rust-nostr/nostr#c25727493b52af561ed6b6aca43cf866ef7aeb16" source = "git+https://github.com/rust-nostr/nostr#8b6a6d797f0a7b7fd9147860b3cb00bdd6fc92cd"
dependencies = [ dependencies = [
"async-utility", "async-utility",
"flume", "flume",
@@ -4054,7 +4066,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#c25727493b52af561ed6b6aca43cf866ef7aeb16" source = "git+https://github.com/rust-nostr/nostr#8b6a6d797f0a7b7fd9147860b3cb00bdd6fc92cd"
dependencies = [ dependencies = [
"async-utility", "async-utility",
"async-wsocket", "async-wsocket",
@@ -4588,7 +4600,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"collections", "collections",
"serde", "serde",
@@ -5237,7 +5249,7 @@ dependencies = [
[[package]] [[package]]
name = "refineable" name = "refineable"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"derive_refineable", "derive_refineable",
] ]
@@ -5336,7 +5348,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -5391,7 +5403,7 @@ dependencies = [
[[package]] [[package]]
name = "rope" name = "rope"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"log", "log",
@@ -5670,7 +5682,7 @@ dependencies = [
[[package]] [[package]]
name = "scheduler" name = "scheduler"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"async-task", "async-task",
"backtrace", "backtrace",
@@ -6189,6 +6201,7 @@ dependencies = [
"gpui_tokio", "gpui_tokio",
"log", "log",
"nostr-connect", "nostr-connect",
"nostr-gossip-memory",
"nostr-lmdb", "nostr-lmdb",
"nostr-sdk", "nostr-sdk",
"petname", "petname",
@@ -6273,7 +6286,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"log", "log",
@@ -7258,7 +7271,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "util" name = "util"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-fs", "async-fs",
@@ -7296,7 +7309,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#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"perf", "perf",
"quote", "quote",
@@ -8772,7 +8785,7 @@ dependencies = [
[[package]] [[package]]
name = "zlog" name = "zlog"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@@ -8789,7 +8802,7 @@ checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
[[package]] [[package]]
name = "ztracing" name = "ztracing"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@@ -8800,7 +8813,7 @@ dependencies = [
[[package]] [[package]]
name = "ztracing_macro" name = "ztracing_macro"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#555c002499a4676bac4c2e4aae2fa11f0a2bbddd" source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667"
[[package]] [[package]]
name = "zune-core" name = "zune-core"

View File

@@ -19,6 +19,7 @@ reqwest_client = { git = "https://github.com/zed-industries/zed" }
nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" } nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" } nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [ "nip96", "nip59", "nip49", "nip44" ] } nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [ "nip96", "nip59", "nip49", "nip44" ] }
nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
# Others # Others
anyhow = "1.0.44" anyhow = "1.0.44"

View File

@@ -243,12 +243,7 @@ impl AutoUpdater {
.author(app_pubkey) .author(app_pubkey)
.limit(1); .limit(1);
if let Err(e) = client // TODO
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
.await
{
log::error!("Failed to subscribe to updates: {e}");
};
}) })
} }
@@ -285,10 +280,7 @@ impl AutoUpdater {
.author(app_pubkey) .author(app_pubkey)
.ids(ids.clone()); .ids(ids.clone());
// Get all files for this release // TODO
client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
.await?;
Ok(ids) Ok(ids)
} else { } else {

View File

@@ -195,8 +195,8 @@ impl ChatRegistry {
let mut notifications = client.notifications(); let mut notifications = client.notifications();
let mut processed_events = HashSet::new(); let mut processed_events = HashSet::new();
while let Ok(notification) = notifications.recv().await { while let Some(notification) = notifications.next().await {
let RelayPoolNotification::Message { message, .. } = notification else { let ClientNotification::Message { message, .. } = notification else {
// Skip non-message notifications // Skip non-message notifications
continue; continue;
}; };
@@ -432,7 +432,7 @@ impl ChatRegistry {
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
cx.background_spawn(async move { cx.background_spawn(async move {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let contacts = client.database().contacts_public_keys(public_key).await?; let contacts = client.database().contacts_public_keys(public_key).await?;
@@ -597,8 +597,8 @@ impl ChatRegistry {
}; };
// Try with the user's signer // Try with the user's signer
let user_signer = client.signer().await?; let user_signer = client.signer().context("Signer not found")?;
let unwrapped = UnwrappedGift::from_gift_wrap(&user_signer, gift_wrap).await?; let unwrapped = UnwrappedGift::from_gift_wrap(user_signer, gift_wrap).await?;
Ok(unwrapped) Ok(unwrapped)
} }

View File

@@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::time::Duration; use std::time::Duration;
use anyhow::Error; use anyhow::{Context as AnyhowContext, Error};
use common::EventUtils; use common::EventUtils;
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task}; use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
use itertools::Itertools; use itertools::Itertools;
@@ -325,7 +325,7 @@ impl Room {
let id = SubscriptionId::new(format!("room-{}", self.id)); let id = SubscriptionId::new(format!("room-{}", self.id));
cx.background_spawn(async move { cx.background_spawn(async move {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
// Subscription options // Subscription options
@@ -343,7 +343,9 @@ impl Room {
// Subscribe to get member's gossip relays // Subscribe to get member's gossip relays
client client
.subscribe_with_id(id.clone(), filter, Some(opts)) .subscribe(filter)
.close_on(opts)
.with_id(id.clone())
.await?; .await?;
} }
@@ -458,7 +460,7 @@ impl Room {
let task = self.members_with_relays(cx); let task = self.members_with_relays(cx);
cx.background_spawn(async move { cx.background_spawn(async move {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let current_user_relays = current_user_relays.await; let current_user_relays = current_user_relays.await;
let mut members = task.await; let mut members = task.await;
@@ -484,10 +486,10 @@ impl Room {
// Construct the gift wrap event // Construct the gift wrap event
let event = let event =
EventBuilder::gift_wrap(&signer, &receiver, rumor.clone(), vec![]).await?; EventBuilder::gift_wrap(signer, &receiver, rumor.clone(), vec![]).await?;
// Send the gift wrap event to the messaging relays // Send the gift wrap event to the messaging relays
match client.send_event_to(relays, &event).await { match client.send_event(&event).to(&relays).await {
Ok(output) => { Ok(output) => {
let id = output.id().to_owned(); let id = output.id().to_owned();
let auth = output.failed.iter().any(|(_, s)| s.starts_with("auth-")); let auth = output.failed.iter().any(|(_, s)| s.starts_with("auth-"));
@@ -525,7 +527,7 @@ impl Room {
// Construct the gift-wrapped event // Construct the gift-wrapped event
let event = let event =
EventBuilder::gift_wrap(&signer, &current_user, rumor.clone(), vec![]).await?; EventBuilder::gift_wrap(signer, &current_user, rumor.clone(), vec![]).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 reports.iter().all(|r| r.is_sent_success()) { if reports.iter().all(|r| r.is_sent_success()) {
@@ -580,7 +582,7 @@ impl Room {
if let Some(event) = client.database().event_by_id(id).await? { if let Some(event) = client.database().event_by_id(id).await? {
for url in urls.into_iter() { for url in urls.into_iter() {
let relay = client.pool().relay(url).await?; let relay = client.relay(url).await?.context("Relay not found")?;
let id = relay.send_event(&event).await?; let id = relay.send_event(&event).await?;
let resent: Output<EventId> = Output { let resent: Output<EventId> = Output {

View File

@@ -72,11 +72,10 @@ pub async fn nip96_upload(
let json: Value = res.json().await?; let json: Value = res.json().await?;
let config = nip96::ServerConfig::from_json(json.to_string())?; let config = nip96::ServerConfig::from_json(json.to_string())?;
let signer = if client.has_signer().await { let signer = client
client.signer().await? .signer()
} else { .cloned()
Keys::generate().into_nostr_signer() .unwrap_or(Keys::generate().into_nostr_signer());
};
let url = upload(&signer, &config, file, None).await?; let url = upload(&signer, &config, file, None).await?;

View File

@@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use anyhow::Error; use anyhow::{Context as AnyhowContext, Error};
use common::{shorten_pubkey, RenderedProfile, RenderedTimestamp, BOOTSTRAP_RELAYS}; use common::{shorten_pubkey, RenderedProfile, RenderedTimestamp, BOOTSTRAP_RELAYS};
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
@@ -45,7 +46,7 @@ impl Screening {
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 = nostr.read(cx).client(); let client = nostr.read(cx).client();
async move { async move {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let signer_pubkey = signer.get_public_key().await?; let signer_pubkey = signer.get_public_key().await?;
// Check if user is in contact list // Check if user is in contact list
@@ -74,8 +75,15 @@ impl Screening {
let filter = Filter::new().author(public_key).limit(1); let filter = Filter::new().author(public_key).limit(1);
let mut activity: Option<Timestamp> = None; let mut activity: Option<Timestamp> = None;
// Construct target for subscription
let target = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
if let Ok(mut stream) = client if let Ok(mut stream) = client
.stream_events_from(BOOTSTRAP_RELAYS, filter, Duration::from_secs(2)) .stream_events(target)
.timeout(Duration::from_secs(2))
.await .await
{ {
while let Some((_url, event)) = stream.next().await { while let Some((_url, event)) = stream.next().await {
@@ -162,12 +170,12 @@ impl Screening {
let public_key = self.profile.public_key(); let public_key = self.profile.public_key();
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 tag = Tag::public_key_report(public_key, Report::Impersonation); let tag = Tag::public_key_report(public_key, Report::Impersonation);
let event = EventBuilder::report(vec![tag], "").sign(&signer).await?; let builder = EventBuilder::report(vec![tag], "");
let event = client.sign_event_builder(builder).await?;
// Send the report to the public relays // Send the report to the public relays
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?; client.send_event(&event).to(BOOTSTRAP_RELAYS).await?;
Ok(()) Ok(())
}); });

View File

@@ -1,7 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Context as AnyhowContext, Error};
use dock::panel::{Panel, PanelEvent}; use dock::panel::{Panel, PanelEvent};
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
@@ -89,7 +89,7 @@ impl MessagingRelayPanel {
} }
async fn load(client: &Client) -> Result<Vec<RelayUrl>, Error> { async fn load(client: &Client) -> Result<Vec<RelayUrl>, Error> {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let filter = Filter::new() let filter = Filter::new()
@@ -162,20 +162,17 @@ impl MessagingRelayPanel {
let task: Task<Result<(), Error>> = cx.background_spawn(async move { let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let urls = write_relays.await; let urls = write_relays.await;
let signer = client.signer().await?;
let tags: Vec<Tag> = relays let tags: Vec<Tag> = relays
.iter() .iter()
.map(|relay| Tag::relay(relay.clone())) .map(|relay| Tag::relay(relay.clone()))
.collect(); .collect();
let event = EventBuilder::new(Kind::InboxRelays, "") let builder = EventBuilder::new(Kind::InboxRelays, "").tags(tags);
.tags(tags) let event = client.sign_event_builder(builder).await?;
.sign(&signer)
.await?;
// Set messaging relays // Set messaging relays
client.send_event_to(urls, &event).await?; client.send_event(&event).to(urls).await?;
// Connect to messaging relays // Connect to messaging relays
for relay in relays.iter() { for relay in relays.iter() {

View File

@@ -1,7 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Context as AnyhowContext, Error};
use common::BOOTSTRAP_RELAYS; use common::BOOTSTRAP_RELAYS;
use dock::panel::{Panel, PanelEvent}; use dock::panel::{Panel, PanelEvent};
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
@@ -96,7 +96,7 @@ impl RelayListPanel {
} }
async fn load(client: &Client) -> Result<Vec<(RelayUrl, Option<RelayMetadata>)>, Error> { async fn load(client: &Client) -> Result<Vec<(RelayUrl, Option<RelayMetadata>)>, Error> {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let filter = Filter::new() let filter = Filter::new()
@@ -167,11 +167,11 @@ impl RelayListPanel {
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 builder = EventBuilder::relay_list(relays);
let event = EventBuilder::relay_list(relays).sign(&signer).await?; let event = client.sign_event_builder(builder).await?;
// Set relay list for current user // Set relay list for current user
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?; client.send_event(&event).to(BOOTSTRAP_RELAYS).await?;
Ok(()) Ok(())
}); });

View File

@@ -1,4 +1,4 @@
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@@ -130,8 +130,8 @@ impl DeviceRegistry {
let mut notifications = client.notifications(); let mut notifications = client.notifications();
let mut processed_events = HashSet::new(); let mut processed_events = HashSet::new();
while let Ok(notification) = notifications.recv().await { while let Some(notification) = notifications.next().await {
if let RelayPoolNotification::Message { if let ClientNotification::Message {
message: RelayMessage::Event { event, .. }, message: RelayMessage::Event { event, .. },
.. ..
} = notification } = notification
@@ -162,7 +162,7 @@ impl DeviceRegistry {
/// Verify the author of an event /// Verify the author of an event
async fn verify_author(client: &Client, event: &Event) -> bool { async fn verify_author(client: &Client, event: &Event) -> bool {
if let Ok(signer) = client.signer().await { if let Some(signer) = client.signer() {
if let Ok(public_key) = signer.get_public_key().await { if let Ok(public_key) = signer.get_public_key().await {
return public_key == event.pubkey; return public_key == event.pubkey;
} }
@@ -172,7 +172,7 @@ impl DeviceRegistry {
/// Encrypt and store device keys in the local database. /// Encrypt and store device keys in the local database.
async fn set_keys(client: &Client, secret: &str) -> Result<(), Error> { async fn set_keys(client: &Client, secret: &str) -> Result<(), Error> {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
// Encrypt the value // Encrypt the value
@@ -193,7 +193,7 @@ impl DeviceRegistry {
/// Get device keys from the local database. /// Get device keys from the local database.
async fn get_keys(client: &Client) -> Result<Keys, Error> { async fn get_keys(client: &Client) -> Result<Keys, Error> {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let filter = Filter::new() let filter = Filter::new()
@@ -278,10 +278,13 @@ impl DeviceRegistry {
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(pkey); let filter = Filter::new().kind(Kind::GiftWrap).pubkey(pkey);
let id = SubscriptionId::new(DEVICE_GIFTWRAP); let id = SubscriptionId::new(DEVICE_GIFTWRAP);
if let Err(e) = client // Construct target for subscription
.subscribe_with_id_to(&urls, id, vec![filter], None) let target = urls
.await .iter()
{ .map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
if let Err(e) = client.subscribe(target).with_id(id).await {
log::error!("Failed to subscribe to gift wrap events: {e}"); log::error!("Failed to subscribe to gift wrap events: {e}");
} }
} }
@@ -291,10 +294,13 @@ impl DeviceRegistry {
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
let id = SubscriptionId::new(USER_GIFTWRAP); let id = SubscriptionId::new(USER_GIFTWRAP);
if let Err(e) = client // Construct target for subscription
.subscribe_with_id_to(urls, id, vec![filter], None) let target = urls
.await .iter()
{ .map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
if let Err(e) = client.subscribe(target).with_id(id).await {
log::error!("Failed to subscribe to gift wrap events: {e}"); log::error!("Failed to subscribe to gift wrap events: {e}");
} }
}) })
@@ -318,8 +324,15 @@ impl DeviceRegistry {
.author(public_key) .author(public_key)
.limit(1); .limit(1);
// Construct target for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
let mut stream = client let mut stream = client
.stream_events_from(&urls, vec![filter], Duration::from_secs(TIMEOUT)) .stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?; .await?;
while let Some((_url, res)) = stream.next().await { while let Some((_url, res)) = stream.next().await {
@@ -368,20 +381,17 @@ impl DeviceRegistry {
let n = keys.public_key(); let n = keys.public_key();
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 urls = write_relays.await; let urls = write_relays.await;
// Construct an announcement event // Construct an announcement event
let event = EventBuilder::new(Kind::Custom(10044), "") let builder = EventBuilder::new(Kind::Custom(10044), "").tags(vec![
.tags(vec![
Tag::custom(TagKind::custom("n"), vec![n]), Tag::custom(TagKind::custom("n"), vec![n]),
Tag::client(app_name()), Tag::client(app_name()),
]) ]);
.sign(&signer) let event = client.sign_event_builder(builder).await?;
.await?;
// Publish announcement // Publish announcement
client.send_event_to(&urls, &event).await?; client.send_event(&event).to(urls).await?;
// Save device keys to the database // Save device keys to the database
Self::set_keys(&client, &secret).await?; Self::set_keys(&client, &secret).await?;
@@ -461,8 +471,14 @@ impl DeviceRegistry {
.author(public_key) .author(public_key)
.since(Timestamp::now()); .since(Timestamp::now());
// Construct target for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Subscribe to the device key requests on user's write relays // Subscribe to the device key requests on user's write relays
client.subscribe_to(&urls, vec![filter], None).await?; client.subscribe(target).await?;
Ok(()) Ok(())
}); });
@@ -487,8 +503,14 @@ impl DeviceRegistry {
.author(public_key) .author(public_key)
.since(Timestamp::now()); .since(Timestamp::now());
// Construct target for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Subscribe to the device key requests on user's write relays // Subscribe to the device key requests on user's write relays
client.subscribe_to(&urls, vec![filter], None).await?; client.subscribe(target).await?;
Ok(()) Ok(())
}); });
@@ -508,7 +530,7 @@ impl DeviceRegistry {
let app_pubkey = app_keys.public_key(); let app_pubkey = app_keys.public_key();
let task: Task<Result<Option<Keys>, Error>> = cx.background_spawn(async move { let task: Task<Result<Option<Keys>, Error>> = cx.background_spawn(async move {
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
let filter = Filter::new() let filter = Filter::new()
@@ -538,16 +560,14 @@ impl DeviceRegistry {
let urls = write_relays.await; let urls = write_relays.await;
// Construct an event for device key request // Construct an event for device key request
let event = EventBuilder::new(Kind::Custom(4454), "") let builder = EventBuilder::new(Kind::Custom(4454), "").tags(vec![
.tags(vec![
Tag::client(app_name()), Tag::client(app_name()),
Tag::custom(TagKind::custom("P"), vec![app_pubkey]), Tag::custom(TagKind::custom("P"), vec![app_pubkey]),
]) ]);
.sign(&signer) let event = client.sign_event_builder(builder).await?;
.await?;
// Send the event to write relays // Send the event to write relays
client.send_event_to(&urls, &event).await?; client.send_event(&event).to(urls).await?;
Ok(None) Ok(None)
} }
@@ -625,7 +645,7 @@ impl DeviceRegistry {
let task: Task<Result<(), Error>> = cx.background_spawn(async move { let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let urls = write_relays.await; let urls = write_relays.await;
let signer = client.signer().await?; let signer = client.signer().context("Signer not found")?;
// Get device keys // Get device keys
let keys = Self::get_keys(&client).await?; let keys = Self::get_keys(&client).await?;
@@ -646,16 +666,14 @@ impl DeviceRegistry {
// //
// P tag: the current device's public key // P tag: the current device's public key
// p tag: the requester's public key // p tag: the requester's public key
let event = EventBuilder::new(Kind::Custom(4455), payload) let builder = EventBuilder::new(Kind::Custom(4455), payload).tags(vec![
.tags(vec![
Tag::custom(TagKind::custom("P"), vec![keys.public_key()]), Tag::custom(TagKind::custom("P"), vec![keys.public_key()]),
Tag::public_key(target), Tag::public_key(target),
]) ]);
.sign(&signer) let event = client.sign_event_builder(builder).await?;
.await?;
// 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(&event).to(urls).await?;
Ok(()) Ok(())
}); });

View File

@@ -139,20 +139,14 @@ impl PersonRegistry {
/// Handle nostr notifications /// Handle nostr notifications
async fn handle_notifications(client: &Client, tx: &flume::Sender<Dispatch>) { async fn handle_notifications(client: &Client, tx: &flume::Sender<Dispatch>) {
let mut notifications = client.notifications(); let mut notifications = client.notifications();
let mut processed_events = HashSet::new();
while let Ok(notification) = notifications.recv().await { while let Some(notification) = notifications.next().await {
let RelayPoolNotification::Message { message, .. } = notification else { let ClientNotification::Message { message, .. } = notification else {
// Skip if the notification is not a message // Skip if the notification is not a message
continue; continue;
}; };
if let RelayMessage::Event { event, .. } = message { if let RelayMessage::Event { event, .. } = message {
if !processed_events.insert(event.id) {
// Skip if the event has already been processed
continue;
}
match event.kind { match event.kind {
Kind::Metadata => { Kind::Metadata => {
let metadata = Metadata::from_json(&event.content).unwrap_or_default(); let metadata = Metadata::from_json(&event.content).unwrap_or_default();
@@ -230,9 +224,13 @@ impl PersonRegistry {
.authors(authors) .authors(authors)
.limit(limit); .limit(limit);
client // Construct target for subscription
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) let target = BOOTSTRAP_RELAYS
.await?; .into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).close_on(opts).await?;
Ok(()) Ok(())
} }

View File

@@ -4,7 +4,7 @@ use std::collections::HashSet;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::rc::Rc; use std::rc::Rc;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Context as AnyhowContext, Error};
use gpui::{ use gpui::{
App, AppContext, Context, Entity, Global, IntoElement, ParentElement, SharedString, Styled, App, AppContext, Context, Entity, Global, IntoElement, ParentElement, SharedString, Styled,
Subscription, Task, Window, Subscription, Task, Window,
@@ -137,8 +137,8 @@ impl RelayAuth {
async fn handle_notifications(client: &Client, tx: &flume::Sender<AuthRequest>) { async fn handle_notifications(client: &Client, tx: &flume::Sender<AuthRequest>) {
let mut notifications = client.notifications(); let mut notifications = client.notifications();
while let Ok(notification) = notifications.recv().await { while let Some(notification) = notifications.next().await {
if let RelayPoolNotification::Message { if let ClientNotification::Message {
message: RelayMessage::Auth { challenge }, message: RelayMessage::Auth { challenge },
relay_url, relay_url,
} = notification } = notification
@@ -184,34 +184,33 @@ impl RelayAuth {
let url_clone = url.clone(); let url_clone = url.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?;
// Construct event // Construct event
let event: Event = EventBuilder::auth(challenge_clone, url_clone.clone()) let builder = EventBuilder::auth(challenge_clone, url_clone.clone());
.sign(&signer) let event = client.sign_event_builder(builder).await?;
.await?;
// Get the event ID // Get the event ID
let id = event.id; let id = event.id;
// Get the relay // Get the relay
let relay = client.pool().relay(url_clone).await?; let relay = client.relay(url_clone).await?.context("Relay not found")?;
let relay_url = relay.url(); let relay_url = relay.url();
// Subscribe to notifications // Subscribe to notifications
let mut notifications = relay.notifications(); let mut notifications = relay.notifications();
// Send the AUTH message // Send the AUTH message
relay.send_msg(ClientMessage::Auth(Cow::Borrowed(&event)))?; relay
.send_msg(ClientMessage::Auth(Cow::Borrowed(&event)))
.await?;
while let Ok(notification) = notifications.recv().await { while let Some(notification) = notifications.next().await {
match notification { match notification {
RelayNotification::Message { RelayNotification::Message {
message: RelayMessage::Ok { event_id, .. }, message: RelayMessage::Ok { event_id, .. },
} => { } => {
if id == event_id { if id == event_id {
// Re-subscribe to previous subscription // Re-subscribe to previous subscription
relay.resubscribe().await?; // relay.resubscribe().await?;
// Get all pending events that need to be resent // Get all pending events that need to be resent
let mut tracker = tracker().write().await; let mut tracker = tracker().write().await;
@@ -228,7 +227,6 @@ impl RelayAuth {
} }
} }
RelayNotification::AuthenticationFailed => break, RelayNotification::AuthenticationFailed => break,
RelayNotification::Shutdown => break,
_ => {} _ => {}
} }
} }

View File

@@ -136,7 +136,7 @@ impl AppSettings {
} }
fn new(cx: &mut Context<Self>) -> Self { fn new(cx: &mut Context<Self>) -> Self {
let load_settings = Self::get_from_database(false, cx); let load_settings = Self::get_from_database(cx);
let mut tasks = smallvec![]; let mut tasks = smallvec![];
let mut subscriptions = smallvec![]; let mut subscriptions = smallvec![];
@@ -169,12 +169,10 @@ impl AppSettings {
} }
/// Get settings from the database /// Get settings from the database
/// fn get_from_database(cx: &App) -> Task<Result<Settings, Error>> {
/// If `current_user` is true, the settings will be retrieved for current user.
/// Otherwise, Coop will load the latest settings from the database.
fn get_from_database(current_user: bool, cx: &App) -> Task<Result<Settings, Error>> {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let current_user = nostr.read(cx).identity().read(cx).public_key;
cx.background_spawn(async move { cx.background_spawn(async move {
// Construct a filter to get the latest settings // Construct a filter to get the latest settings
@@ -183,10 +181,7 @@ impl AppSettings {
.identifier(SETTINGS_IDENTIFIER) .identifier(SETTINGS_IDENTIFIER)
.limit(1); .limit(1);
if current_user { if let Some(public_key) = current_user {
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?;
// Push author to the filter // Push author to the filter
filter = filter.author(public_key); filter = filter.author(public_key);
} }
@@ -201,7 +196,7 @@ impl AppSettings {
/// Load settings /// Load settings
pub fn load(&mut self, cx: &mut Context<Self>) { pub fn load(&mut self, cx: &mut Context<Self>) {
let task = Self::get_from_database(true, cx); let task = Self::get_from_database(cx);
self._tasks.push( self._tasks.push(
// Run task in the background // Run task in the background
@@ -221,18 +216,17 @@ impl AppSettings {
pub fn save(&mut self, cx: &mut Context<Self>) { pub fn save(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx); let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client(); let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
if let Ok(content) = serde_json::to_string(&self.values) { if let Ok(content) = serde_json::to_string(&self.values) {
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 public_key = signer.get_public_key().await?;
let event = EventBuilder::new(Kind::ApplicationSpecificData, content) let event = EventBuilder::new(Kind::ApplicationSpecificData, content)
.tag(Tag::identifier(SETTINGS_IDENTIFIER)) .tag(Tag::identifier(SETTINGS_IDENTIFIER))
.build(public_key) .build(public_key)
.sign(&Keys::generate()) .sign(&Keys::generate())
.await?; .await?;
// Save event to the local database without sending to relays
client.database().save_event(&event).await?; client.database().save_event(&event).await?;
Ok(()) Ok(())

View File

@@ -10,6 +10,7 @@ common = { path = "../common" }
nostr-sdk.workspace = true nostr-sdk.workspace = true
nostr-lmdb.workspace = true nostr-lmdb.workspace = true
nostr-connect.workspace = true nostr-connect.workspace = true
nostr-gossip-memory.workspace = true
gpui.workspace = true gpui.workspace = true
gpui_tokio.workspace = true gpui_tokio.workspace = true

View File

@@ -15,9 +15,9 @@ impl RelayState {
} }
/// Identity /// Identity
#[derive(Debug, Clone, Default)] #[derive(Debug, Default)]
pub struct Identity { pub struct Identity {
/// The public key of the account /// Signer's public key
pub public_key: Option<PublicKey>, pub public_key: Option<PublicKey>,
/// Whether the identity is owned by the user /// Whether the identity is owned by the user

View File

@@ -1,5 +1,6 @@
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
@@ -14,12 +15,14 @@ mod event;
mod gossip; mod gossip;
mod identity; mod identity;
mod nip05; mod nip05;
mod signer;
pub use device::*; pub use device::*;
pub use event::*; pub use event::*;
pub use gossip::*; pub use gossip::*;
pub use identity::*; pub use identity::*;
pub use nip05::*; pub use nip05::*;
pub use signer::*;
use crate::identity::Identity; use crate::identity::Identity;
@@ -68,6 +71,9 @@ pub struct NostrRegistry {
/// Nostr client /// Nostr client
client: Client, client: Client,
/// Nostr signer
signer: Arc<CoopSigner>,
/// App keys /// App keys
/// ///
/// Used for Nostr Connect and NIP-4e operations /// Used for Nostr Connect and NIP-4e operations
@@ -108,14 +114,6 @@ impl NostrRegistry {
.install_default() .install_default()
.ok(); .ok();
// Construct the nostr client options
let opts = ClientOptions::new()
.automatic_authentication(false)
.verify_subscriptions(false)
.sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(600),
});
// Construct the lmdb // Construct the lmdb
let lmdb = cx.foreground_executor().block_on(async move { let lmdb = cx.foreground_executor().block_on(async move {
NostrLmdb::open(config_dir().join("nostr")) NostrLmdb::open(config_dir().join("nostr"))
@@ -123,9 +121,23 @@ impl NostrRegistry {
.expect("Failed to initialize database") .expect("Failed to initialize database")
}); });
// Construct the nostr signer
let keys = Keys::generate();
let signer = Arc::new(CoopSigner::new(keys));
// Construct the nostr client // Construct the nostr client
let client = ClientBuilder::default().database(lmdb).opts(opts).build(); let client = ClientBuilder::default()
let _ = tracker(); .signer(signer.clone())
.database(lmdb)
.automatic_authentication(false)
.verify_subscriptions(false)
.sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(600),
})
.build();
// Construct the event tracker
let _tracker = tracker();
// Get the app keys // Get the app keys
let app_keys = Self::create_or_init_app_keys().unwrap(); let app_keys = Self::create_or_init_app_keys().unwrap();
@@ -135,7 +147,7 @@ impl NostrRegistry {
let async_gossip = gossip.downgrade(); let async_gossip = gossip.downgrade();
// Construct the identity entity // Construct the identity entity
let identity = cx.new(|_| Identity::default()); let identity = cx.new(|_| Identity::new());
// Channel for communication between nostr and gpui // Channel for communication between nostr and gpui
let (tx, rx) = flume::bounded::<Event>(2048); let (tx, rx) = flume::bounded::<Event>(2048);
@@ -207,6 +219,7 @@ impl NostrRegistry {
Self { Self {
client, client,
signer,
app_keys, app_keys,
identity, identity,
gossip, gossip,
@@ -239,8 +252,8 @@ impl NostrRegistry {
let mut notifications = client.notifications(); let mut notifications = client.notifications();
let mut processed_events = HashSet::new(); let mut processed_events = HashSet::new();
while let Ok(notification) = notifications.recv().await { while let Some(notification) = notifications.next().await {
if let RelayPoolNotification::Message { message, relay_url } = notification { if let ClientNotification::Message { message, relay_url } = notification {
match message { match message {
RelayMessage::Event { RelayMessage::Event {
event, event,
@@ -325,9 +338,13 @@ impl NostrRegistry {
.author(event.pubkey) .author(event.pubkey)
.limit(1); .limit(1);
client // Construct target for subscription
.subscribe_to(write_relays, vec![inbox, announcement], Some(opts)) let target = write_relays
.await?; .into_iter()
.map(|relay| (relay, vec![inbox.clone(), announcement.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).close_on(opts).await?;
Ok(()) Ok(())
} }
@@ -433,21 +450,21 @@ impl NostrRegistry {
} }
/// Set the signer for the nostr client and verify the public key /// Set the signer for the nostr client and verify the public key
pub fn set_signer<T>(&mut self, signer: T, owned: bool, cx: &mut Context<Self>) pub fn set_signer<T>(&mut self, new: T, owned: bool, cx: &mut Context<Self>)
where where
T: NostrSigner + 'static, T: NostrSigner + 'static,
{ {
let client = self.client();
let identity = self.identity.downgrade(); let identity = self.identity.downgrade();
let signer = self.signer.clone();
// Create a task to update the signer and verify the public key // Create a task to update the signer and verify the public key
let task: Task<Result<PublicKey, Error>> = cx.background_spawn(async move { let task: Task<Result<PublicKey, Error>> = cx.background_spawn(async move {
// Update signer // Update signer
client.set_signer(signer).await; signer.switch(new).await;
// Verify signer // Verify signer
let signer = client.signer().await?;
let public_key = signer.get_public_key().await?; let public_key = signer.get_public_key().await?;
log::info!("test: {public_key:?}");
Ok(public_key) Ok(public_key)
}); });
@@ -471,31 +488,6 @@ impl NostrRegistry {
})); }));
} }
/// Unset the current signer
pub fn unset_signer(&mut self, cx: &mut Context<Self>) {
let client = self.client();
let async_identity = self.identity.downgrade();
self.tasks.push(cx.spawn(async move |_this, cx| {
// Unset the signer from nostr client
cx.background_executor()
.await_on_background(async move {
client.unset_signer().await;
})
.await;
// Unset the current identity
async_identity
.update(cx, |this, cx| {
this.unset_public_key();
cx.notify();
})
.ok();
Ok(())
}));
}
// Get relay list for current user // Get relay list for current user
fn get_relay_list(&mut self, cx: &mut Context<Self>) { fn get_relay_list(&mut self, cx: &mut Context<Self>) {
let client = self.client(); let client = self.client();
@@ -508,8 +500,15 @@ impl NostrRegistry {
.author(public_key) .author(public_key)
.limit(1); .limit(1);
// Construct targets for subscription
let target = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
let mut stream = client let mut stream = client
.stream_events_from(BOOTSTRAP_RELAYS, vec![filter], Duration::from_secs(TIMEOUT)) .stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?; .await?;
while let Some((_url, res)) = stream.next().await { while let Some((_url, res)) = stream.next().await {
@@ -523,10 +522,14 @@ impl NostrRegistry {
.author(public_key) .author(public_key)
.since(Timestamp::now()); .since(Timestamp::now());
// Construct targets for subscription
let target = BOOTSTRAP_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Subscribe to the relay list events // Subscribe to the relay list events
client client.subscribe(target).await?;
.subscribe_to(BOOTSTRAP_RELAYS, vec![filter], None)
.await?;
return Ok(RelayState::Set); return Ok(RelayState::Set);
} }
@@ -565,8 +568,7 @@ impl NostrRegistry {
let write_relays = self.write_relays(&public_key, cx); let write_relays = self.write_relays(&public_key, cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move { let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let mut urls = vec![]; let mut urls = write_relays.await;
urls.extend(write_relays.await);
urls.extend( urls.extend(
BOOTSTRAP_RELAYS BOOTSTRAP_RELAYS
.iter() .iter()
@@ -590,9 +592,13 @@ impl NostrRegistry {
.limit(1) .limit(1)
.author(public_key); .author(public_key);
client // Construct targets for subscription
.subscribe_to(urls, vec![metadata, contact_list], Some(opts)) let target = urls
.await?; .into_iter()
.map(|relay| (relay, vec![metadata.clone(), contact_list.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).close_on(opts).await?;
Ok(()) Ok(())
}); });
@@ -616,9 +622,16 @@ impl NostrRegistry {
.author(public_key) .author(public_key)
.limit(1); .limit(1);
// Construct targets for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Stream events from the write relays // Stream events from the write relays
let mut stream = client let mut stream = client
.stream_events_from(&urls, vec![filter], Duration::from_secs(TIMEOUT)) .stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?; .await?;
while let Some((_url, res)) = stream.next().await { while let Some((_url, res)) = stream.next().await {
@@ -632,8 +645,14 @@ impl NostrRegistry {
.author(public_key) .author(public_key)
.since(Timestamp::now()); .since(Timestamp::now());
// Construct targets for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Subscribe to the relay list events // Subscribe to the relay list events
client.subscribe_to(&urls, vec![filter], None).await?; client.subscribe(target).await?;
return Ok(RelayState::Set); return Ok(RelayState::Set);
} }
@@ -687,13 +706,13 @@ impl NostrRegistry {
cx.background_spawn(async move { cx.background_spawn(async move {
let urls = write_relays.await; let urls = write_relays.await;
let signer = client.signer().await?;
// Sign the new metadata event // Build and sign the metadata event
let event = EventBuilder::metadata(&metadata).sign(&signer).await?; let builder = EventBuilder::metadata(&metadata);
let event = client.sign_event_builder(builder).await?;
// Send event to user's write relayss // Send event to user's write relayss
client.send_event_to(urls, &event).await?; client.send_event(&event).to(urls).await?;
Ok(()) Ok(())
}) })
@@ -728,9 +747,6 @@ impl NostrRegistry {
/// Create a new identity /// Create a new identity
fn create_identity(&mut self, cx: &mut Context<Self>) { fn create_identity(&mut self, cx: &mut Context<Self>) {
let client = self.client();
// Generate new keys
let keys = Keys::generate(); let keys = Keys::generate();
// Get write credential task // Get write credential task
@@ -743,8 +759,7 @@ impl NostrRegistry {
// Update the signer // Update the signer
self.set_signer(keys, false, cx); self.set_signer(keys, false, cx);
// Spawn a task to set metadata and write the credentials // Generate a unique name and avatar for the identity
cx.background_spawn(async move {
let name = petname::petname(2, "-").unwrap_or("Cooper".to_string()); let name = petname::petname(2, "-").unwrap_or("Cooper".to_string());
let avatar = Url::parse(DEFAULT_AVATAR).unwrap(); let avatar = Url::parse(DEFAULT_AVATAR).unwrap();
@@ -754,12 +769,15 @@ impl NostrRegistry {
.name(&name) .name(&name)
.picture(avatar); .picture(avatar);
// Set metadata for the identity // Update user's metadata
if let Err(e) = client.set_metadata(&metadata).await { let task = self.set_metadata(&metadata, cx);
log::error!("Failed to set metadata: {}", e);
// Spawn a task to write the credentials
cx.background_spawn(async move {
if let Err(e) = task.await {
log::error!("Failed to update metadata: {}", e);
} }
// Write the credentials
if let Err(e) = write_credential.await { if let Err(e) = write_credential.await {
log::error!("Failed to write credentials: {}", e); log::error!("Failed to write credentials: {}", e);
} }
@@ -874,10 +892,13 @@ impl NostrRegistry {
.author(public_key) .author(public_key)
.limit(1); .limit(1);
// Subscribe to bootstrap relays // Construct target for subscription
client let target = BOOTSTRAP_RELAYS
.subscribe_to(BOOTSTRAP_RELAYS, vec![filter], Some(opts)) .into_iter()
.await?; .map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).close_on(opts).await?;
Ok(public_key) Ok(public_key)
}) })
@@ -897,9 +918,16 @@ impl NostrRegistry {
.kind(Kind::Metadata) .kind(Kind::Metadata)
.limit(FIND_LIMIT); .limit(FIND_LIMIT);
// Construct target for subscription
let target = SEARCH_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Stream events from the search relays // Stream events from the search relays
let mut stream = client let mut stream = client
.stream_events_from(SEARCH_RELAYS, vec![filter], Duration::from_secs(3)) .stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?; .await?;
// Collect the results // Collect the results
@@ -923,28 +951,31 @@ impl NostrRegistry {
let query = query.to_string(); let query = query.to_string();
cx.background_spawn(async move { cx.background_spawn(async move {
let signer = client.signer().await?;
// Construct a vertex request event // Construct a vertex request event
let event = EventBuilder::new(Kind::Custom(5315), "") let builder = EventBuilder::new(Kind::Custom(5315), "").tags(vec![
.tags(vec![
Tag::custom(TagKind::custom("param"), vec!["search", &query]), Tag::custom(TagKind::custom("param"), vec!["search", &query]),
Tag::custom(TagKind::custom("param"), vec!["limit", "10"]), Tag::custom(TagKind::custom("param"), vec!["limit", "10"]),
]) ]);
.sign(&signer) let event = client.sign_event_builder(builder).await?;
.await?;
// Send the event to vertex relays // Send the event to vertex relays
let output = client.send_event_to(WOT_RELAYS, &event).await?; let output = client.send_event(&event).to(WOT_RELAYS).await?;
// Construct a filter to get the response or error from vertex // Construct a filter to get the response or error from vertex
let filter = Filter::new() let filter = Filter::new()
.kinds(vec![Kind::Custom(6315), Kind::Custom(7000)]) .kinds(vec![Kind::Custom(6315), Kind::Custom(7000)])
.event(output.id().to_owned()); .event(output.id().to_owned());
// Stream events from the search relays // Construct target for subscription
let target = WOT_RELAYS
.into_iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
// Stream events from the wot relays
let mut stream = client let mut stream = client
.stream_events_from(WOT_RELAYS, vec![filter], Duration::from_secs(3)) .stream_events(target)
.timeout(Duration::from_secs(TIMEOUT))
.await?; .await?;
while let Some((_url, res)) = stream.next().await { while let Some((_url, res)) = stream.next().await {

View File

@@ -0,0 +1,88 @@
use std::borrow::Cow;
use std::sync::Arc;
use nostr_sdk::prelude::*;
use smol::lock::RwLock;
#[derive(Debug)]
pub struct CoopSigner {
signer: RwLock<Arc<dyn NostrSigner>>,
}
impl CoopSigner {
pub fn new<T>(signer: T) -> Self
where
T: IntoNostrSigner,
{
Self {
signer: RwLock::new(signer.into_nostr_signer()),
}
}
async fn get(&self) -> Arc<dyn NostrSigner> {
self.signer.read().await.clone()
}
pub async fn switch<T>(&self, new: T)
where
T: IntoNostrSigner,
{
let mut signer = self.signer.write().await;
*signer = new.into_nostr_signer();
}
}
impl NostrSigner for CoopSigner {
fn backend(&self) -> SignerBackend {
SignerBackend::Custom(Cow::Borrowed("custom"))
}
fn get_public_key(&self) -> BoxedFuture<Result<PublicKey, SignerError>> {
Box::pin(async move { Ok(self.get().await.get_public_key().await?) })
}
fn sign_event(
&self,
unsigned: UnsignedEvent,
) -> BoxedFuture<std::result::Result<Event, SignerError>> {
Box::pin(async move { Ok(self.get().await.sign_event(unsigned).await?) })
}
fn nip04_encrypt<'a>(
&'a self,
public_key: &'a PublicKey,
content: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move { Ok(self.get().await.nip04_encrypt(public_key, content).await?) })
}
fn nip04_decrypt<'a>(
&'a self,
public_key: &'a PublicKey,
encrypted_content: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move {
Ok(self
.get()
.await
.nip04_decrypt(public_key, encrypted_content)
.await?)
})
}
fn nip44_encrypt<'a>(
&'a self,
public_key: &'a PublicKey,
content: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move { Ok(self.get().await.nip44_encrypt(public_key, content).await?) })
}
fn nip44_decrypt<'a>(
&'a self,
public_key: &'a PublicKey,
payload: &'a str,
) -> BoxedFuture<'a, std::result::Result<String, SignerError>> {
Box::pin(async move { Ok(self.get().await.nip44_decrypt(public_key, payload).await?) })
}
}

View File

@@ -246,8 +246,7 @@ impl Element for ContextMenu {
let menu = PopupMenu::build(window, cx, |menu, window, cx| { let menu = PopupMenu::build(window, cx, |menu, window, cx| {
(builder)(menu, window, cx) (builder)(menu, window, cx)
}) });
.into_element();
let _subscription = window.subscribe(&menu, cx, { let _subscription = window.subscribe(&menu, cx, {
let shared_state = shared_state.clone(); let shared_state = shared_state.clone();