fix
This commit is contained in:
@@ -360,7 +360,6 @@ impl ChatRegistry {
|
|||||||
// Subscribe
|
// Subscribe
|
||||||
client
|
client
|
||||||
.subscribe(vec![msg_relays, contact_list])
|
.subscribe(vec![msg_relays, contact_list])
|
||||||
.with_id(SubscriptionId::new("user-meta"))
|
|
||||||
.close_on(opts)
|
.close_on(opts)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -622,7 +621,13 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Load all rooms from the database.
|
/// Load all rooms from the database.
|
||||||
pub fn get_rooms(&mut self, cx: &mut Context<Self>) {
|
pub fn get_rooms(&mut self, cx: &mut Context<Self>) {
|
||||||
let task = self.get_rooms_from_database(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
|
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let task = self.get_rooms_from_database(public_key, cx);
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
match task.await {
|
match task.await {
|
||||||
@@ -642,14 +647,14 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a task to load rooms from the database
|
/// Create a task to load rooms from the database
|
||||||
fn get_rooms_from_database(&self, cx: &App) -> Task<Result<HashSet<Room>, Error>> {
|
fn get_rooms_from_database(
|
||||||
|
&self,
|
||||||
|
public_key: PublicKey,
|
||||||
|
cx: &App,
|
||||||
|
) -> Task<Result<HashSet<Room>, Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
|
|
||||||
return Task::ready(Err(anyhow!("Signer is required")));
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
// Get contacts
|
// Get contacts
|
||||||
let contacts = client
|
let contacts = client
|
||||||
|
|||||||
@@ -618,11 +618,11 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ChangeSigner(kind) => {
|
Command::ChangeSigner(kind) => {
|
||||||
let is_nip4e_enabled = AppSettings::get_encryption_key(cx);
|
let settings = AppSettings::global(cx);
|
||||||
|
let is_nip4e_enabled = settings.read(cx).is_nip4e_enabled(cx);
|
||||||
|
let is_force_nip4e = *kind == SignerKind::Encryption || *kind == SignerKind::Auto;
|
||||||
|
|
||||||
if !is_nip4e_enabled
|
if !is_nip4e_enabled && is_force_nip4e {
|
||||||
&& (*kind == SignerKind::Encryption || *kind == SignerKind::Auto)
|
|
||||||
{
|
|
||||||
window.push_notification("Decoupling Encryption Key is not enabled", cx);
|
window.push_notification("Decoupling Encryption Key is not enabled", cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use nostr_sdk::prelude::*;
|
|||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::{Announcement, CLIENT_NAME, NostrRegistry, StateEvent};
|
use state::{Announcement, CLIENT_NAME, NostrRegistry};
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::Button;
|
use ui::button::Button;
|
||||||
@@ -91,6 +91,7 @@ impl DeviceRegistry {
|
|||||||
/// Create a new device registry instance
|
/// Create a new device registry instance
|
||||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let signer = cx.new(|_| None);
|
let signer = cx.new(|_| None);
|
||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let user_signer = nostr.read(cx).signer.clone();
|
let user_signer = nostr.read(cx).signer.clone();
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ impl DeviceRegistry {
|
|||||||
);
|
);
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Subscribe to nostr state events
|
// Observe the user signer
|
||||||
cx.observe(&user_signer, move |this, signer, cx| {
|
cx.observe(&user_signer, move |this, signer, cx| {
|
||||||
if signer.read(cx).is_some() && is_nip4e_enabled {
|
if signer.read(cx).is_some() && is_nip4e_enabled {
|
||||||
this.get_announcement(cx);
|
this.get_announcement(cx);
|
||||||
@@ -133,14 +134,11 @@ impl DeviceRegistry {
|
|||||||
fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn handle_notifications(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
let current_user = nostr.read(cx).signer_pubkey(cx);
|
||||||
|
|
||||||
let announcement_existed = self.announcement_existed.clone();
|
let announcement_existed = self.announcement_existed.clone();
|
||||||
let (tx, rx) = flume::bounded::<Event>(100);
|
let (tx, rx) = flume::bounded::<Event>(100);
|
||||||
|
|
||||||
let Some(current_user) = nostr.read(cx).signer_pubkey(cx) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tasks.push(cx.background_spawn(async move {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
@@ -155,21 +153,15 @@ impl DeviceRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::Custom(10044) => {
|
Kind::Custom(10044) if current_user == Some(event.pubkey) => {
|
||||||
if current_user == event.pubkey {
|
announcement_existed.store(true, Ordering::Relaxed);
|
||||||
announcement_existed.store(true, Ordering::Relaxed);
|
tx.send_async(event.into_owned()).await?;
|
||||||
tx.send_async(event.into_owned()).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Kind::Custom(4454) => {
|
Kind::Custom(4454) if current_user == Some(event.pubkey) => {
|
||||||
if current_user == event.pubkey {
|
tx.send_async(event.into_owned()).await?;
|
||||||
tx.send_async(event.into_owned()).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Kind::Custom(4455) => {
|
Kind::Custom(4455) if current_user == Some(event.pubkey) => {
|
||||||
if current_user == event.pubkey {
|
tx.send_async(event.into_owned()).await?;
|
||||||
tx.send_async(event.into_owned()).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -426,14 +418,16 @@ impl DeviceRegistry {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
let app_keys = Keys::generate();
|
|
||||||
let app_pubkey = app_keys.public_key();
|
|
||||||
|
|
||||||
let Some(signer) = nostr.read(cx).signer(cx) else {
|
let Some(signer) = nostr.read(cx).signer(cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Ok(app_keys) = get_or_init_app_keys(cx) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let task: Task<Result<Option<Event>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Option<Event>, Error>> = cx.background_spawn(async move {
|
||||||
|
let app_pubkey = app_keys.public_key();
|
||||||
let public_key = signer.get_public_key_async().await?;
|
let public_key = signer.get_public_key_async().await?;
|
||||||
|
|
||||||
// Construct a filter to get the latest approval event
|
// Construct a filter to get the latest approval event
|
||||||
@@ -516,8 +510,9 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
/// Parse the approval event to get encryption key then set it
|
/// Parse the approval event to get encryption key then set it
|
||||||
fn extract_encryption(&mut self, event: Event, cx: &mut Context<Self>) {
|
fn extract_encryption(&mut self, event: Event, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let Ok(app_keys) = get_or_init_app_keys(cx) else {
|
||||||
let app_keys = Keys::generate();
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Keys, Error>> = cx.background_spawn(async move {
|
||||||
let master = event
|
let master = event
|
||||||
@@ -744,6 +739,35 @@ impl DeviceRegistry {
|
|||||||
|
|
||||||
struct DeviceNotification;
|
struct DeviceNotification;
|
||||||
|
|
||||||
|
/// Get or create new app keys
|
||||||
|
fn get_or_init_app_keys(cx: &App) -> Result<Keys, Error> {
|
||||||
|
let read = cx.read_credentials(CLIENT_NAME);
|
||||||
|
let stored_keys: Option<Keys> = cx.foreground_executor().block_on(async move {
|
||||||
|
if let Ok(Some((_, secret))) = read.await {
|
||||||
|
SecretKey::from_slice(&secret).map(Keys::new).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(keys) = stored_keys {
|
||||||
|
Ok(keys)
|
||||||
|
} else {
|
||||||
|
let keys = Keys::generate();
|
||||||
|
let user = keys.public_key().to_hex();
|
||||||
|
let secret = keys.secret_key().to_secret_bytes();
|
||||||
|
let write = cx.write_credentials(CLIENT_NAME, &user, &secret);
|
||||||
|
|
||||||
|
cx.foreground_executor().block_on(async move {
|
||||||
|
if let Err(e) = write.await {
|
||||||
|
log::error!("Keyring not available or panic: {e}")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Encrypt and store device keys in the local database.
|
/// Encrypt and store device keys in the local database.
|
||||||
async fn set_keys(client: &Client, signer: &Keys, secret: &str) -> Result<(), Error> {
|
async fn set_keys(client: &Client, signer: &Keys, secret: &str) -> Result<(), Error> {
|
||||||
let public_key = signer.get_public_key_async().await?;
|
let public_key = signer.get_public_key_async().await?;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -42,10 +41,9 @@ setting_accessors! {
|
|||||||
pub theme_mode: ThemeMode,
|
pub theme_mode: ThemeMode,
|
||||||
pub hide_avatar: bool,
|
pub hide_avatar: bool,
|
||||||
pub screening: bool,
|
pub screening: bool,
|
||||||
pub encryption_key: bool,
|
pub nip4e: bool,
|
||||||
pub auth_mode: AuthMode,
|
pub auth_mode: AuthMode,
|
||||||
pub trusted_relays: HashSet<RelayUrl>,
|
pub trusted_relays: Vec<String>,
|
||||||
pub room_configs: HashMap<u64, RoomConfig>,
|
|
||||||
pub file_server: Url,
|
pub file_server: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,18 +139,13 @@ pub struct Settings {
|
|||||||
pub screening: bool,
|
pub screening: bool,
|
||||||
|
|
||||||
/// Enable decoupling encryption key
|
/// Enable decoupling encryption key
|
||||||
///
|
pub nip4e: bool,
|
||||||
/// NIP-4e
|
|
||||||
pub encryption_key: bool,
|
|
||||||
|
|
||||||
/// Authentication mode
|
/// Authentication mode
|
||||||
pub auth_mode: AuthMode,
|
pub auth_mode: AuthMode,
|
||||||
|
|
||||||
/// Trusted relays; Coop will automatically authenticate with these relays
|
/// Trusted relays; Coop will automatically authenticate with these relays
|
||||||
pub trusted_relays: HashSet<RelayUrl>,
|
pub trusted_relays: Vec<String>,
|
||||||
|
|
||||||
/// Configuration for each chat room
|
|
||||||
pub room_configs: HashMap<u64, RoomConfig>,
|
|
||||||
|
|
||||||
/// Server for blossom media attachments
|
/// Server for blossom media attachments
|
||||||
pub file_server: Url,
|
pub file_server: Url,
|
||||||
@@ -165,10 +158,9 @@ impl Default for Settings {
|
|||||||
theme_mode: ThemeMode::default(),
|
theme_mode: ThemeMode::default(),
|
||||||
hide_avatar: false,
|
hide_avatar: false,
|
||||||
screening: true,
|
screening: true,
|
||||||
encryption_key: false,
|
nip4e: false,
|
||||||
auth_mode: AuthMode::default(),
|
auth_mode: AuthMode::default(),
|
||||||
trusted_relays: HashSet::default(),
|
trusted_relays: vec![],
|
||||||
room_configs: HashMap::default(),
|
|
||||||
file_server: Url::parse("https://blossom.band/").unwrap(),
|
file_server: Url::parse("https://blossom.band/").unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,21 +307,29 @@ impl AppSettings {
|
|||||||
|
|
||||||
/// Check if decoupling encryption key is enabled
|
/// Check if decoupling encryption key is enabled
|
||||||
pub fn is_nip4e_enabled(&self, cx: &App) -> bool {
|
pub fn is_nip4e_enabled(&self, cx: &App) -> bool {
|
||||||
self.inner.read(cx).encryption_key
|
self.inner.read(cx).nip4e
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the given relay is already authenticated
|
/// Check if the given relay is already authenticated
|
||||||
pub fn trusted_relay(&self, url: &RelayUrl, cx: &App) -> bool {
|
pub fn trusted_relay(&self, url: &RelayUrl, cx: &App) -> bool {
|
||||||
self.inner.read(cx).trusted_relays.iter().any(|relay| {
|
self.inner
|
||||||
relay.as_str_without_trailing_slash() == url.as_str_without_trailing_slash()
|
.read(cx)
|
||||||
})
|
.trusted_relays
|
||||||
|
.iter()
|
||||||
|
.any(|relay| relay == url.as_str_without_trailing_slash())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a relay to the trusted list
|
/// Add a relay to the trusted list
|
||||||
pub fn add_trusted_relay(&mut self, url: &RelayUrl, cx: &mut Context<Self>) {
|
pub fn add_trusted_relay(&mut self, url: &RelayUrl, cx: &mut Context<Self>) {
|
||||||
self.inner.update(cx, |this, cx| {
|
self.inner.update(cx, |this, cx| {
|
||||||
this.trusted_relays.insert(url.clone());
|
if !this
|
||||||
cx.notify();
|
.trusted_relays
|
||||||
|
.iter()
|
||||||
|
.any(|relay| relay == url.as_str_without_trailing_slash())
|
||||||
|
{
|
||||||
|
this.trusted_relays.push(url.to_string());
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,12 +98,7 @@ impl NostrRegistry {
|
|||||||
let client = ClientBuilder::default()
|
let client = ClientBuilder::default()
|
||||||
.database(lmdb)
|
.database(lmdb)
|
||||||
.gossip(NostrGossipMemory::unbounded())
|
.gossip(NostrGossipMemory::unbounded())
|
||||||
.gossip_config(
|
.gossip_config(GossipConfig::default().no_background_refresh())
|
||||||
GossipConfig::default()
|
|
||||||
.sync_initial_timeout(Duration::from_millis(100))
|
|
||||||
.sync_idle_timeout(Duration::from_millis(100))
|
|
||||||
.no_background_refresh(),
|
|
||||||
)
|
|
||||||
.connect_timeout(Duration::from_secs(10))
|
.connect_timeout(Duration::from_secs(10))
|
||||||
.sleep_when_idle(SleepWhenIdle::Enabled {
|
.sleep_when_idle(SleepWhenIdle::Enabled {
|
||||||
timeout: Duration::from_secs(600),
|
timeout: Duration::from_secs(600),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use state::NostrRegistry;
|
|||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::input::{Input, InputEvent, InputState};
|
use ui::input::{Input, InputEvent, InputState};
|
||||||
use ui::{Disableable, v_flex};
|
use ui::{Disableable, WindowExtension, v_flex};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ImportIdentity {
|
pub struct ImportIdentity {
|
||||||
@@ -74,6 +74,7 @@ impl ImportIdentity {
|
|||||||
nostr.update(cx, |this, cx| {
|
nostr.update(cx, |this, cx| {
|
||||||
this.set_signer(keys, cx);
|
this.set_signer(keys, cx);
|
||||||
});
|
});
|
||||||
|
window.close_modal(cx);
|
||||||
} else {
|
} else {
|
||||||
self.set_error("Invalid key", cx);
|
self.set_error("Invalid key", cx);
|
||||||
}
|
}
|
||||||
@@ -109,9 +110,10 @@ impl ImportIdentity {
|
|||||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
match task.await {
|
match task.await {
|
||||||
Ok(keys) => {
|
Ok(keys) => {
|
||||||
nostr.update(cx, |this, cx| {
|
nostr.update_in(cx, |this, window, cx| {
|
||||||
this.set_signer(keys, cx);
|
this.set_signer(keys, cx);
|
||||||
});
|
window.close_modal(cx);
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ impl Render for Preferences {
|
|||||||
|
|
||||||
let screening = AppSettings::get_screening(cx);
|
let screening = AppSettings::get_screening(cx);
|
||||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
let nip4e = AppSettings::get_encryption_key(cx);
|
let nip4e = AppSettings::get_nip4e(cx);
|
||||||
let auth_mode = AppSettings::get_auth_mode(cx);
|
let auth_mode = AppSettings::get_auth_mode(cx);
|
||||||
let theme_mode = AppSettings::get_theme_mode(cx);
|
let theme_mode = AppSettings::get_theme_mode(cx);
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ impl Render for Preferences {
|
|||||||
.description(NIP4E)
|
.description(NIP4E)
|
||||||
.checked(nip4e)
|
.checked(nip4e)
|
||||||
.on_click(move |_, _window, cx| {
|
.on_click(move |_, _window, cx| {
|
||||||
AppSettings::update_encryption_key(!nip4e, cx);
|
AppSettings::update_nip4e(!nip4e, cx);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -659,7 +659,8 @@ impl Workspace {
|
|||||||
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let trash_messages = chat.read(cx).count_trash_messages(cx);
|
let trash_messages = chat.read(cx).count_trash_messages(cx);
|
||||||
let is_nip4e_enabled = AppSettings::get_encryption_key(cx);
|
|
||||||
|
let is_nip4e_enabled = AppSettings::get_nip4e(cx);
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
|
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
|
||||||
|
|||||||
Reference in New Issue
Block a user