use keyring::Entry; use nostr_sdk::prelude::*; use serde::{Deserialize, Serialize}; use specta::Type; use std::{str::FromStr, time::Duration}; use tauri::{Emitter, State}; use crate::{common::get_all_accounts, Nostr}; #[derive(Debug, Clone, Serialize, Deserialize, Type)] struct Account { secret_key: String, nostr_connect: Option, } #[tauri::command] #[specta::specta] pub fn get_accounts() -> Vec { get_all_accounts() } #[tauri::command] #[specta::specta] pub async fn watch_account(id: String, state: State<'_, Nostr>) -> Result { let client = &state.client; let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?; let npub = public_key.to_bech32().map_err(|e| e.to_string())?; let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?; // Set empty password keyring.set_password("").map_err(|e| e.to_string())?; // Get user's profile let _ = client .fetch_metadata(public_key, Some(Duration::from_secs(4))) .await; Ok(npub) } #[tauri::command] #[specta::specta] pub async fn import_account( key: String, password: Option, state: State<'_, Nostr>, ) -> Result { let client = &state.client; // Create secret key let secret_key = if let Some(pw) = password { let enc = EncryptedSecretKey::from_bech32(key).map_err(|err| err.to_string())?; enc.to_secret_key(pw).map_err(|err| err.to_string())? } else { SecretKey::from_str(&key).map_err(|err| err.to_string())? }; let hex = secret_key.to_secret_hex(); let keys = Keys::new(secret_key); let public_key = keys.public_key(); let npub = public_key.to_bech32().map_err(|err| err.to_string())?; let signer = NostrSigner::Keys(keys); let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?; let account = Account { secret_key: hex, nostr_connect: None, }; // Save secret key to keyring let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?; keyring.set_password(&pwd).map_err(|e| e.to_string())?; // Update signer client.set_signer(Some(signer)).await; // Get user's profile let _ = client .fetch_metadata(public_key, Some(Duration::from_secs(4))) .await; Ok(npub) } #[tauri::command] #[specta::specta] pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result { let client = &state.client; match NostrConnectURI::parse(uri.clone()) { Ok(bunker_uri) => { // Local user let app_keys = Keys::generate(); let app_secret = app_keys.secret_key().to_secret_hex(); // Get remote user let remote_user = bunker_uri.signer_public_key().unwrap(); let remote_npub = remote_user.to_bech32().unwrap(); match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None) { Ok(signer) => { let mut url = Url::parse(&uri).unwrap(); let query: Vec<(String, String)> = url .query_pairs() .filter(|(name, _)| name != "secret") .map(|(name, value)| (name.into_owned(), value.into_owned())) .collect(); url.query_pairs_mut().clear().extend_pairs(&query); let key = format!("{}_nostrconnect", remote_npub); let keyring = Entry::new("Lume Safe Storage", &key).unwrap(); let account = Account { secret_key: app_secret, nostr_connect: Some(url.to_string()), }; // Save secret key to keyring let pwd = serde_json::to_string(&account).map_err(|e| e.to_string())?; keyring.set_password(&pwd).map_err(|e| e.to_string())?; // Update signer let _ = client.set_signer(Some(signer.into())).await; // Get user's profile let _ = client .fetch_metadata(remote_user, Some(Duration::from_secs(4))) .await; Ok(remote_npub) } Err(err) => Err(err.to_string()), } } Err(err) => Err(err.to_string()), } } #[tauri::command] #[specta::specta] pub async fn reset_password(key: String, password: String) -> Result<(), String> { let secret_key = SecretKey::from_bech32(key).map_err(|err| err.to_string())?; let keys = Keys::new(secret_key.clone()); let npub = keys.public_key().to_bech32().unwrap(); let enc = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium) .map_err(|err| err.to_string())?; let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?; let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?; let account = Account { secret_key: enc_bech32, nostr_connect: None, }; let j = serde_json::to_string(&account).map_err(|e| e.to_string())?; let _ = keyring.set_password(&j); Ok(()) } #[tauri::command] #[specta::specta] pub fn get_private_key(id: String) -> Result { let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?; let password = keyring.get_password().map_err(|e| e.to_string())?; let account: Account = serde_json::from_str(&password).map_err(|e| e.to_string())?; Ok(account.secret_key) } #[tauri::command] #[specta::specta] pub fn delete_account(id: String) -> Result<(), String> { let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?; let _ = keyring.delete_credential(); Ok(()) } #[tauri::command] #[specta::specta] pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result { let client = &state.client; let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?; match client.signer().await { Ok(signer) => { let signer_key = signer.public_key().await.map_err(|e| e.to_string())?; let is_match = signer_key == public_key; Ok(is_match) } Err(_) => Ok(false), } } #[tauri::command] #[specta::specta] pub async fn set_signer( id: String, state: State<'_, Nostr>, handle: tauri::AppHandle, ) -> Result<(), String> { let client = &state.client; let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?; let account = match keyring.get_password() { Ok(pw) => { let account: Account = serde_json::from_str(&pw).map_err(|e| e.to_string())?; if account.secret_key.is_empty() { return Err("Watch Only account".into()); }; account } Err(e) => return Err(e.to_string()), }; match account.nostr_connect { None => { let secret_key = SecretKey::from_str(&account.secret_key).map_err(|e| e.to_string())?; let keys = Keys::new(secret_key); let signer = NostrSigner::Keys(keys); // Update signer client.set_signer(Some(signer)).await; // Emit to front-end handle.emit("signer-updated", ()).unwrap(); Ok(()) } Some(bunker) => { let uri = NostrConnectURI::parse(bunker).map_err(|e| e.to_string())?; let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?; match Nip46Signer::new(uri, app_keys, Duration::from_secs(120), None) { Ok(signer) => { // Update signer client.set_signer(Some(signer.into())).await; // Emit to front-end handle.emit("signer-updated", ()).unwrap(); Ok(()) } Err(e) => Err(e.to_string()), } } } }