From ac9afb1790099fa4d9cee42480fac9fbde74f7f3 Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 14 Jan 2026 09:48:15 +0800 Subject: [PATCH] chore: refactor app settings (#2) # Changelog ### Added - [x] Add `Auth Mode` setting. - [x] Add `Room Config` setting. ### Changed - [x] Rename `media server` setting to `file server` ### Removed - [x] Remove `proxy` setting. Coop is no longer depend on any 3rd party services. - [x] Remove `contact bypass` settings. All chat requests from known contacts will be bypass by default. **Note:** - The Settings UI has been removed. It will be re-added in a separate PR. Reviewed-on: https://git.reya.su/reya/coop/pulls/2 --- Cargo.lock | 60 ++++----- crates/chat/src/lib.rs | 19 +-- crates/chat_ui/src/lib.rs | 7 +- crates/common/src/display.rs | 18 +-- crates/coop/src/new_identity/mod.rs | 2 +- crates/coop/src/sidebar/list_item.rs | 6 +- crates/coop/src/user/mod.rs | 2 +- crates/coop/src/views/preferences.rs | 173 +------------------------ crates/coop/src/views/screening.rs | 2 +- crates/relay_auth/src/lib.rs | 10 +- crates/settings/src/lib.rs | 182 ++++++++++++++++----------- 11 files changed, 171 insertions(+), 310 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa19728..088afc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1155,7 +1155,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1583,7 +1583,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "proc-macro2", "quote", @@ -2487,7 +2487,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2588,7 +2588,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2599,7 +2599,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "anyhow", "gpui", @@ -2821,7 +2821,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "anyhow", "async-compression", @@ -2846,7 +2846,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3607,7 +3607,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "anyhow", "bindgen", @@ -4458,7 +4458,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "collections", "serde", @@ -4887,7 +4887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.4", + "rand_core 0.9.5", ] [[package]] @@ -4907,7 +4907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.4", + "rand_core 0.9.5", ] [[package]] @@ -4921,9 +4921,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1b3bc831f92381018fd9c6350b917c7b21f1eed35a65a51900e0e55a3d7afa" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -5093,7 +5093,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "derive_refineable", ] @@ -5192,7 +5192,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "anyhow", "bytes", @@ -5246,7 +5246,7 @@ dependencies = [ [[package]] name = "rope" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "arrayvec", "log", @@ -5266,9 +5266,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rust-embed" -version = "8.9.0" +version = "8.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca" +checksum = "f783a9e226b5319beefe29d45941f559ace8b56801bb8355be17eea277fc8272" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -5277,9 +5277,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.9.0" +version = "8.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2" +checksum = "303d4e979140595f1d824b3dd53a32684835fa32425542056826521ac279f538" dependencies = [ "proc-macro2", "quote", @@ -5290,9 +5290,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.9.0" +version = "8.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475" +checksum = "3f6b4ab509cae251bd524d2425d746b0af0018f5a81fc1eaecdd4e661c8ab3a0" dependencies = [ "globset", "sha2", @@ -6095,7 +6095,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "arrayvec", "log", @@ -7053,7 +7053,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "anyhow", "async-fs", @@ -7089,7 +7089,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "perf", "quote", @@ -8564,7 +8564,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "anyhow", "chrono", @@ -8574,14 +8574,14 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac93432f5b761b22864c774aac244fa5c0fd877678a4c37ebf6cf42208f9c9ec" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" dependencies = [ "tracing", "tracing-subscriber", @@ -8592,7 +8592,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#024485133d7294263604e25c85d65dc71608a29f" +source = "git+https://github.com/zed-industries/zed#20284e4f218081a2c9f90ab78dbb7062e6203725" [[package]] name = "zune-core" diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 70a5306..6080a97 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -15,7 +15,6 @@ use gpui::{ App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task, WeakEntity, }; use nostr_sdk::prelude::*; -use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION}; @@ -426,7 +425,7 @@ impl ChatRegistry { /// Load all rooms from the database. pub fn get_rooms(&mut self, cx: &mut Context) { - let task = self.create_get_rooms_task(cx); + let task = self.get_rooms_from_database(cx); self.tasks.push( // Run and finished in the background @@ -437,7 +436,7 @@ impl ChatRegistry { this.extend_rooms(rooms, cx); this.sort(cx); }) - .expect("Entity has been released"); + .ok(); } Err(e) => { log::error!("Failed to load rooms: {e}") @@ -448,13 +447,10 @@ impl ChatRegistry { } /// Create a task to load rooms from the database - fn create_get_rooms_task(&self, cx: &App) -> Task, Error>> { + fn get_rooms_from_database(&self, cx: &App) -> Task, Error>> { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - // Get the contact bypass setting - let bypass_setting = AppSettings::get_contact_bypass(cx); - cx.background_spawn(async move { let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -508,16 +504,11 @@ impl ChatRegistry { // Check if the user has responded to the room let user_sent = messages.iter().any(|m| m.pubkey == public_key); - // Determine if the room is ongoing or not - let mut bypassed = false; - // Check if public keys are from the user's contacts - if bypass_setting { - bypassed = public_keys.iter().any(|k| contacts.contains(k)); - } + let is_contact = public_keys.iter().any(|k| contacts.contains(k)); // Set the room's kind based on status - if user_sent || bypassed { + if user_sent || is_contact { room = room.kind(RoomKind::Ongoing); } diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index f1e6900..109182f 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -426,7 +426,7 @@ impl ChatPanel { let client = nostr.read(cx).client(); // Get the user's configured NIP96 server - let nip96_server = AppSettings::get_media_server(cx); + let nip96_server = AppSettings::get_file_server(cx); let path = cx.prompt_for_paths(PathPromptOptions { files: true, @@ -594,8 +594,6 @@ impl ChatPanel { text: AnyElement, cx: &Context, ) -> AnyElement { - let hide_avatar = AppSettings::get_hide_user_avatars(cx); - let id = message.id; let author = self.profile(&message.author, cx); let public_key = author.public_key(); @@ -609,6 +607,9 @@ impl ChatPanel { // Check if message is sent successfully let is_sent_success = self.is_sent_success(&id); + // Hide avatar setting + let hide_avatar = AppSettings::get_hide_avatar(cx); + div() .id(ix) .group("") diff --git a/crates/common/src/display.rs b/crates/common/src/display.rs index a435788..3f967ff 100644 --- a/crates/common/src/display.rs +++ b/crates/common/src/display.rs @@ -12,31 +12,19 @@ const SECONDS_IN_MINUTE: i64 = 60; const MINUTES_IN_HOUR: i64 = 60; const HOURS_IN_DAY: i64 = 24; const DAYS_IN_MONTH: i64 = 30; -const FALLBACK_IMG: &str = "https://image.nostr.build/c30703b48f511c293a9003be8100cdad37b8798b77a1dc3ec6eb8a20443d5dea.png"; -const IMAGE_RESIZE_SERVICE: &str = "https://wsrv.nl"; pub trait RenderedProfile { - fn avatar(&self, proxy: bool) -> SharedString; + fn avatar(&self) -> SharedString; fn display_name(&self) -> SharedString; } impl RenderedProfile for Profile { - fn avatar(&self, proxy: bool) -> SharedString { + fn avatar(&self) -> SharedString { self.metadata() .picture .as_ref() .filter(|picture| !picture.is_empty()) - .map(|picture| { - if proxy { - let url = format!( - "{IMAGE_RESIZE_SERVICE}/?url={picture}&w=100&h=100&fit=cover&mask=circle&default={FALLBACK_IMG}&n=-1" - ); - - url.into() - } else { - picture.into() - } - }) + .map(|picture| picture.into()) .unwrap_or_else(|| "brand/avatar.png".into()) } diff --git a/crates/coop/src/new_identity/mod.rs b/crates/coop/src/new_identity/mod.rs index 6d2ad62..ab7daa8 100644 --- a/crates/coop/src/new_identity/mod.rs +++ b/crates/coop/src/new_identity/mod.rs @@ -209,7 +209,7 @@ impl NewAccount { let client = nostr.read(cx).client(); // Get the user's configured NIP96 server - let nip96_server = AppSettings::get_media_server(cx); + let nip96_server = AppSettings::get_file_server(cx); // Open native file dialog let paths = cx.prompt_for_paths(PathPromptOptions { diff --git a/crates/coop/src/sidebar/list_item.rs b/crates/coop/src/sidebar/list_item.rs index c4452a6..a018f38 100644 --- a/crates/coop/src/sidebar/list_item.rs +++ b/crates/coop/src/sidebar/list_item.rs @@ -86,8 +86,8 @@ impl RoomListItem { impl RenderOnce for RoomListItem { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let hide_avatar = AppSettings::get_hide_user_avatars(cx); - let require_screening = AppSettings::get_screening(cx); + let hide_avatar = AppSettings::get_hide_avatar(cx); + let screening = AppSettings::get_screening(cx); let ( Some(public_key), @@ -173,7 +173,7 @@ impl RenderOnce for RoomListItem { .on_click(move |event, window, cx| { handler(event, window, cx); - if kind != RoomKind::Ongoing && require_screening { + if kind != RoomKind::Ongoing && screening { let screening = screening::init(public_key, window, cx); window.open_modal(cx, move |this, _window, _cx| { diff --git a/crates/coop/src/user/mod.rs b/crates/coop/src/user/mod.rs index cb76374..cfee59a 100644 --- a/crates/coop/src/user/mod.rs +++ b/crates/coop/src/user/mod.rs @@ -183,7 +183,7 @@ impl UserProfile { let client = nostr.read(cx).client(); // Get the user's configured NIP96 server - let nip96_server = AppSettings::get_media_server(cx); + let nip96_server = AppSettings::get_file_server(cx); // Open native file dialog let paths = cx.prompt_for_paths(PathPromptOptions { diff --git a/crates/coop/src/views/preferences.rs b/crates/coop/src/views/preferences.rs index 2562221..8c51dee 100644 --- a/crates/coop/src/views/preferences.rs +++ b/crates/coop/src/views/preferences.rs @@ -1,182 +1,21 @@ -use gpui::http_client::Url; -use gpui::{ - div, px, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, SharedString, - Styled, Window, -}; -use settings::AppSettings; -use theme::ActiveTheme; -use ui::button::{Button, ButtonVariants}; -use ui::input::{InputState, TextInput}; -use ui::switch::Switch; -use ui::{h_flex, v_flex, IconName, Sizable, Size, StyledExt}; +use gpui::{div, App, AppContext, Context, Entity, IntoElement, Render, Window}; pub fn init(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Preferences::new(window, cx)) } pub struct Preferences { - media_input: Entity, + // } impl Preferences { - pub fn new(window: &mut Window, cx: &mut App) -> Self { - let media_server = AppSettings::get_media_server(cx).to_string(); - let media_input = cx.new(|cx| { - InputState::new(window, cx) - .default_value(media_server.clone()) - .placeholder(media_server) - }); - - Self { media_input } + pub fn new(_window: &mut Window, _cx: &mut App) -> Self { + Self {} } } impl Render for Preferences { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let auto_auth = AppSettings::get_auto_auth(cx); - let backup = AppSettings::get_backup_messages(cx); - let screening = AppSettings::get_screening(cx); - let bypass = AppSettings::get_contact_bypass(cx); - let proxy = AppSettings::get_proxy_user_avatars(cx); - let hide = AppSettings::get_hide_user_avatars(cx); - - let input_state = self.media_input.downgrade(); - - v_flex() - .child( - v_flex() - .py_2() - .border_t_1() - .border_color(cx.theme().border) - .child( - div() - .text_sm() - .text_color(cx.theme().text_placeholder) - .font_semibold() - .child(SharedString::from("Relay and Media")), - ) - .child( - v_flex() - .my_1() - .gap_1() - .child( - h_flex() - .gap_1() - .child(TextInput::new(&self.media_input).xsmall()) - .child( - Button::new("update") - .icon(IconName::Check) - .ghost() - .with_size(Size::Size(px(26.))) - .on_click(move |_, _window, cx| { - if let Some(input) = input_state.upgrade() { - let Ok(url) = - Url::parse(&input.read(cx).value()) - else { - return; - }; - AppSettings::update_media_server(url, cx); - } - }), - ), - ) - .child( - div() - .text_xs() - .text_color(cx.theme().text_muted) - .child(SharedString::from("Coop currently only supports NIP-96 media servers.")), - ), - ) - .child( - Switch::new("auth") - .label("Automatically authenticate for known relays") - .description("After you approve the authentication request, Coop will automatically complete this step next time.") - .checked(auto_auth) - .on_click(move |_, _window, cx| { - AppSettings::update_auto_auth(!auto_auth, cx); - }), - ), - ) - .child( - v_flex() - .py_2() - .gap_2() - .border_t_1() - .border_color(cx.theme().border) - .child( - div() - .text_sm() - .text_color(cx.theme().text_placeholder) - .font_semibold() - .child(SharedString::from("Messages")), - ) - .child( - v_flex() - .gap_2() - .child( - Switch::new("screening") - .label("Screening") - .description("When opening a chat request, Coop will show a popup to help you verify the sender.") - .checked(screening) - .on_click(move |_, _window, cx| { - AppSettings::update_screening(!screening, cx); - }), - ) - .child( - Switch::new("bypass") - .label("Skip screening for contacts") - .description("Requests from your contacts will automatically go to inbox.") - .checked(bypass) - .on_click(move |_, _window, cx| { - AppSettings::update_contact_bypass(!bypass, cx); - }), - ) - .child( - Switch::new("backup") - .label("Backup messages") - .description("When you send a message, Coop will also forward it to your configured Messaging Relays. Disabling this will cause all messages sent during the current session to disappear when the app is closed.") - .checked(backup) - .on_click(move |_, _window, cx| { - AppSettings::update_backup_messages(!backup, cx); - }), - ), - ), - ) - .child( - v_flex() - .py_2() - .gap_2() - .border_t_1() - .border_color(cx.theme().border) - .child( - div() - .text_sm() - .text_color(cx.theme().text_placeholder) - .font_semibold() - .child(SharedString::from("Display")), - ) - .child( - v_flex() - .gap_2() - .child( - Switch::new("hide_avatar") - .label("Hide user avatars") - .description("Unload all avatar pictures to improve performance and reduce memory usage.") - .checked(hide) - .on_click(move |_, _window, cx| { - AppSettings::update_hide_user_avatars(!hide, cx); - }), - ) - .child( - Switch::new("proxy_avatar") - .label("Proxy user avatars") - .description("Use wsrv.nl to resize and downscale avatar pictures (saves ~50MB of data).") - .checked(proxy) - .on_click(move |_, _window, cx| { - AppSettings::update_proxy_user_avatars(!proxy, cx); - }), - ), - ), - ) + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + div() } } diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/views/screening.rs index 0de27eb..bc0705f 100644 --- a/crates/coop/src/views/screening.rs +++ b/crates/coop/src/views/screening.rs @@ -207,7 +207,7 @@ impl Screening { .hover(|this| { this.bg(cx.theme().elevated_surface_background) }) - .child(Avatar::new(contact.avatar(true)).size(rems(1.75))) + .child(Avatar::new(contact.avatar()).size(rems(1.75))) .child(contact.display_name()), ); } diff --git a/crates/relay_auth/src/lib.rs b/crates/relay_auth/src/lib.rs index 9fcb681..b753261 100644 --- a/crates/relay_auth/src/lib.rs +++ b/crates/relay_auth/src/lib.rs @@ -10,7 +10,7 @@ use gpui::{ Subscription, Task, Window, }; use nostr_sdk::prelude::*; -use settings::AppSettings; +use settings::{AppSettings, AuthMode}; use smallvec::{smallvec, SmallVec}; use state::{tracker, NostrRegistry}; use theme::ActiveTheme; @@ -93,12 +93,12 @@ impl RelayAuth { // Observe the current state cx.observe_in(&entity, window, |this, _, window, cx| { let settings = AppSettings::global(cx); - let auto_auth = AppSettings::get_auto_auth(cx); + let mode = AppSettings::get_auth_mode(cx); for req in this.requests.clone().into_iter() { - let is_authenticated = settings.read(cx).is_authenticated(&req.url); + let is_trusted_relay = settings.read(cx).is_trusted_relay(&req.url, cx); - if auto_auth && is_authenticated { + if is_trusted_relay && mode == AuthMode::Auto { // Automatically authenticate if the relay is authenticated before this.response(req, window, cx); } else { @@ -250,7 +250,7 @@ impl RelayAuth { // Save the authenticated relay to automatically authenticate future requests settings.update(cx, |this, cx| { - this.push_relay(&url, cx); + this.add_trusted_relay(url, cx); }); // Remove the challenge from the list of pending authentications diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 900c100..013880c 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -1,3 +1,5 @@ +use std::collections::{HashMap, HashSet}; + use anyhow::{anyhow, Error}; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task}; use nostr_sdk::prelude::*; @@ -17,12 +19,12 @@ macro_rules! setting_accessors { $( paste::paste! { pub fn [](cx: &App) -> $type { - Self::global(cx).read(cx).setting_values.$field.clone() + Self::global(cx).read(cx).values.$field.clone() } pub fn [](value: $type, cx: &mut App) { Self::global(cx).update(cx, |this, cx| { - this.setting_values.$field = value; + this.values.$field = value; cx.notify(); }); } @@ -33,41 +35,69 @@ macro_rules! setting_accessors { } setting_accessors! { - pub media_server: Url, - pub proxy_user_avatars: bool, - pub hide_user_avatars: bool, - pub backup_messages: bool, + pub hide_avatar: bool, pub screening: bool, - pub contact_bypass: bool, - pub auto_login: bool, - pub auto_auth: bool, + pub auth_mode: AuthMode, + pub trusted_relays: HashSet, + pub room_configs: HashMap, + pub file_server: Url, } +/// Authentication mode +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum AuthMode { + #[default] + Manual, + Auto, +} + +/// Signer kind +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub enum SignerKind { + #[default] + Auto, + User, + Device, +} + +/// Room configuration +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] +pub struct RoomConfig { + backup: bool, + signer_kind: SignerKind, +} + +/// Settings #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Settings { - pub media_server: Url, - pub proxy_user_avatars: bool, - pub hide_user_avatars: bool, - pub backup_messages: bool, + /// Hide user avatars + pub hide_avatar: bool, + + /// Enable screening for unknown chat requests pub screening: bool, - pub contact_bypass: bool, - pub auto_login: bool, - pub auto_auth: bool, - pub authenticated_relays: Vec, + + /// Authentication mode + pub auth_mode: AuthMode, + + /// Trusted relays; Coop will automatically authenticate with these relays + pub trusted_relays: HashSet, + + /// Configuration for each chat room + pub room_configs: HashMap, + + /// File server for NIP-96 media attachments + pub file_server: Url, } impl Default for Settings { fn default() -> Self { Self { - media_server: Url::parse("https://nostrmedia.com").unwrap(), - proxy_user_avatars: true, - hide_user_avatars: false, - backup_messages: true, + hide_avatar: false, screening: true, - contact_bypass: true, - auto_login: false, - auto_auth: true, - authenticated_relays: vec![], + auth_mode: AuthMode::default(), + trusted_relays: HashSet::default(), + room_configs: HashMap::default(), + file_server: Url::parse("https://nostrmedia.com").unwrap(), } } } @@ -82,29 +112,31 @@ struct GlobalAppSettings(Entity); impl Global for GlobalAppSettings {} +/// Application settings pub struct AppSettings { - setting_values: Settings, + /// Settings + values: Settings, - // Event subscriptions + /// Event subscriptions _subscriptions: SmallVec<[Subscription; 1]>, - // Background tasks + /// Background tasks _tasks: SmallVec<[Task<()>; 1]>, } impl AppSettings { - /// Retrieve the Global Settings instance + /// Retrieve the global settings instance pub fn global(cx: &App) -> Entity { cx.global::().0.clone() } - /// Set the Global Settings instance - pub(crate) fn set_global(state: Entity, cx: &mut App) { + /// Set the global settings instance + fn set_global(state: Entity, cx: &mut App) { cx.set_global(GlobalAppSettings(state)); } fn new(cx: &mut Context) -> Self { - let load_settings = Self::_load_settings(false, cx); + let load_settings = Self::get_from_database(false, cx); let mut tasks = smallvec![]; let mut subscriptions = smallvec![]; @@ -112,7 +144,7 @@ impl AppSettings { subscriptions.push( // Observe and automatically save settings on changes cx.observe_self(|this, cx| { - this.set_settings(cx); + this.save(cx); }), ); @@ -121,7 +153,7 @@ impl AppSettings { cx.spawn(async move |this, cx| { if let Ok(settings) = load_settings.await { this.update(cx, |this, cx| { - this.setting_values = settings; + this.values = settings; cx.notify(); }) .ok(); @@ -130,42 +162,32 @@ impl AppSettings { ); Self { - setting_values: Settings::default(), + values: Settings::default(), _subscriptions: subscriptions, _tasks: tasks, } } - pub fn load_settings(&mut self, cx: &mut Context) { - let task = Self::_load_settings(true, cx); - - self._tasks.push( - // Run task in the background - cx.spawn(async move |this, cx| { - if let Ok(settings) = task.await { - this.update(cx, |this, cx| { - this.setting_values = settings; - cx.notify(); - }) - .ok(); - } - }), - ); - } - - fn _load_settings(user: bool, cx: &App) -> Task> { + /// Get settings from the database + /// + /// 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> { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); cx.background_spawn(async move { + // Construct a filter to get the latest settings let mut filter = Filter::new() .kind(Kind::ApplicationSpecificData) .identifier(SETTINGS_IDENTIFIER) .limit(1); - if user { + if current_user { let signer = client.signer().await?; let public_key = signer.get_public_key().await?; + + // Push author to the filter filter = filter.author(public_key); } @@ -177,11 +199,30 @@ impl AppSettings { }) } - pub fn set_settings(&mut self, cx: &mut Context) { + /// Load settings + pub fn load(&mut self, cx: &mut Context) { + let task = Self::get_from_database(true, cx); + + self._tasks.push( + // Run task in the background + cx.spawn(async move |this, cx| { + if let Ok(settings) = task.await { + this.update(cx, |this, cx| { + this.values = settings; + cx.notify(); + }) + .ok(); + } + }), + ); + } + + /// Save settings + pub fn save(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - if let Ok(content) = serde_json::to_string(&self.setting_values) { + if let Ok(content) = serde_json::to_string(&self.values) { let task: Task> = cx.background_spawn(async move { let signer = client.signer().await?; let public_key = signer.get_public_key().await?; @@ -201,23 +242,24 @@ impl AppSettings { } } - /// Check if auto authentication is enabled - pub fn is_auto_auth(&self) -> bool { - !self.setting_values.authenticated_relays.is_empty() && self.setting_values.auto_auth + /// Check if the given relay is trusted + pub fn is_trusted_relay(&self, url: &RelayUrl, _cx: &App) -> bool { + self.values.trusted_relays.contains(url) } - /// Check if a relay is authenticated - pub fn is_authenticated(&self, url: &RelayUrl) -> bool { - self.setting_values.authenticated_relays.contains(url) + /// Add a relay to the trusted list + pub fn add_trusted_relay(&mut self, url: RelayUrl, cx: &mut Context) { + self.values.trusted_relays.insert(url); + cx.notify(); } - /// Push a relay to the authenticated relays list - pub fn push_relay(&mut self, relay_url: &RelayUrl, cx: &mut Context) { - if !self.is_authenticated(relay_url) { - self.setting_values - .authenticated_relays - .push(relay_url.to_owned()); - cx.notify(); - } + /// Add a room configuration + pub fn add_room_config(&mut self, id: u64, config: RoomConfig, cx: &mut Context) { + self.values + .room_configs + .entry(id) + .and_modify(|this| *this = config) + .or_default(); + cx.notify(); } }