chore: improve gossip implementation (#184)
* add send event function * add set nip17 and set nip65 functions * setup gossip relays * .
This commit is contained in:
@@ -19,17 +19,6 @@ pub const BOOTSTRAP_RELAYS: [&str; 5] = [
|
||||
/// Search Relays.
|
||||
pub const SEARCH_RELAYS: [&str; 1] = ["wss://relay.nostr.band"];
|
||||
|
||||
/// NIP65 Relays. Used for new account
|
||||
pub const NIP65_RELAYS: [&str; 4] = [
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.primal.net",
|
||||
"wss://relay.nostr.net",
|
||||
"wss://nos.lol",
|
||||
];
|
||||
|
||||
/// Messaging Relays. Used for new account
|
||||
pub const NIP17_RELAYS: [&str; 2] = ["wss://nip17.com", "wss://auth.nostr1.com"];
|
||||
|
||||
/// Default relay for Nostr Connect
|
||||
pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ pub mod state;
|
||||
|
||||
static APP_STATE: OnceLock<AppState> = OnceLock::new();
|
||||
static NOSTR_CLIENT: OnceLock<Client> = OnceLock::new();
|
||||
static NIP65_RELAYS: OnceLock<Vec<(RelayUrl, Option<RelayMetadata>)>> = OnceLock::new();
|
||||
static NIP17_RELAYS: OnceLock<Vec<RelayUrl>> = OnceLock::new();
|
||||
|
||||
/// Initialize the application state.
|
||||
pub fn app_state() -> &'static AppState {
|
||||
@@ -42,3 +44,39 @@ pub fn nostr_client() -> &'static Client {
|
||||
ClientBuilder::default().database(lmdb).opts(opts).build()
|
||||
})
|
||||
}
|
||||
|
||||
/// Default NIP65 Relays. Used for new account
|
||||
pub fn default_nip65_relays() -> &'static Vec<(RelayUrl, Option<RelayMetadata>)> {
|
||||
NIP65_RELAYS.get_or_init(|| {
|
||||
vec![
|
||||
(
|
||||
RelayUrl::parse("wss://nostr.mom").unwrap(),
|
||||
Some(RelayMetadata::Read),
|
||||
),
|
||||
(
|
||||
RelayUrl::parse("wss://nostr.bitcoiner.social").unwrap(),
|
||||
Some(RelayMetadata::Read),
|
||||
),
|
||||
(
|
||||
RelayUrl::parse("wss://nostr.oxtr.dev").unwrap(),
|
||||
Some(RelayMetadata::Write),
|
||||
),
|
||||
(
|
||||
RelayUrl::parse("wss://nostr.fmt.wiz.biz").unwrap(),
|
||||
Some(RelayMetadata::Write),
|
||||
),
|
||||
(RelayUrl::parse("wss://relay.primal.net").unwrap(), None),
|
||||
(RelayUrl::parse("wss://relay.damus.io").unwrap(), None),
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
/// Default NIP17 Relays. Used for new account
|
||||
pub fn default_nip17_relays() -> &'static Vec<RelayUrl> {
|
||||
NIP17_RELAYS.get_or_init(|| {
|
||||
vec![
|
||||
RelayUrl::parse("wss://nip17.com").unwrap(),
|
||||
RelayUrl::parse("wss://auth.nostr1.com").unwrap(),
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,10 +15,12 @@ pub struct Gossip {
|
||||
}
|
||||
|
||||
impl Gossip {
|
||||
/// Parse and insert NIP-65 or NIP-17 relays into the gossip state.
|
||||
pub fn insert(&mut self, event: &Event) {
|
||||
match event.kind {
|
||||
Kind::InboxRelays => {
|
||||
let urls: Vec<RelayUrl> = nip17::extract_relay_list(event).cloned().collect();
|
||||
let urls: Vec<RelayUrl> =
|
||||
nip17::extract_relay_list(event).take(3).cloned().collect();
|
||||
|
||||
if !urls.is_empty() {
|
||||
self.nip17.entry(event.pubkey).or_default().extend(urls);
|
||||
@@ -37,6 +39,7 @@ impl Gossip {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all write relays for a given public key
|
||||
pub fn write_relays(&self, public_key: &PublicKey) -> Vec<&RelayUrl> {
|
||||
self.nip65
|
||||
.get(public_key)
|
||||
@@ -51,6 +54,7 @@ impl Gossip {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get all read relays for a given public key
|
||||
pub fn read_relays(&self, public_key: &PublicKey) -> Vec<&RelayUrl> {
|
||||
self.nip65
|
||||
.get(public_key)
|
||||
@@ -65,6 +69,7 @@ impl Gossip {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get all messaging relays for a given public key
|
||||
pub fn messaging_relays(&self, public_key: &PublicKey) -> Vec<&RelayUrl> {
|
||||
self.nip17
|
||||
.get(public_key)
|
||||
@@ -72,17 +77,30 @@ impl Gossip {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn get_nip65(&mut self, public_key: PublicKey) -> Result<(), Error> {
|
||||
/// Get and verify NIP-65 relays for a given public key
|
||||
///
|
||||
/// Only fetch from the public relays
|
||||
pub async fn get_nip65(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let filter = Filter::new()
|
||||
let latest_filter = Filter::new()
|
||||
.kind(Kind::RelayList)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to events from the bootstrapping relays
|
||||
client
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, latest_filter.clone(), Some(opts))
|
||||
.await?;
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::RelayList)
|
||||
.author(public_key)
|
||||
.since(Timestamp::now());
|
||||
|
||||
// Continuously subscribe for new events from the bootstrap relays
|
||||
client
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter.clone(), Some(opts))
|
||||
.await?;
|
||||
@@ -91,7 +109,7 @@ impl Gossip {
|
||||
smol::spawn(async move {
|
||||
smol::Timer::after(timeout).await;
|
||||
|
||||
if client.database().count(filter).await.unwrap_or(0) < 1 {
|
||||
if client.database().count(latest_filter).await.unwrap_or(0) < 1 {
|
||||
app_state()
|
||||
.signal
|
||||
.send(SignalKind::GossipRelaysNotFound)
|
||||
@@ -103,16 +121,49 @@ impl Gossip {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_nip17(&mut self, public_key: PublicKey) -> Result<(), Error> {
|
||||
/// Set NIP-65 relays for a current user
|
||||
pub async fn set_nip65(
|
||||
&mut self,
|
||||
relays: &[(RelayUrl, Option<RelayMetadata>)],
|
||||
) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
|
||||
let tags: Vec<Tag> = relays
|
||||
.iter()
|
||||
.map(|(url, metadata)| Tag::relay_metadata(url.to_owned(), metadata.to_owned()))
|
||||
.collect();
|
||||
|
||||
let event = EventBuilder::new(Kind::RelayList, "")
|
||||
.tags(tags)
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Send event to the public relays
|
||||
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
|
||||
|
||||
// Update gossip data
|
||||
for relay in relays {
|
||||
self.nip65
|
||||
.entry(event.pubkey)
|
||||
.or_default()
|
||||
.insert(relay.to_owned());
|
||||
}
|
||||
|
||||
// Get NIP-17 relays
|
||||
self.get_nip17(event.pubkey).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get and verify NIP-17 relays for a given public key
|
||||
///
|
||||
/// Only fetch from public key's write relays
|
||||
pub async fn get_nip17(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
let urls = self.write_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one write relay
|
||||
@@ -126,7 +177,22 @@ impl Gossip {
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
let latest_filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
// Subscribe to events from the bootstrapping relays
|
||||
client
|
||||
.subscribe_to(urls.clone(), latest_filter.clone(), Some(opts))
|
||||
.await?;
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.since(Timestamp::now());
|
||||
|
||||
// Continuously subscribe for new events from the bootstrap relays
|
||||
client
|
||||
.subscribe_to(urls, filter.clone(), Some(opts))
|
||||
.await?;
|
||||
@@ -135,7 +201,7 @@ impl Gossip {
|
||||
smol::spawn(async move {
|
||||
smol::Timer::after(timeout).await;
|
||||
|
||||
if client.database().count(filter).await.unwrap_or(0) < 1 {
|
||||
if client.database().count(latest_filter).await.unwrap_or(0) < 1 {
|
||||
app_state()
|
||||
.signal
|
||||
.send(SignalKind::MessagingRelaysNotFound)
|
||||
@@ -147,7 +213,51 @@ impl Gossip {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn subscribe(&mut self, public_key: PublicKey, kind: Kind) -> Result<(), Error> {
|
||||
/// Set NIP-17 relays for a current user
|
||||
pub async fn set_nip17(&mut self, relays: &[RelayUrl]) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
let urls = self.write_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter().cloned() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
let event = EventBuilder::new(Kind::InboxRelays, "")
|
||||
.tags(relays.iter().map(|relay| Tag::relay(relay.to_owned())))
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Send event to the public relays
|
||||
client.send_event_to(urls, &event).await?;
|
||||
|
||||
// Update gossip data
|
||||
for relay in relays {
|
||||
self.nip17
|
||||
.entry(event.pubkey)
|
||||
.or_default()
|
||||
.insert(relay.to_owned());
|
||||
}
|
||||
|
||||
// Run inbox monitor
|
||||
self.monitor_inbox(event.pubkey).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Subscribe for events that match the given kind for a given author
|
||||
///
|
||||
/// Only fetch from author's write relays
|
||||
pub async fn subscribe(&self, public_key: PublicKey, kind: Kind) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
@@ -171,7 +281,10 @@ impl Gossip {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn bulk_subscribe(&mut self, public_keys: HashSet<PublicKey>) -> Result<(), Error> {
|
||||
/// Bulk subscribe to metadata events for a list of public keys
|
||||
///
|
||||
/// Only fetch from the public relays
|
||||
pub async fn bulk_subscribe(&self, public_keys: HashSet<PublicKey>) -> Result<(), Error> {
|
||||
if public_keys.is_empty() {
|
||||
return Err(anyhow!("You need at least one public key"));
|
||||
}
|
||||
@@ -192,7 +305,7 @@ impl Gossip {
|
||||
}
|
||||
|
||||
/// Monitor all gift wrap events in the messaging relays for a given public key
|
||||
pub async fn monitor_inbox(&mut self, public_key: PublicKey) -> Result<(), Error> {
|
||||
pub async fn monitor_inbox(&self, public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let id = SubscriptionId::new("inbox");
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
@@ -214,4 +327,27 @@ impl Gossip {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send an event to author's write relays
|
||||
pub async fn send_event_to_write_relays(&self, event: &Event) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let public_key = event.pubkey;
|
||||
let urls = self.write_relays(&public_key);
|
||||
|
||||
// Ensure user's have at least one relay
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("Relays are empty"));
|
||||
}
|
||||
|
||||
// Ensure connection to relays
|
||||
for url in urls.iter().cloned() {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
|
||||
// Send event to relays
|
||||
client.send_event(event).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::nostr_client;
|
||||
use crate::paths::support_dir;
|
||||
use crate::state::gossip::Gossip;
|
||||
|
||||
pub mod gossip;
|
||||
mod gossip;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct AuthRequest {
|
||||
@@ -267,14 +267,18 @@ impl AppState {
|
||||
|
||||
match event.kind {
|
||||
Kind::RelayList => {
|
||||
let mut gossip = self.gossip.write().await;
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
// Update NIP-65 relays for event's public key
|
||||
gossip.insert(&event);
|
||||
{
|
||||
let mut gossip = self.gossip.write().await;
|
||||
gossip.insert(&event);
|
||||
}
|
||||
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
// Get events if relay list belongs to current user
|
||||
if is_self_authored {
|
||||
let gossip = self.gossip.read().await;
|
||||
|
||||
// Fetch user's metadata event
|
||||
gossip.subscribe(event.pubkey, Kind::Metadata).await.ok();
|
||||
|
||||
@@ -286,16 +290,19 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
Kind::InboxRelays => {
|
||||
let mut gossip = self.gossip.write().await;
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
// Update NIP-17 relays for event's public key
|
||||
gossip.insert(&event);
|
||||
{
|
||||
let mut gossip = self.gossip.write().await;
|
||||
gossip.insert(&event);
|
||||
}
|
||||
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
// Subscribe to gift wrap events if messaging relays belong to the current user
|
||||
if is_self_authored {
|
||||
if let Err(e) = gossip.monitor_inbox(event.pubkey).await {
|
||||
log::error!("Error: {e}");
|
||||
let gossip = self.gossip.read().await;
|
||||
|
||||
if gossip.monitor_inbox(event.pubkey).await.is_err() {
|
||||
self.signal.send(SignalKind::MessagingRelaysNotFound).await;
|
||||
}
|
||||
}
|
||||
@@ -304,11 +311,15 @@ impl AppState {
|
||||
let is_self_authored = Self::is_self_authored(&event).await;
|
||||
|
||||
if is_self_authored {
|
||||
let mut gossip = self.gossip.write().await;
|
||||
let public_keys: HashSet<PublicKey> =
|
||||
event.tags.public_keys().copied().collect();
|
||||
|
||||
gossip.bulk_subscribe(public_keys).await.ok();
|
||||
self.gossip
|
||||
.read()
|
||||
.await
|
||||
.bulk_subscribe(public_keys)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Kind::Metadata => {
|
||||
@@ -395,16 +406,16 @@ impl AppState {
|
||||
|
||||
// Process the batch if it's full
|
||||
if batch.len() >= METADATA_BATCH_LIMIT {
|
||||
let mut gossip = self.gossip.write().await;
|
||||
let gossip = self.gossip.read().await;
|
||||
gossip.bulk_subscribe(std::mem::take(&mut batch)).await.ok();
|
||||
}
|
||||
}
|
||||
BatchEvent::Timeout => {
|
||||
let mut gossip = self.gossip.write().await;
|
||||
let gossip = self.gossip.read().await;
|
||||
gossip.bulk_subscribe(std::mem::take(&mut batch)).await.ok();
|
||||
}
|
||||
BatchEvent::Closed => {
|
||||
let mut gossip = self.gossip.write().await;
|
||||
let gossip = self.gossip.read().await;
|
||||
gossip.bulk_subscribe(std::mem::take(&mut batch)).await.ok();
|
||||
|
||||
// Exit the current loop
|
||||
|
||||
@@ -7,15 +7,15 @@ use std::time::Duration;
|
||||
use anyhow::{anyhow, Error};
|
||||
use app_state::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS, DEFAULT_SIDEBAR_WIDTH};
|
||||
use app_state::state::{AuthRequest, SignalKind, UnwrappingStatus};
|
||||
use app_state::{app_state, nostr_client};
|
||||
use app_state::{app_state, default_nip17_relays, default_nip65_relays, nostr_client};
|
||||
use auto_update::AutoUpdater;
|
||||
use client_keys::ClientKeys;
|
||||
use common::display::RenderedProfile;
|
||||
use common::event::EventUtils;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
deferred, div, px, rems, App, AppContext, AsyncWindowContext, Axis, ClipboardItem, Context,
|
||||
Entity, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||
deferred, div, px, relative, rems, App, AppContext, AsyncWindowContext, Axis, ClipboardItem,
|
||||
Context, Entity, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, WeakEntity, Window,
|
||||
};
|
||||
use i18n::{shared_t, t};
|
||||
@@ -41,7 +41,7 @@ use ui::{h_flex, v_flex, ContextModal, Disableable, IconName, Root, Sizable, Sty
|
||||
|
||||
use crate::actions::{DarkMode, Logout, ReloadMetadata, Settings};
|
||||
use crate::views::compose::compose_button;
|
||||
use crate::views::setup_relay::setup_nip17_relay;
|
||||
use crate::views::setup_relay::SetupRelay;
|
||||
use crate::views::{
|
||||
account, chat, login, new_account, onboarding, preferences, sidebar, user_profile, welcome,
|
||||
};
|
||||
@@ -61,22 +61,25 @@ pub fn new_account(window: &mut Window, cx: &mut App) {
|
||||
}
|
||||
|
||||
pub struct ChatSpace {
|
||||
// App's Title Bar
|
||||
/// App's Title Bar
|
||||
title_bar: Entity<TitleBar>,
|
||||
|
||||
// App's Dock Area
|
||||
/// App's Dock Area
|
||||
dock: Entity<DockArea>,
|
||||
|
||||
// All authentication requests
|
||||
/// All authentication requests
|
||||
auth_requests: Entity<HashMap<RelayUrl, AuthRequest>>,
|
||||
|
||||
// Local state to determine if the user has set up NIP-17 relays
|
||||
nip17_relays: bool,
|
||||
/// Local state to determine if the user has set up NIP-17 relays
|
||||
nip17_ready: bool,
|
||||
|
||||
// All subscriptions for observing the app state
|
||||
/// Local state to determine if the user has set up NIP-65 relays
|
||||
nip65_ready: bool,
|
||||
|
||||
/// All subscriptions for observing the app state
|
||||
_subscriptions: SmallVec<[Subscription; 4]>,
|
||||
|
||||
// All long running tasks
|
||||
/// All long running tasks
|
||||
_tasks: SmallVec<[Task<()>; 5]>,
|
||||
}
|
||||
|
||||
@@ -203,7 +206,8 @@ impl ChatSpace {
|
||||
dock,
|
||||
title_bar,
|
||||
auth_requests,
|
||||
nip17_relays: true,
|
||||
nip17_ready: true,
|
||||
nip65_ready: true,
|
||||
_subscriptions: subscriptions,
|
||||
_tasks: tasks,
|
||||
}
|
||||
@@ -361,13 +365,14 @@ impl ChatSpace {
|
||||
}
|
||||
SignalKind::GossipRelaysNotFound => {
|
||||
view.update(cx, |this, cx| {
|
||||
this.set_required_relays(cx);
|
||||
this.set_required_gossip_relays(cx);
|
||||
this.render_setup_gossip_relays_modal(window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
SignalKind::MessagingRelaysNotFound => {
|
||||
view.update(cx, |this, cx| {
|
||||
this.set_required_relays(cx);
|
||||
this.set_required_dm_relays(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -639,8 +644,13 @@ impl ChatSpace {
|
||||
});
|
||||
}
|
||||
|
||||
fn set_required_relays(&mut self, cx: &mut Context<Self>) {
|
||||
self.nip17_relays = false;
|
||||
fn set_required_dm_relays(&mut self, cx: &mut Context<Self>) {
|
||||
self.nip17_ready = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_required_gossip_relays(&mut self, cx: &mut Context<Self>) {
|
||||
self.nip65_ready = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -789,6 +799,212 @@ impl ChatSpace {
|
||||
window.push_notification(t!("common.copied"), cx);
|
||||
}
|
||||
|
||||
fn render_setup_gossip_relays_modal(&mut self, window: &mut Window, cx: &mut App) {
|
||||
let relays = default_nip65_relays();
|
||||
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
this.overlay_closable(false)
|
||||
.show_close(false)
|
||||
.keyboard(false)
|
||||
.confirm()
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
.cancel_text(t!("common.configure"))
|
||||
.ok_text(t!("common.use_default")),
|
||||
)
|
||||
.title(shared_t!("mailbox.modal"))
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(shared_t!("mailbox.description"))
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(shared_t!("mailbox.write_label"))
|
||||
.child(shared_t!("mailbox.read_label")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.text_xs()
|
||||
.child(shared_t!("common.default")),
|
||||
)
|
||||
.child(v_flex().gap_1().children({
|
||||
let mut items = Vec::with_capacity(relays.len());
|
||||
|
||||
for (url, metadata) in relays {
|
||||
items.push(
|
||||
div()
|
||||
.h_7()
|
||||
.px_1p5()
|
||||
.h_flex()
|
||||
.justify_between()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.line_height(relative(1.2))
|
||||
.child(SharedString::from(url.to_string())),
|
||||
)
|
||||
.when_some(metadata.as_ref(), |this, metadata| {
|
||||
this.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.line_height(relative(1.2))
|
||||
.child(SharedString::from(
|
||||
metadata.to_string(),
|
||||
)),
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
items
|
||||
})),
|
||||
)
|
||||
.on_cancel(|_, _window, _cx| {
|
||||
// TODO: add configure relays
|
||||
// true to close the modal
|
||||
true
|
||||
})
|
||||
.on_ok(|_, window, cx| {
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let app_state = app_state();
|
||||
let relays = default_nip65_relays();
|
||||
|
||||
let mut gossip = app_state.gossip.write().await;
|
||||
let result = gossip.set_nip65(relays).await;
|
||||
|
||||
cx.update(|window, cx| {
|
||||
match result {
|
||||
Ok(_) => {
|
||||
window.close_modal(cx);
|
||||
}
|
||||
Err(e) => {
|
||||
window.push_notification(e.to_string(), cx);
|
||||
}
|
||||
};
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
// false to keep modal open
|
||||
false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn render_setup_dm_relays_modal(window: &mut Window, cx: &mut App) {
|
||||
let relays = default_nip17_relays();
|
||||
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
this.overlay_closable(false)
|
||||
.show_close(false)
|
||||
.keyboard(false)
|
||||
.confirm()
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
.cancel_text(t!("common.configure"))
|
||||
.ok_text(t!("common.use_default")),
|
||||
)
|
||||
.title(shared_t!("messaging.modal"))
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(shared_t!("messaging.description"))
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.text_xs()
|
||||
.child(shared_t!("common.default")),
|
||||
)
|
||||
.child(v_flex().gap_1().children({
|
||||
let mut items = Vec::with_capacity(relays.len());
|
||||
|
||||
for url in relays {
|
||||
items.push(
|
||||
div()
|
||||
.h_7()
|
||||
.px_1p5()
|
||||
.h_flex()
|
||||
.justify_between()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.line_height(relative(1.2))
|
||||
.child(SharedString::from(url.to_string())),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
items
|
||||
})),
|
||||
)
|
||||
.on_cancel(|_, window, cx| {
|
||||
let view = cx.new(|cx| SetupRelay::new(window, cx));
|
||||
let weak_view = view.downgrade();
|
||||
|
||||
window.open_modal(cx, move |modal, _window, _cx| {
|
||||
let weak_view = weak_view.clone();
|
||||
|
||||
modal
|
||||
.confirm()
|
||||
.title(shared_t!("relays.modal"))
|
||||
.child(view.clone())
|
||||
.button_props(ModalButtonProps::default().ok_text(t!("common.update")))
|
||||
.on_ok(move |_, window, cx| {
|
||||
weak_view
|
||||
.update(cx, |this, cx| {
|
||||
this.set_relays(window, cx);
|
||||
})
|
||||
.ok();
|
||||
// true to close the modal
|
||||
false
|
||||
})
|
||||
});
|
||||
|
||||
// true to close the modal
|
||||
true
|
||||
})
|
||||
.on_ok(|_, window, cx| {
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let app_state = app_state();
|
||||
let relays = default_nip17_relays();
|
||||
|
||||
let mut gossip = app_state.gossip.write().await;
|
||||
let result = gossip.set_nip17(relays).await;
|
||||
|
||||
cx.update(|window, cx| {
|
||||
match result {
|
||||
Ok(_) => {
|
||||
window.close_modal(cx);
|
||||
}
|
||||
Err(e) => {
|
||||
window.push_notification(e.to_string(), cx);
|
||||
}
|
||||
};
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
// false to keep modal open
|
||||
false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn render_proxy_modal(&mut self, window: &mut Window, cx: &mut App) {
|
||||
window.open_modal(cx, |this, _window, _cx| {
|
||||
this.overlay_closable(false)
|
||||
@@ -954,8 +1170,18 @@ impl ChatSpace {
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(!self.nip17_relays, |this| {
|
||||
this.child(setup_nip17_relay(t!("relays.button")))
|
||||
.when(!self.nip17_ready, |this| {
|
||||
this.child(
|
||||
Button::new("setup-relays-button")
|
||||
.icon(IconName::Info)
|
||||
.label(t!("messaging.button"))
|
||||
.warning()
|
||||
.xsmall()
|
||||
.rounded()
|
||||
.on_click(move |_ev, window, cx| {
|
||||
Self::render_setup_dm_relays_modal(window, cx);
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Button::new("user")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use app_state::nostr_client;
|
||||
use anyhow::Error;
|
||||
use app_state::{app_state, nostr_client};
|
||||
use common::nip96::nip96_upload;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -164,7 +165,7 @@ impl EditProfile {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn set_metadata(&mut self, cx: &mut Context<Self>) -> Task<Result<Option<Profile>, Error>> {
|
||||
pub fn set_metadata(&mut self, cx: &mut Context<Self>) -> Task<Result<Profile, Error>> {
|
||||
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();
|
||||
@@ -187,18 +188,23 @@ impl EditProfile {
|
||||
}
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let output = client.set_metadata(&new_metadata).await?;
|
||||
let event = client
|
||||
.database()
|
||||
.event_by_id(&output.val)
|
||||
.await?
|
||||
.map(|event| {
|
||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||
Profile::new(event.pubkey, metadata)
|
||||
});
|
||||
let app_state = app_state();
|
||||
let gossip = app_state.gossip.read().await;
|
||||
|
||||
Ok(event)
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
|
||||
// Sign the new metadata event
|
||||
let event = EventBuilder::metadata(&new_metadata).sign(&signer).await?;
|
||||
|
||||
// Send event to user's write relayss
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
|
||||
// Return the updated profile
|
||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||
let profile = Profile::new(event.pubkey, metadata);
|
||||
|
||||
Ok(profile)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use anyhow::anyhow;
|
||||
use app_state::constants::{ACCOUNT_IDENTIFIER, NIP17_RELAYS, NIP65_RELAYS};
|
||||
use app_state::nostr_client;
|
||||
use anyhow::{anyhow, Error};
|
||||
use app_state::constants::{ACCOUNT_IDENTIFIER, BOOTSTRAP_RELAYS};
|
||||
use app_state::{app_state, default_nip17_relays, default_nip65_relays, nostr_client};
|
||||
use common::nip96::nip96_upload;
|
||||
use gpui::{
|
||||
div, relative, rems, AnyElement, App, AppContext, AsyncWindowContext, Context, Entity,
|
||||
EventEmitter, Flatten, FocusHandle, Focusable, IntoElement, ParentElement, PathPromptOptions,
|
||||
Render, SharedString, Styled, WeakEntity, Window,
|
||||
Render, SharedString, Styled, Task, WeakEntity, Window,
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use i18n::{shared_t, t};
|
||||
@@ -123,48 +123,51 @@ impl NewAccount {
|
||||
self.write_keys_to_disk(&keys, password, cx);
|
||||
|
||||
// Set the client's signer with the current keys
|
||||
cx.background_spawn(async move {
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let app_state = app_state();
|
||||
let gossip = app_state.gossip.read().await;
|
||||
|
||||
// Set the client's signer with the current keys
|
||||
client.set_signer(keys).await;
|
||||
|
||||
// Set metadata
|
||||
if let Err(e) = client.set_metadata(&metadata).await {
|
||||
log::error!("Failed to set metadata: {e}");
|
||||
}
|
||||
// Verify the signer
|
||||
let signer = client.signer().await?;
|
||||
|
||||
// Construct a NIP-65 event
|
||||
let event = EventBuilder::new(Kind::RelayList, "")
|
||||
.tags(default_nip65_relays().iter().map(|(url, metadata)| {
|
||||
Tag::relay_metadata(url.to_owned(), metadata.to_owned())
|
||||
}))
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Set NIP-65 relays
|
||||
let builder = EventBuilder::new(Kind::RelayList, "").tags(
|
||||
NIP65_RELAYS.into_iter().filter_map(|url| {
|
||||
if let Ok(url) = RelayUrl::parse(url) {
|
||||
Some(Tag::relay_metadata(url, None))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
);
|
||||
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
|
||||
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send NIP-65 relay list event: {e}");
|
||||
}
|
||||
// Construct a NIP-17 event
|
||||
let event = EventBuilder::new(Kind::InboxRelays, "")
|
||||
.tags(
|
||||
default_nip17_relays()
|
||||
.iter()
|
||||
.map(|url| Tag::relay(url.to_owned())),
|
||||
)
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Set NIP-17 relays
|
||||
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(
|
||||
NIP17_RELAYS.into_iter().filter_map(|url| {
|
||||
if let Ok(url) = RelayUrl::parse(url) {
|
||||
Some(Tag::relay(url))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
);
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
|
||||
if let Err(e) = client.send_event_builder(builder).await {
|
||||
log::error!("Failed to send messaging relay list event: {e}");
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
// Construct a metadata event
|
||||
let event = EventBuilder::metadata(&metadata).sign(&signer).await?;
|
||||
|
||||
// Set metadata
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
task.detach();
|
||||
}
|
||||
|
||||
fn write_keys_to_disk(&self, keys: &Keys, password: String, cx: &mut Context<Self>) {
|
||||
|
||||
@@ -6,7 +6,6 @@ use gpui::{
|
||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
use i18n::{shared_t, t};
|
||||
use nostr_sdk::prelude::*;
|
||||
use registry::Registry;
|
||||
use settings::AppSettings;
|
||||
use theme::ActiveTheme;
|
||||
@@ -54,28 +53,25 @@ impl Preferences {
|
||||
.on_ok(move |_, window, cx| {
|
||||
weak_view
|
||||
.update(cx, |this, cx| {
|
||||
let set_metadata = this.set_metadata(cx);
|
||||
let registry = Registry::global(cx);
|
||||
let set_metadata = this.set_metadata(cx);
|
||||
|
||||
cx.spawn_in(window, async move |_, cx| {
|
||||
match set_metadata.await {
|
||||
Ok(profile) => {
|
||||
if let Some(profile) = profile {
|
||||
cx.update(|_, cx| {
|
||||
registry.update(cx, |this, cx| {
|
||||
this.insert_or_update_person(profile, cx);
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let result = set_metadata.await;
|
||||
|
||||
this.update_in(cx, |_, window, cx| {
|
||||
match result {
|
||||
Ok(profile) => {
|
||||
registry.update(cx, |this, cx| {
|
||||
this.insert_or_update_person(profile, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
cx.update(|window, cx| {
|
||||
Err(e) => {
|
||||
window.push_notification(e.to_string(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
@@ -87,7 +83,7 @@ impl Preferences {
|
||||
}
|
||||
|
||||
fn open_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let view = setup_relay::init(Kind::InboxRelays, window, cx);
|
||||
let view = setup_relay::init(window, cx);
|
||||
let weak_view = view.downgrade();
|
||||
|
||||
window.open_modal(cx, move |this, _window, _cx| {
|
||||
|
||||
@@ -158,11 +158,13 @@ impl Screening {
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let builder = EventBuilder::report(
|
||||
vec![Tag::public_key_report(public_key, Report::Impersonation)],
|
||||
"scam/impersonation",
|
||||
);
|
||||
let _ = client.send_event_builder(builder).await?;
|
||||
let signer = client.signer().await?;
|
||||
|
||||
let tag = Tag::public_key_report(public_key, Report::Impersonation);
|
||||
let event = EventBuilder::report(vec![tag], "").sign(&signer).await?;
|
||||
|
||||
// Send the report to the public relays
|
||||
client.send_event_to(BOOTSTRAP_RELAYS, &event).await?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use app_state::constants::NIP17_RELAYS;
|
||||
use app_state::{app_state, nostr_client};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, px, uniform_list, App, AppContext, Context, Entity, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task,
|
||||
TextAlign, UniformList, Window,
|
||||
div, px, uniform_list, App, AppContext, AsyncWindowContext, Context, Entity,
|
||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
|
||||
Task, TextAlign, UniformList, Window,
|
||||
};
|
||||
use i18n::{shared_t, t};
|
||||
use nostr_sdk::prelude::*;
|
||||
@@ -15,100 +15,40 @@ use smallvec::{smallvec, SmallVec};
|
||||
use theme::ActiveTheme;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, StyledExt};
|
||||
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable};
|
||||
|
||||
pub fn init(kind: Kind, window: &mut Window, cx: &mut App) -> Entity<SetupRelay> {
|
||||
cx.new(|cx| SetupRelay::new(kind, window, cx))
|
||||
}
|
||||
|
||||
pub fn setup_nip17_relay<T>(label: T) -> impl IntoElement
|
||||
where
|
||||
T: Into<SharedString>,
|
||||
{
|
||||
div().child(
|
||||
Button::new("setup-relays")
|
||||
.icon(IconName::Info)
|
||||
.label(label)
|
||||
.warning()
|
||||
.xsmall()
|
||||
.rounded()
|
||||
.on_click(move |_, window, cx| {
|
||||
let view = cx.new(|cx| SetupRelay::new(Kind::InboxRelays, window, cx));
|
||||
let weak_view = view.downgrade();
|
||||
|
||||
window.open_modal(cx, move |modal, _window, _cx| {
|
||||
let weak_view = weak_view.clone();
|
||||
|
||||
modal
|
||||
.confirm()
|
||||
.title(shared_t!("relays.modal"))
|
||||
.child(view.clone())
|
||||
.button_props(ModalButtonProps::default().ok_text(t!("common.update")))
|
||||
.on_ok(move |_, window, cx| {
|
||||
weak_view
|
||||
.update(cx, |this, cx| {
|
||||
this.set_relays(window, cx);
|
||||
})
|
||||
.ok();
|
||||
// true to close the modal
|
||||
false
|
||||
})
|
||||
})
|
||||
}),
|
||||
)
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<SetupRelay> {
|
||||
cx.new(|cx| SetupRelay::new(window, cx))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SetupRelay {
|
||||
input: Entity<InputState>,
|
||||
relays: Vec<RelayUrl>,
|
||||
error: Option<SharedString>,
|
||||
|
||||
// All relays
|
||||
relays: HashSet<RelayUrl>,
|
||||
|
||||
// Event subscriptions
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
|
||||
// Background tasks
|
||||
_tasks: SmallVec<[Task<()>; 1]>,
|
||||
}
|
||||
|
||||
impl SetupRelay {
|
||||
pub fn new(kind: Kind, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let input = cx.new(|cx| InputState::new(window, cx).placeholder("wss://example.com"));
|
||||
|
||||
let mut subscriptions = smallvec![];
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
let load_relay = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
let filter = Filter::new().kind(kind).author(public_key).limit(1);
|
||||
|
||||
if let Some(event) = client.database().query(filter).await?.first() {
|
||||
let relays: Vec<RelayUrl> = event
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|tag| tag.as_standardized())
|
||||
.filter_map(|tag| {
|
||||
if let TagStandard::RelayMetadata { relay_url, .. } = tag {
|
||||
Some(relay_url.to_owned())
|
||||
} else if let TagStandard::Relay(url) = tag {
|
||||
Some(url.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(relays)
|
||||
} else {
|
||||
Err(anyhow!("Not found."))
|
||||
}
|
||||
});
|
||||
|
||||
tasks.push(
|
||||
// Load user's relays in the local database
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if let Ok(relays) = load_relay.await {
|
||||
if let Ok(relays) = Self::load(cx).await {
|
||||
this.update(cx, |this, cx| {
|
||||
this.relays = relays;
|
||||
this.relays.extend(relays);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
@@ -131,35 +71,55 @@ impl SetupRelay {
|
||||
|
||||
Self {
|
||||
input,
|
||||
relays: vec![],
|
||||
relays: HashSet::new(),
|
||||
error: None,
|
||||
_subscriptions: subscriptions,
|
||||
_tasks: tasks,
|
||||
}
|
||||
}
|
||||
|
||||
fn load(cx: &AsyncWindowContext) -> Task<Result<Vec<RelayUrl>, Error>> {
|
||||
cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
let urls = nip17::extract_owned_relay_list(event).collect();
|
||||
Ok(urls)
|
||||
} else {
|
||||
Err(anyhow!("Not found."))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let value = self.input.read(cx).value().to_string();
|
||||
|
||||
if !value.starts_with("ws") {
|
||||
self.set_error("Relay URl is invalid", window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(url) = RelayUrl::parse(&value) {
|
||||
if !self.relays.contains(&url) {
|
||||
self.relays.push(url);
|
||||
if !self.relays.insert(url) {
|
||||
self.input.update(cx, |this, cx| {
|
||||
this.set_value("", window, cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
self.input.update(cx, |this, cx| {
|
||||
this.set_value("", window, cx);
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
} else {
|
||||
self.set_error("Relay URl is invalid", window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, ix: usize, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.relays.remove(ix);
|
||||
fn remove(&mut self, url: &RelayUrl, cx: &mut Context<Self>) {
|
||||
self.relays.remove(url);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -173,12 +133,10 @@ impl SetupRelay {
|
||||
// Clear the error message after a delay
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
cx.update(|_, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.error = None;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.error = None;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -198,6 +156,9 @@ impl SetupRelay {
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
let app_state = app_state();
|
||||
let gossip = app_state.gossip.read().await;
|
||||
|
||||
let tags: Vec<Tag> = relays
|
||||
.iter()
|
||||
.map(|relay| Tag::relay(relay.clone()))
|
||||
@@ -205,21 +166,20 @@ impl SetupRelay {
|
||||
|
||||
let event = EventBuilder::new(Kind::InboxRelays, "")
|
||||
.tags(tags)
|
||||
.build(public_key)
|
||||
.sign(&signer)
|
||||
.await?;
|
||||
|
||||
// Set messaging relays
|
||||
client.send_event(&event).await?;
|
||||
gossip.send_event_to_write_relays(&event).await?;
|
||||
|
||||
// Connect to messaging relays
|
||||
for relay in relays.iter() {
|
||||
_ = client.add_relay(relay).await;
|
||||
_ = client.connect_relay(relay).await;
|
||||
client.add_relay(relay).await.ok();
|
||||
client.connect_relay(relay).await.ok();
|
||||
}
|
||||
|
||||
// Fetch gift wrap events
|
||||
let sub_id = app_state().gift_wrap_sub_id.clone();
|
||||
let sub_id = app_state.gift_wrap_sub_id.clone();
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
|
||||
if client
|
||||
@@ -259,38 +219,47 @@ impl SetupRelay {
|
||||
uniform_list(
|
||||
"relays",
|
||||
total,
|
||||
cx.processor(move |_, range, _window, cx| {
|
||||
cx.processor(move |_v, range, _window, cx| {
|
||||
let mut items = Vec::new();
|
||||
|
||||
for ix in range {
|
||||
let item = relays.get(ix).map(|i: &RelayUrl| i.to_string()).unwrap();
|
||||
|
||||
items.push(
|
||||
div().group("").w_full().h_9().py_0p5().child(
|
||||
if let Some(url) = relays.iter().nth(ix) {
|
||||
items.push(
|
||||
div()
|
||||
.px_2()
|
||||
.h_full()
|
||||
.id(SharedString::from(url.to_string()))
|
||||
.group("")
|
||||
.w_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.text_xs()
|
||||
.child(item)
|
||||
.h_9()
|
||||
.py_0p5()
|
||||
.child(
|
||||
Button::new("remove_{ix}")
|
||||
.icon(IconName::Close)
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.invisible()
|
||||
.group_hover("", |this| this.visible())
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.remove(ix, window, cx)
|
||||
})),
|
||||
div()
|
||||
.px_2()
|
||||
.h_full()
|
||||
.w_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.text_xs()
|
||||
.child(SharedString::from(url.to_string()))
|
||||
.child(
|
||||
Button::new("remove_{ix}")
|
||||
.icon(IconName::Close)
|
||||
.xsmall()
|
||||
.ghost()
|
||||
.invisible()
|
||||
.group_hover("", |this| this.visible())
|
||||
.on_click({
|
||||
let url = url.to_owned();
|
||||
cx.listener(move |this, _ev, _window, cx| {
|
||||
this.remove(&url, cx);
|
||||
})
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
items
|
||||
@@ -339,39 +308,6 @@ impl Render for SetupRelay {
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(shared_t!("common.recommended")),
|
||||
)
|
||||
.child(h_flex().gap_1().children({
|
||||
NIP17_RELAYS.iter().map(|&relay| {
|
||||
div()
|
||||
.id(relay)
|
||||
.group("")
|
||||
.py_0p5()
|
||||
.px_1p5()
|
||||
.text_xs()
|
||||
.text_center()
|
||||
.bg(cx.theme().secondary_background)
|
||||
.hover(|this| this.bg(cx.theme().secondary_hover))
|
||||
.active(|this| this.bg(cx.theme().secondary_active))
|
||||
.rounded_full()
|
||||
.child(relay)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.input.update(cx, |this, cx| {
|
||||
this.set_value(relay, window, cx);
|
||||
});
|
||||
this.add(window, cx);
|
||||
}))
|
||||
})
|
||||
})),
|
||||
)
|
||||
.when_some(self.error.as_ref(), |this, error| {
|
||||
this.child(
|
||||
div()
|
||||
|
||||
@@ -69,7 +69,7 @@ impl Focusable for Welcome {
|
||||
}
|
||||
|
||||
impl Render for Welcome {
|
||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
|
||||
@@ -505,7 +505,7 @@ impl Room {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send the event to the relays
|
||||
// Send the event to the messaging relays
|
||||
match client.send_event_to(urls, &event).await {
|
||||
Ok(output) => {
|
||||
let id = output.id().to_owned();
|
||||
@@ -556,7 +556,7 @@ impl Room {
|
||||
if urls.is_empty() {
|
||||
reports.push(SendReport::new(public_key).not_found());
|
||||
} else {
|
||||
// Send the event to the relays
|
||||
// Send the event to the messaging relays
|
||||
match client.send_event_to(urls, &event).await {
|
||||
Ok(output) => {
|
||||
reports.push(SendReport::new(public_key).status(output));
|
||||
@@ -619,7 +619,7 @@ impl Room {
|
||||
if urls.is_empty() {
|
||||
resend_reports.push(SendReport::new(receiver).not_found());
|
||||
} else {
|
||||
// Send the event to the relays
|
||||
// Send the event to the messaging relays
|
||||
match client.send_event_to(urls, &event).await {
|
||||
Ok(output) => {
|
||||
resend_reports.push(SendReport::new(receiver).status(output));
|
||||
|
||||
Reference in New Issue
Block a user