accounts.rs (16544B)
1 use uuid::Uuid; 2 3 use crate::account::cache::AccountCache; 4 use crate::account::contacts::Contacts; 5 use crate::account::mute::AccountMutedData; 6 use crate::account::relay::{ 7 modify_advertised_relays, update_relay_configuration, AccountRelayData, RelayAction, 8 RelayDefaults, 9 }; 10 use crate::storage::AccountStorageWriter; 11 use crate::user_account::UserAccountSerializable; 12 use crate::{ 13 AccountStorage, MuteFun, SingleUnkIdAction, UnifiedSubscription, UnknownIds, UserAccount, 14 ZapWallet, 15 }; 16 use enostr::{ClientMessage, FilledKeypair, Keypair, Pubkey, RelayPool}; 17 use nostrdb::{Ndb, Note, Transaction}; 18 19 // TODO: remove this 20 use std::sync::Arc; 21 22 /// The interface for managing the user's accounts. 23 /// Represents all user-facing operations related to account management. 24 pub struct Accounts { 25 pub cache: AccountCache, 26 storage_writer: Option<AccountStorageWriter>, 27 relay_defaults: RelayDefaults, 28 subs: AccountSubs, 29 } 30 31 impl Accounts { 32 #[allow(clippy::too_many_arguments)] 33 pub fn new( 34 key_store: Option<AccountStorage>, 35 forced_relays: Vec<String>, 36 fallback: Pubkey, 37 ndb: &mut Ndb, 38 txn: &Transaction, 39 pool: &mut RelayPool, 40 ctx: &egui::Context, 41 unknown_ids: &mut UnknownIds, 42 ) -> Self { 43 let (mut cache, unknown_id) = AccountCache::new(UserAccount::new( 44 Keypair::only_pubkey(fallback), 45 AccountData::new(fallback.bytes()), 46 )); 47 48 unknown_id.process_action(unknown_ids, ndb, txn); 49 50 let mut storage_writer = None; 51 if let Some(keystore) = key_store { 52 let (reader, writer) = keystore.rw(); 53 match reader.get_accounts() { 54 Ok(accounts) => { 55 for account in accounts { 56 add_account_from_storage(&mut cache, account).process_action( 57 unknown_ids, 58 ndb, 59 txn, 60 ) 61 } 62 } 63 Err(e) => { 64 tracing::error!("could not get keys: {e}"); 65 } 66 } 67 if let Some(selected) = reader.get_selected_key().ok().flatten() { 68 cache.select(selected); 69 } 70 71 storage_writer = Some(writer); 72 }; 73 74 let relay_defaults = RelayDefaults::new(forced_relays); 75 76 let selected = cache.selected_mut(); 77 let selected_data = &mut selected.data; 78 79 selected_data.query(ndb, txn); 80 81 let subs = { 82 AccountSubs::new( 83 ndb, 84 pool, 85 &relay_defaults, 86 &selected.key.pubkey, 87 selected_data, 88 create_wakeup(ctx), 89 ) 90 }; 91 92 Accounts { 93 cache, 94 storage_writer, 95 relay_defaults, 96 subs, 97 } 98 } 99 100 pub fn remove_account( 101 &mut self, 102 pk: &Pubkey, 103 ndb: &mut Ndb, 104 pool: &mut RelayPool, 105 ctx: &egui::Context, 106 ) -> bool { 107 let Some(resp) = self.cache.remove(pk) else { 108 return false; 109 }; 110 111 if pk != self.cache.fallback() { 112 if let Some(key_store) = &self.storage_writer { 113 if let Err(e) = key_store.remove_key(&resp.deleted) { 114 tracing::error!("Could not remove account {pk}: {e}"); 115 } 116 } 117 } 118 119 if let Some(swap_to) = resp.swap_to { 120 let txn = Transaction::new(ndb).expect("txn"); 121 self.select_account_internal(&swap_to, ndb, &txn, pool, ctx); 122 } 123 124 true 125 } 126 127 pub fn contains_full_kp(&self, pubkey: &enostr::Pubkey) -> bool { 128 self.cache 129 .get(pubkey) 130 .is_some_and(|u| u.key.secret_key.is_some()) 131 } 132 133 #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"] 134 pub fn add_account(&mut self, kp: Keypair) -> Option<AddAccountResponse> { 135 let acc = if let Some(acc) = self.cache.get_mut(&kp.pubkey) { 136 if kp.secret_key.is_none() || acc.key.secret_key.is_some() { 137 tracing::info!("Already have account, not adding"); 138 return None; 139 } 140 141 acc.key = kp.clone(); 142 AccType::Acc(&*acc) 143 } else { 144 let new_account_data = AccountData::new(kp.pubkey.bytes()); 145 AccType::Entry( 146 self.cache 147 .add(UserAccount::new(kp.clone(), new_account_data)), 148 ) 149 }; 150 151 if let Some(key_store) = &self.storage_writer { 152 if let Err(e) = key_store.write_account(&acc.get_acc().into()) { 153 tracing::error!("Could not add key for {:?}: {e}", kp.pubkey); 154 } 155 } 156 157 Some(AddAccountResponse { 158 switch_to: kp.pubkey, 159 unk_id_action: SingleUnkIdAction::pubkey(kp.pubkey), 160 }) 161 } 162 163 /// Update the `UserAccount` via callback and save the result to disk. 164 /// return true if the update was successful 165 pub fn update_current_account(&mut self, update: impl FnOnce(&mut UserAccount)) -> bool { 166 let cur_account = self.get_selected_account_mut(); 167 168 update(cur_account); 169 170 let cur_acc = self.get_selected_account(); 171 172 let Some(key_store) = &self.storage_writer else { 173 return false; 174 }; 175 176 if let Err(err) = key_store.write_account(&cur_acc.into()) { 177 tracing::error!("Could not add account {:?} to storage: {err}", cur_acc.key); 178 return false; 179 } 180 181 true 182 } 183 184 pub fn selected_filled(&self) -> Option<FilledKeypair<'_>> { 185 self.get_selected_account().key.to_full() 186 } 187 188 /// Get the selected account's pubkey as bytes. Common operation so 189 /// we make it a helper here. 190 pub fn selected_account_pubkey_bytes(&self) -> &[u8; 32] { 191 self.get_selected_account().key.pubkey.bytes() 192 } 193 194 pub fn selected_account_pubkey(&self) -> &Pubkey { 195 &self.get_selected_account().key.pubkey 196 } 197 198 pub fn get_selected_account(&self) -> &UserAccount { 199 self.cache.selected() 200 } 201 202 pub fn selected_account_has_wallet(&self) -> bool { 203 self.get_selected_account().wallet.is_some() 204 } 205 206 fn get_selected_account_mut(&mut self) -> &mut UserAccount { 207 self.cache.selected_mut() 208 } 209 210 pub fn get_selected_wallet(&self) -> Option<&ZapWallet> { 211 self.cache.selected().wallet.as_ref() 212 } 213 214 pub fn get_selected_wallet_mut(&mut self) -> Option<&mut ZapWallet> { 215 self.cache.selected_mut().wallet.as_mut() 216 } 217 218 fn get_selected_account_data(&self) -> &AccountData { 219 &self.cache.selected().data 220 } 221 222 pub fn select_account( 223 &mut self, 224 pk_to_select: &Pubkey, 225 ndb: &mut Ndb, 226 txn: &Transaction, 227 pool: &mut RelayPool, 228 ctx: &egui::Context, 229 ) { 230 if !self.cache.select(*pk_to_select) { 231 return; 232 } 233 234 self.select_account_internal(pk_to_select, ndb, txn, pool, ctx); 235 } 236 237 /// Have already selected in `AccountCache`, updating other things 238 fn select_account_internal( 239 &mut self, 240 pk_to_select: &Pubkey, 241 ndb: &mut Ndb, 242 txn: &Transaction, 243 pool: &mut RelayPool, 244 ctx: &egui::Context, 245 ) { 246 if let Some(key_store) = &self.storage_writer { 247 if let Err(e) = key_store.select_key(Some(*pk_to_select)) { 248 tracing::error!("Could not select key {:?}: {e}", pk_to_select); 249 } 250 } 251 252 self.get_selected_account_mut().data.query(ndb, txn); 253 self.subs.swap_to( 254 ndb, 255 pool, 256 &self.relay_defaults, 257 pk_to_select, 258 &self.cache.selected().data, 259 create_wakeup(ctx), 260 ); 261 } 262 263 pub fn mutefun(&self) -> Box<MuteFun> { 264 let account_data = self.get_selected_account_data(); 265 266 let muted = Arc::clone(&account_data.muted.muted); 267 Box::new(move |note: &Note, thread: &[u8; 32]| muted.is_muted(note, thread)) 268 } 269 270 pub fn mute(&self) -> Box<Arc<crate::Muted>> { 271 let account_data = self.get_selected_account_data(); 272 Box::new(Arc::clone(&account_data.muted.muted)) 273 } 274 275 pub fn update_max_hashtags_per_note(&mut self, max_hashtags: usize) { 276 for account in self.cache.accounts_mut() { 277 account.data.muted.update_max_hashtags(max_hashtags); 278 } 279 } 280 281 pub fn send_initial_filters(&mut self, pool: &mut RelayPool, relay_url: &str) { 282 let data = &self.get_selected_account().data; 283 // send the active account's relay list subscription 284 pool.send_to( 285 &ClientMessage::req( 286 self.subs.relay.remote.clone(), 287 vec![data.relay.filter.clone()], 288 ), 289 relay_url, 290 ); 291 // send the active account's muted subscription 292 pool.send_to( 293 &ClientMessage::req( 294 self.subs.mute.remote.clone(), 295 vec![data.muted.filter.clone()], 296 ), 297 relay_url, 298 ); 299 pool.send_to( 300 &ClientMessage::req( 301 self.subs.contacts.remote.clone(), 302 vec![data.contacts.filter.clone()], 303 ), 304 relay_url, 305 ); 306 if let Some(cur_pk) = self.selected_filled().map(|s| s.pubkey) { 307 let giftwraps_filter = nostrdb::Filter::new() 308 .kinds([1059]) 309 .pubkeys([cur_pk.bytes()]) 310 .build(); 311 pool.send_to( 312 &ClientMessage::req(self.subs.giftwraps.remote.clone(), vec![giftwraps_filter]), 313 relay_url, 314 ); 315 } 316 } 317 318 pub fn update(&mut self, ndb: &mut Ndb, pool: &mut RelayPool, ctx: &egui::Context) { 319 // IMPORTANT - This function is called in the UI update loop, 320 // make sure it is fast when idle 321 322 let Some(update) = self 323 .cache 324 .selected_mut() 325 .data 326 .poll_for_updates(ndb, &self.subs) 327 else { 328 return; 329 }; 330 331 match update { 332 // If needed, update the relay configuration 333 AccountDataUpdate::Relay => { 334 let acc = self.cache.selected(); 335 update_relay_configuration( 336 pool, 337 &self.relay_defaults, 338 &acc.key.pubkey, 339 &acc.data.relay, 340 create_wakeup(ctx), 341 ); 342 } 343 } 344 } 345 346 pub fn get_full<'a>(&'a self, pubkey: &Pubkey) -> Option<FilledKeypair<'a>> { 347 self.cache.get(pubkey).and_then(|r| r.key.to_full()) 348 } 349 350 pub fn process_relay_action( 351 &mut self, 352 ctx: &egui::Context, 353 pool: &mut RelayPool, 354 action: RelayAction, 355 ) { 356 let acc = self.cache.selected_mut(); 357 modify_advertised_relays(&acc.key, action, pool, &self.relay_defaults, &mut acc.data); 358 359 update_relay_configuration( 360 pool, 361 &self.relay_defaults, 362 &acc.key.pubkey, 363 &acc.data.relay, 364 create_wakeup(ctx), 365 ); 366 } 367 368 pub fn get_subs(&self) -> &AccountSubs { 369 &self.subs 370 } 371 } 372 373 enum AccType<'a> { 374 Entry(hashbrown::hash_map::OccupiedEntry<'a, Pubkey, UserAccount>), 375 Acc(&'a UserAccount), 376 } 377 378 impl<'a> AccType<'a> { 379 fn get_acc(&'a self) -> &'a UserAccount { 380 match self { 381 AccType::Entry(occupied_entry) => occupied_entry.get(), 382 AccType::Acc(user_account) => user_account, 383 } 384 } 385 } 386 387 fn create_wakeup(ctx: &egui::Context) -> impl Fn() + Send + Sync + Clone + 'static { 388 let ctx = ctx.clone(); 389 move || { 390 ctx.request_repaint(); 391 } 392 } 393 394 fn add_account_from_storage( 395 cache: &mut AccountCache, 396 user_account_serializable: UserAccountSerializable, 397 ) -> SingleUnkIdAction { 398 let Some(acc) = get_acc_from_storage(user_account_serializable) else { 399 return SingleUnkIdAction::NoAction; 400 }; 401 402 let pk = acc.key.pubkey; 403 cache.add(acc); 404 405 SingleUnkIdAction::pubkey(pk) 406 } 407 408 fn get_acc_from_storage(user_account_serializable: UserAccountSerializable) -> Option<UserAccount> { 409 let keypair = user_account_serializable.key; 410 let new_account_data = AccountData::new(keypair.pubkey.bytes()); 411 412 let mut wallet = None; 413 if let Some(wallet_s) = user_account_serializable.wallet { 414 let m_wallet: Result<crate::ZapWallet, crate::Error> = wallet_s.into(); 415 match m_wallet { 416 Ok(w) => wallet = Some(w), 417 Err(e) => { 418 tracing::error!("Problem creating wallet from disk: {e}"); 419 } 420 }; 421 } 422 423 Some(UserAccount { 424 key: keypair, 425 wallet, 426 data: new_account_data, 427 }) 428 } 429 430 #[derive(Clone)] 431 pub struct AccountData { 432 pub(crate) relay: AccountRelayData, 433 pub(crate) muted: AccountMutedData, 434 pub contacts: Contacts, 435 } 436 437 impl AccountData { 438 pub fn new(pubkey: &[u8; 32]) -> Self { 439 Self { 440 relay: AccountRelayData::new(pubkey), 441 muted: AccountMutedData::new(pubkey), 442 contacts: Contacts::new(pubkey), 443 } 444 } 445 446 pub(super) fn poll_for_updates( 447 &mut self, 448 ndb: &Ndb, 449 subs: &AccountSubs, 450 ) -> Option<AccountDataUpdate> { 451 let txn = Transaction::new(ndb).expect("txn"); 452 let mut resp = None; 453 if self.relay.poll_for_updates(ndb, &txn, subs.relay.local) { 454 resp = Some(AccountDataUpdate::Relay); 455 } 456 457 self.muted.poll_for_updates(ndb, &txn, subs.mute.local); 458 self.contacts 459 .poll_for_updates(ndb, &txn, subs.contacts.local); 460 461 resp 462 } 463 464 /// Note: query should be called as close to the subscription as possible 465 pub(super) fn query(&mut self, ndb: &Ndb, txn: &Transaction) { 466 self.relay.query(ndb, txn); 467 self.muted.query(ndb, txn); 468 self.contacts.query(ndb, txn); 469 } 470 } 471 472 pub(super) enum AccountDataUpdate { 473 Relay, 474 } 475 476 pub struct AddAccountResponse { 477 pub switch_to: Pubkey, 478 pub unk_id_action: SingleUnkIdAction, 479 } 480 481 pub struct AccountSubs { 482 relay: UnifiedSubscription, 483 giftwraps: UnifiedSubscription, 484 mute: UnifiedSubscription, 485 pub contacts: UnifiedSubscription, 486 } 487 488 impl AccountSubs { 489 pub(super) fn new( 490 ndb: &mut Ndb, 491 pool: &mut RelayPool, 492 relay_defaults: &RelayDefaults, 493 pk: &Pubkey, 494 data: &AccountData, 495 wakeup: impl Fn() + Send + Sync + Clone + 'static, 496 ) -> Self { 497 // TODO: since optimize 498 let giftwraps_filter = nostrdb::Filter::new() 499 .kinds([1059]) 500 .pubkeys([pk.bytes()]) 501 .build(); 502 503 update_relay_configuration(pool, relay_defaults, pk, &data.relay, wakeup); 504 505 let relay = subscribe(ndb, pool, &data.relay.filter); 506 let giftwraps = subscribe(ndb, pool, &giftwraps_filter); 507 let mute = subscribe(ndb, pool, &data.muted.filter); 508 let contacts = subscribe(ndb, pool, &data.contacts.filter); 509 510 Self { 511 relay, 512 mute, 513 contacts, 514 giftwraps, 515 } 516 } 517 518 pub(super) fn swap_to( 519 &mut self, 520 ndb: &mut Ndb, 521 pool: &mut RelayPool, 522 relay_defaults: &RelayDefaults, 523 pk: &Pubkey, 524 new_selection_data: &AccountData, 525 wakeup: impl Fn() + Send + Sync + Clone + 'static, 526 ) { 527 unsubscribe(ndb, pool, &self.relay); 528 unsubscribe(ndb, pool, &self.mute); 529 unsubscribe(ndb, pool, &self.contacts); 530 unsubscribe(ndb, pool, &self.giftwraps); 531 532 *self = AccountSubs::new(ndb, pool, relay_defaults, pk, new_selection_data, wakeup); 533 } 534 } 535 536 fn subscribe(ndb: &Ndb, pool: &mut RelayPool, filter: &nostrdb::Filter) -> UnifiedSubscription { 537 let filters = vec![filter.clone()]; 538 let sub = ndb 539 .subscribe(&filters) 540 .expect("ndb relay list subscription"); 541 542 // remote subscription 543 let subid = Uuid::new_v4().to_string(); 544 pool.subscribe(subid.clone(), filters); 545 546 UnifiedSubscription { 547 local: sub, 548 remote: subid, 549 } 550 } 551 552 fn unsubscribe(ndb: &mut Ndb, pool: &mut RelayPool, sub: &UnifiedSubscription) { 553 pool.unsubscribe(sub.remote.clone()); 554 555 // local subscription 556 ndb.unsubscribe(sub.local) 557 .expect("ndb relay list unsubscribe"); 558 }