refactor encryption dropdown menu
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m43s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled

This commit is contained in:
2026-02-28 15:22:46 +07:00
parent 2423cdca19
commit 635c500233
6 changed files with 207 additions and 75 deletions

View File

@@ -65,3 +65,7 @@ webbrowser.workspace = true
indexset = "0.12.3"
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
[target.'cfg(target_os = "macos")'.dependencies]
# Temporary workaround https://github.com/zed-industries/zed/issues/47168
core-text = "=21.0.0"

View File

@@ -2,6 +2,7 @@ use std::sync::Arc;
use ::settings::AppSettings;
use chat::{ChatEvent, ChatRegistry, InboxState};
use device::DeviceRegistry;
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement,
@@ -19,6 +20,7 @@ use ui::dock_area::dock::DockPlacement;
use ui::dock_area::panel::PanelView;
use ui::dock_area::{ClosePanel, DockArea, DockItem};
use ui::menu::{DropdownMenu, PopupMenuItem};
use ui::notification::Notification;
use ui::{h_flex, v_flex, IconName, Root, Sizable, WindowExtension};
use crate::dialogs::settings;
@@ -27,6 +29,13 @@ use crate::panels::{
};
use crate::sidebar;
const ENC_MSG: &str =
"Encryption Key is a special key that used to encrypt and decrypt your messages. \
Your identity is completely decoupled from all encryption processes to protect your privacy.";
const ENC_WARN: &str = "By resetting your encryption key, you will lose access to \
all your encrypted messages before. This action cannot be undone.";
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
cx.new(|cx| Workspace::new(window, cx))
}
@@ -36,8 +45,10 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
enum Command {
ToggleTheme,
RefreshEncryption,
RefreshRelayList,
RefreshMessagingRelays,
ResetEncryption,
ShowRelayList,
ShowMessaging,
@@ -273,12 +284,21 @@ impl Workspace {
);
});
}
Command::RefreshEncryption => {
let device = DeviceRegistry::global(cx);
device.update(cx, |this, cx| {
this.get_announcement(cx);
});
}
Command::RefreshRelayList => {
let nostr = NostrRegistry::global(cx);
nostr.update(cx, |this, cx| {
this.ensure_relay_list(cx);
});
}
Command::ResetEncryption => {
self.confirm_reset_encryption(window, cx);
}
Command::RefreshMessagingRelays => {
let chat = ChatRegistry::global(cx);
chat.update(cx, |this, cx| {
@@ -291,6 +311,54 @@ impl Workspace {
}
}
fn confirm_reset_encryption(&mut self, window: &mut Window, cx: &mut Context<Self>) {
window.open_modal(cx, |this, _window, cx| {
this.confirm()
.show_close(true)
.title("Reset Encryption Keys")
.child(
v_flex()
.gap_1()
.text_sm()
.child(SharedString::from(ENC_MSG))
.child(
div()
.italic()
.text_color(cx.theme().warning_active)
.child(SharedString::from(ENC_WARN)),
),
)
.on_ok(move |_ev, window, cx| {
let device = DeviceRegistry::global(cx);
let task = device.read(cx).create_encryption(cx);
window
.spawn(cx, async move |cx| {
let result = task.await;
cx.update(|window, cx| match result {
Ok(keys) => {
device.update(cx, |this, cx| {
this.set_signer(keys, cx);
this.listen_request(cx);
});
window.close_modal(cx);
}
Err(e) => {
window
.push_notification(Notification::error(e.to_string()), cx);
}
})
.ok();
})
.detach();
// false to keep modal open
false
})
});
}
fn theme_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
window.open_modal(cx, move |this, _window, cx| {
let registry = ThemeRegistry::global(cx);
@@ -471,8 +539,44 @@ impl Workspace {
.tooltip("Decoupled encryption key")
.small()
.ghost()
.on_click(|_ev, window, cx| {
window.dispatch_action(Box::new(Command::ShowEncryption), cx);
.dropdown_menu(move |this, _window, cx| {
let device = DeviceRegistry::global(cx);
let state = device.read(cx).state();
this.min_w(px(260.))
.item(
PopupMenuItem::element(move |_window, _cx| {
h_flex()
.px_1()
.w_full()
.gap_2()
.text_sm()
.child(
div()
.size_1p5()
.rounded_full()
.when(state.set(), |this| this.bg(gpui::green()))
.when(state.requesting(), |this| {
this.bg(gpui::yellow())
}),
)
.child(SharedString::from(state.to_string()))
})
.on_click(|_ev, window, cx| {
window.dispatch_action(Box::new(Command::ShowEncryption), cx);
}),
)
.separator()
.menu_with_icon(
"Reload",
IconName::Refresh,
Box::new(Command::RefreshEncryption),
)
.menu_with_icon(
"Reset",
IconName::Warning,
Box::new(Command::ResetEncryption),
)
}),
)
.child(

View File

@@ -151,7 +151,7 @@ impl DeviceRegistry {
}
/// Set the decoupled encryption key for the current user
fn set_signer<S>(&mut self, new: S, cx: &mut Context<Self>)
pub fn set_signer<S>(&mut self, new: S, cx: &mut Context<Self>)
where
S: NostrSigner + 'static,
{
@@ -242,7 +242,7 @@ impl DeviceRegistry {
}
/// Get device announcement for current user
fn get_announcement(&mut self, cx: &mut Context<Self>) {
pub fn get_announcement(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
@@ -307,8 +307,8 @@ impl DeviceRegistry {
}));
}
/// Create a new device signer and announce it
fn announce(&mut self, cx: &mut Context<Self>) {
/// Create new encryption keys
pub fn create_encryption(&self, cx: &App) -> Task<Result<Keys, Error>> {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
@@ -323,7 +323,7 @@ impl DeviceRegistry {
let secret = keys.secret_key().to_secret_hex();
let n = keys.public_key();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
cx.background_spawn(async move {
let urls = write_relays.await;
// Construct an announcement event
@@ -340,23 +340,29 @@ impl DeviceRegistry {
// Save device keys to the database
set_keys(&client, &secret).await?;
Ok(())
});
Ok(keys)
})
}
/// Create a new device signer and announce it
fn announce(&mut self, cx: &mut Context<Self>) {
let task = self.create_encryption(cx);
self.tasks.push(cx.spawn(async move |this, cx| {
if task.await.is_ok() {
this.update(cx, |this, cx| {
this.set_signer(keys, cx);
this.listen_request(cx);
})?;
}
let keys = task.await?;
// Update signer
this.update(cx, |this, cx| {
this.set_signer(keys, cx);
this.listen_request(cx);
})?;
Ok(())
}));
}
/// Initialize device signer (decoupled encryption key) for the current user
fn new_signer(&mut self, event: &Event, cx: &mut Context<Self>) {
pub fn new_signer(&mut self, event: &Event, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
@@ -375,31 +381,29 @@ impl DeviceRegistry {
}
});
cx.spawn(async move |this, cx| {
self.tasks.push(cx.spawn(async move |this, cx| {
match task.await {
Ok(keys) => {
this.update(cx, |this, cx| {
this.set_signer(keys, cx);
this.listen_request(cx);
})
.ok();
})?;
}
Err(e) => {
log::warn!("Failed to initialize device signer: {e}");
this.update(cx, |this, cx| {
this.request(cx);
this.listen_approval(cx);
})
.ok();
log::warn!("Failed to initialize device signer: {e}");
})?;
}
};
})
.detach();
Ok(())
}));
}
/// Listen for device key requests on user's write relays
fn listen_request(&mut self, cx: &mut Context<Self>) {
pub fn listen_request(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();

View File

@@ -1,3 +1,5 @@
use std::fmt::Display;
use gpui::SharedString;
use nostr_sdk::prelude::*;
@@ -9,6 +11,16 @@ pub enum DeviceState {
Set,
}
impl Display for DeviceState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeviceState::Idle => write!(f, "Idle"),
DeviceState::Requesting => write!(f, "Wait for approval"),
DeviceState::Set => write!(f, "Encryption Key is ready"),
}
}
}
impl DeviceState {
pub fn idle(&self) -> bool {
matches!(self, DeviceState::Idle)