Remove hardcoded default relays and add user relay prompt

Instead of automatically connecting to hardcoded relays (wss://nos.lol,
wss://relay.damus.io, wss://relay.nostr.band), the app now prompts users
to enter a relay URL when no relays are configured.

Changes:
- Remove hardcoded relays from RelayConfig::default_relays()
- Add RelayDialog UI component for relay input
- Automatically show relay dialog on startup when no relays exist
- Save user-entered relay to relay_config.json
- Connect to relay pool after user adds it

This gives users full control over which relays they connect to from
the start.
This commit is contained in:
Claude
2025-11-15 11:02:11 +00:00
parent ce0ffa491f
commit 2d61148d93
4 changed files with 211 additions and 11 deletions

View File

@@ -51,10 +51,14 @@ pub struct Damus {
/// Flag to defer account redirect until after first frame to avoid egui layer conflicts /// Flag to defer account redirect until after first frame to avoid egui layer conflicts
need_account_redirect: bool, need_account_redirect: bool,
/// Flag to show relay dialog if no relays are configured
need_relay_prompt: bool,
pub decks_cache: DecksCache, pub decks_cache: DecksCache,
pub channels_cache: crate::channels::ChannelsCache, pub channels_cache: crate::channels::ChannelsCache,
pub relay_config: crate::relay_config::RelayConfig, pub relay_config: crate::relay_config::RelayConfig,
pub channel_dialog: ui::ChannelDialog, pub channel_dialog: ui::ChannelDialog,
pub relay_dialog: ui::RelayDialog,
pub thread_panel: ui::ThreadPanel, pub thread_panel: ui::ThreadPanel,
pub view_state: ViewState, pub view_state: ViewState,
pub drafts: Drafts, pub drafts: Drafts,
@@ -277,6 +281,13 @@ fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ctx: &egui::Con
} }
DamusState::Initialized => { DamusState::Initialized => {
// Perform deferred relay prompt if needed
if damus.need_relay_prompt {
damus.need_relay_prompt = false;
info!("Opening relay dialog - no relays configured");
damus.relay_dialog.open();
}
// Perform deferred account redirect if needed // Perform deferred account redirect if needed
if damus.need_account_redirect { if damus.need_account_redirect {
damus.need_account_redirect = false; damus.need_account_redirect = false;
@@ -552,6 +563,31 @@ fn render_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui::
} }
} }
// Show relay dialog and handle relay addition
if let Some(dialog_action) = damus.relay_dialog.show(ui.ctx(), app_ctx.i18n) {
match dialog_action {
ui::RelayDialogAction::Add { url } => {
info!("User adding relay: {}", url);
// Add relay to config
damus.relay_config.add_relay(url.clone());
// Save relay config to disk
storage::save_relay_config(app_ctx.path, &damus.relay_config);
// Add relay to pool
let wakeup = || {}; // Wakeup closure for relay events
if let Err(e) = app_ctx.pool.add_url(url.clone(), wakeup) {
error!("Failed to add relay {}: {}", url, e);
} else {
info!("Successfully connected to relay: {}", url);
}
}
ui::RelayDialogAction::Cancel => {
// Dialog was canceled, nothing to do
}
}
}
// Show thread panel if open // Show thread panel if open
if damus.thread_panel.is_open { if damus.thread_panel.is_open {
let mut note_context = notedeck::NoteContext { let mut note_context = notedeck::NoteContext {
@@ -797,6 +833,12 @@ impl Damus {
crate::relay_config::RelayConfig::default() crate::relay_config::RelayConfig::default()
}; };
// Check if we need to prompt user for a relay
let need_relay_prompt = relay_config.is_empty();
if need_relay_prompt {
info!("No relays configured, will prompt user to add relay");
}
// Apply relay config to pool - connect to configured relays // Apply relay config to pool - connect to configured relays
for relay_url in relay_config.get_relays() { for relay_url in relay_config.get_relays() {
let wakeup = || {}; // Wakeup closure for relay events let wakeup = || {}; // Wakeup closure for relay events
@@ -813,6 +855,7 @@ impl Damus {
drafts: Drafts::default(), drafts: Drafts::default(),
state: DamusState::Initializing, state: DamusState::Initializing,
need_account_redirect: false, need_account_redirect: false,
need_relay_prompt,
note_options, note_options,
options, options,
//frame_history: FrameHistory::default(), //frame_history: FrameHistory::default(),
@@ -822,6 +865,7 @@ impl Damus {
channels_cache, channels_cache,
relay_config, relay_config,
channel_dialog: ui::ChannelDialog::default(), channel_dialog: ui::ChannelDialog::default(),
relay_dialog: ui::RelayDialog::default(),
thread_panel: ui::ThreadPanel::default(), thread_panel: ui::ThreadPanel::default(),
unrecognized_args, unrecognized_args,
jobs, jobs,
@@ -871,6 +915,7 @@ impl Damus {
drafts: Drafts::default(), drafts: Drafts::default(),
state: DamusState::Initializing, state: DamusState::Initializing,
need_account_redirect: false, need_account_redirect: false,
need_relay_prompt: relay_config.is_empty(),
note_options: NoteOptions::default(), note_options: NoteOptions::default(),
//frame_history: FrameHistory::default(), //frame_history: FrameHistory::default(),
view_state: ViewState::default(), view_state: ViewState::default(),
@@ -880,6 +925,7 @@ impl Damus {
channels_cache, channels_cache,
relay_config, relay_config,
channel_dialog: ui::ChannelDialog::default(), channel_dialog: ui::ChannelDialog::default(),
relay_dialog: ui::RelayDialog::default(),
thread_panel: ui::ThreadPanel::default(), thread_panel: ui::ThreadPanel::default(),
unrecognized_args: BTreeSet::default(), unrecognized_args: BTreeSet::default(),
jobs: JobsCache::default(), jobs: JobsCache::default(),

View File

@@ -17,16 +17,10 @@ impl RelayConfig {
} }
} }
/// Create a default configuration with some popular relays /// Create an empty default configuration
/// Users should be prompted to add relays if none exist
pub fn default_relays() -> Self { pub fn default_relays() -> Self {
let mut relays = BTreeSet::new(); Self::new()
// Add some default public relays
relays.insert("wss://relay.damus.io".to_string());
relays.insert("wss://relay.nostr.band".to_string());
relays.insert("wss://nos.lol".to_string());
Self { relays }
} }
pub fn add_relay(&mut self, url: String) -> bool { pub fn add_relay(&mut self, url: String) -> bool {
@@ -88,7 +82,6 @@ mod tests {
#[test] #[test]
fn test_default_relays() { fn test_default_relays() {
let config = RelayConfig::default_relays(); let config = RelayConfig::default_relays();
assert!(!config.is_empty()); assert!(config.is_empty());
assert!(config.has_relay("wss://relay.damus.io"));
} }
} }

View File

@@ -15,6 +15,7 @@ pub mod post;
pub mod preview; pub mod preview;
pub mod profile; pub mod profile;
pub mod relay; pub mod relay;
pub mod relay_dialog;
pub mod repost; pub mod repost;
pub mod search; pub mod search;
pub mod settings; pub mod settings;
@@ -35,6 +36,7 @@ pub use note::{PostReplyView, PostView};
pub use preview::{Preview, PreviewApp, PreviewConfig}; pub use preview::{Preview, PreviewApp, PreviewConfig};
pub use profile::ProfileView; pub use profile::ProfileView;
pub use relay::RelayView; pub use relay::RelayView;
pub use relay_dialog::{RelayDialog, RelayDialogAction};
pub use settings::SettingsView; pub use settings::SettingsView;
pub use side_panel::{DesktopSidePanel, SidePanelAction}; pub use side_panel::{DesktopSidePanel, SidePanelAction};
pub use thread::ThreadView; pub use thread::ThreadView;

View File

@@ -0,0 +1,159 @@
use egui::{RichText, TextEdit, Vec2};
use notedeck::{tr, Localization};
pub struct RelayDialog {
pub relay_url: String,
pub is_open: bool,
pub focus_requested: bool,
}
pub enum RelayDialogAction {
Add { url: String },
Cancel,
}
impl RelayDialog {
pub fn new() -> Self {
Self {
relay_url: String::new(),
is_open: false,
focus_requested: false,
}
}
pub fn open(&mut self) {
self.is_open = true;
self.relay_url.clear();
self.focus_requested = false;
}
pub fn close(&mut self) {
self.is_open = false;
}
pub fn show(
&mut self,
ctx: &egui::Context,
i18n: &mut Localization,
) -> Option<RelayDialogAction> {
if !self.is_open {
return None;
}
let mut action: Option<RelayDialogAction> = None;
let title = tr!(i18n, "Add Relay", "Dialog title for adding a relay");
egui::Window::new(title)
.collapsible(false)
.resizable(false)
.anchor(egui::Align2::CENTER_CENTER, Vec2::ZERO)
.fixed_size(Vec2::new(450.0, 200.0))
.show(ctx, |ui| {
ui.vertical(|ui| {
ui.add_space(16.0);
// Relay URL input
ui.label(
RichText::new(tr!(i18n, "Relay URL", "Label for relay URL input"))
.size(14.0)
.strong(),
);
ui.add_space(4.0);
ui.label(
RichText::new(tr!(
i18n,
"Enter a relay URL to connect to the nostr network",
"Help text for relay URL input"
))
.size(12.0)
.color(ui.visuals().weak_text_color()),
);
ui.add_space(8.0);
let url_response = ui.add(
TextEdit::singleline(&mut self.relay_url)
.hint_text(tr!(
i18n,
"wss://relay.example.com",
"Placeholder for relay URL"
))
.desired_width(f32::INFINITY),
);
// Auto-focus on URL field when first opened
if !self.focus_requested {
url_response.request_focus();
self.focus_requested = true;
}
// Handle Escape and Enter keys
let escape_pressed = ui.input(|i| i.key_pressed(egui::Key::Escape));
let enter_pressed = ui.input(|i| i.key_pressed(egui::Key::Enter));
if escape_pressed {
action = Some(RelayDialogAction::Cancel);
}
if enter_pressed && !self.relay_url.trim().is_empty() {
action = Some(RelayDialogAction::Add {
url: self.relay_url.trim().to_string(),
});
}
ui.add_space(24.0);
// Buttons
ui.horizontal(|ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
// Add button
let button_enabled = !self.relay_url.trim().is_empty();
let button = egui::Button::new(
RichText::new(tr!(i18n, "Add", "Button to add relay"))
.size(14.0),
)
.min_size(Vec2::new(80.0, 32.0));
let button_response = ui.add_enabled(button_enabled, button);
if button_response.clicked() {
action = Some(RelayDialogAction::Add {
url: self.relay_url.trim().to_string(),
});
}
ui.add_space(8.0);
// Cancel button
let cancel_button = egui::Button::new(
RichText::new(tr!(i18n, "Cancel", "Button to cancel"))
.size(14.0)
.color(ui.visuals().weak_text_color()),
)
.frame(false)
.min_size(Vec2::new(80.0, 32.0));
if ui.add(cancel_button).clicked() {
action = Some(RelayDialogAction::Cancel);
}
});
});
});
});
// Close dialog if action was taken
if action.is_some() {
self.close();
}
action
}
}
impl Default for RelayDialog {
fn default() -> Self {
Self::new()
}
}