chore: improve gossip implementation (#184)
* add send event function * add set nip17 and set nip65 functions * setup gossip relays * .
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user