diff --git a/src-tauri/src/commands/account.rs b/src-tauri/src/commands/account.rs index a9956f18..41371760 100644 --- a/src-tauri/src/commands/account.rs +++ b/src-tauri/src/commands/account.rs @@ -9,7 +9,7 @@ use crate::{common::get_all_accounts, Nostr}; #[derive(Debug, Clone, Serialize, Deserialize, Type)] struct Account { - password: String, + secret_key: String, nostr_connect: Option, } @@ -21,74 +21,58 @@ pub fn get_accounts() -> Vec { #[tauri::command] #[specta::specta] -pub async fn watch_account(key: String, state: State<'_, Nostr>) -> Result { - let public_key = PublicKey::from_str(&key).map_err(|e| e.to_string())?; - let bech32 = public_key.to_bech32().map_err(|e| e.to_string())?; - let keyring = Entry::new("Lume Secret Storage", &bech32).map_err(|e| e.to_string())?; +pub async fn watch_account(id: String, state: State<'_, Nostr>) -> Result { + 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())?; - - // Run sync for this account - // run_sync_for(public_key, app_handle); - // Update state - state.accounts.lock().unwrap().push(bech32.clone()); + state.accounts.lock().unwrap().push(npub.clone()); - Ok(bech32) + Ok(npub) } #[tauri::command] #[specta::specta] pub async fn import_account( key: String, - password: String, + password: Option, state: State<'_, Nostr>, ) -> Result { let client = &state.client; - let (npub, enc_bech32, signer) = match key.starts_with("ncryptsec") { - true => { - let enc = EncryptedSecretKey::from_bech32(key).map_err(|err| err.to_string())?; - let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?; - let secret_key = enc.to_secret_key(password).map_err(|err| err.to_string())?; - let keys = Keys::new(secret_key); - let npub = keys - .public_key() - .to_bech32() - .map_err(|err| err.to_string())?; - let signer = NostrSigner::Keys(keys); - - (npub, enc_bech32, signer) - } - false => { - 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 signer = NostrSigner::Keys(keys); - 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())?; - - (npub, enc_bech32, signer) - } + // 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 keyring = Entry::new("Lume Secret Storage", &npub).map_err(|e| e.to_string())?; + let hex = secret_key.to_secret_hex(); + let keys = Keys::new(secret_key); + + let npub = keys + .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 { - password: enc_bech32, + 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())?; - // Run sync for this account - // run_sync_for(public_key, app_handle); - // Update signer client.set_signer(Some(signer)).await; - // Update state state.accounts.lock().unwrap().push(npub.clone()); @@ -121,20 +105,18 @@ pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result Result<(), String> .map_err(|err| err.to_string())?; let enc_bech32 = enc.to_bech32().map_err(|err| err.to_string())?; - let keyring = Entry::new("Lume Secret Storage", &npub).map_err(|e| e.to_string())?; + let keyring = Entry::new("Lume Safe Storage", &npub).map_err(|e| e.to_string())?; let account = Account { - password: enc_bech32, + secret_key: enc_bech32, nostr_connect: None, }; let j = serde_json::to_string(&account).map_err(|e| e.to_string())?; @@ -172,16 +154,17 @@ pub async fn reset_password(key: String, password: String) -> Result<(), String> #[tauri::command] #[specta::specta] pub fn get_private_key(id: String) -> Result { - let keyring = Entry::new("Lume Secret Storage", &id).map_err(|e| e.to_string())?; + 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(password) + Ok(account.secret_key) } #[tauri::command] #[specta::specta] pub fn delete_account(id: String) -> Result<(), String> { - let keyring = Entry::new("Lume Secret Storage", &id).map_err(|e| e.to_string())?; + let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?; let _ = keyring.delete_credential(); Ok(()) @@ -195,9 +178,6 @@ pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result { - // Emit reload in front-end - // handle.emit("signer", ()).unwrap(); - let signer_key = signer.public_key().await.map_err(|e| e.to_string())?; let is_match = signer_key == public_key; @@ -210,13 +190,12 @@ pub async fn has_signer(id: String, state: State<'_, Nostr>) -> Result, handle: tauri::AppHandle, ) -> Result<(), String> { let client = &state.client; - let keyring = Entry::new("Lume Secret Storage", &account).map_err(|e| e.to_string())?; + let keyring = Entry::new("Lume Safe Storage", &id).map_err(|e| e.to_string())?; let account = match keyring.get_password() { Ok(pw) => { @@ -228,11 +207,7 @@ pub async fn set_signer( match account.nostr_connect { None => { - let ncryptsec = - EncryptedSecretKey::from_bech32(account.password).map_err(|e| e.to_string())?; - let secret_key = ncryptsec - .to_secret_key(password) - .map_err(|_| "Wrong password.")?; + 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); @@ -245,7 +220,7 @@ pub async fn set_signer( } Some(bunker) => { let uri = NostrConnectURI::parse(bunker).map_err(|e| e.to_string())?; - let app_keys = Keys::from_str(&account.password).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) => { diff --git a/src-tauri/src/commands/event.rs b/src-tauri/src/commands/event.rs index 5adce799..253e5207 100644 --- a/src-tauri/src/commands/event.rs +++ b/src-tauri/src/commands/event.rs @@ -290,10 +290,10 @@ pub async fn publish( warning: Option, difficulty: Option, state: State<'_, Nostr>, -) -> Result { +) -> Result { let client = &state.client; - // Create tags from content + // Create event tags from content let mut tags = create_tags(&content); // Add client tag @@ -319,9 +319,14 @@ pub async fn publish( .await .map_err(|err| err.to_string())?; - // Publish - match client.send_event(event).await { - Ok(event_id) => Ok(event_id.to_hex()), + // Save to local database + match client.database().save_event(&event).await { + Ok(status) => { + // Add event to queue to broadcast it later. + state.send_queue.lock().unwrap().insert(event); + // Return + Ok(status) + } Err(err) => Err(err.to_string()), } } @@ -333,83 +338,81 @@ pub async fn reply( to: String, root: Option, state: State<'_, Nostr>, -) -> Result { +) -> Result { let client = &state.client; - let database = client.database(); - let reply_id = EventId::parse(&to).map_err(|err| err.to_string())?; + // Create event tags from content let mut tags = create_tags(&content); - match database.query(vec![Filter::new().id(reply_id)]).await { - Ok(events) => { - if let Some(event) = events.first() { - let relay_hint = if let Some(relays) = database - .event_seen_on_relays(&event.id) - .await - .map_err(|err| err.to_string())? - { - relays.into_iter().next().map(UncheckedUrl::new) - } else { - None - }; - let t = TagStandard::Event { - event_id: event.id, - relay_url: relay_hint, - marker: Some(Marker::Reply), - public_key: Some(event.pubkey), - }; - let tag = Tag::from(t); - tags.push(tag) + // Add client tag + // TODO: allow user config this setting + tags.push(Tag::custom(TagKind::custom("client"), vec!["Lume"])); + + // Get reply event + let reply_id = EventId::parse(&to).map_err(|err| err.to_string())?; + let reply_to = match client.database().event_by_id(&reply_id).await { + Ok(event) => { + if let Some(event) = event { + event } else { - return Err("Reply event is not found.".into()); + return Err("Event not found in database, cannot reply.".into()); } } - Err(err) => return Err(err.to_string()), + Err(e) => return Err(e.to_string()), }; - if let Some(id) = root { - let root_id = match EventId::from_hex(id) { - Ok(val) => val, - Err(_) => return Err("Event is not valid.".into()), - }; - - if let Ok(events) = database.query(vec![Filter::new().id(root_id)]).await { - if let Some(event) = events.first() { - let relay_hint = if let Some(relays) = database - .event_seen_on_relays(&event.id) - .await - .map_err(|err| err.to_string())? - { - relays.into_iter().next().map(UncheckedUrl::new) - } else { - None - }; - let t = TagStandard::Event { - event_id: event.id, - relay_url: relay_hint, - marker: Some(Marker::Root), - public_key: Some(event.pubkey), - }; - let tag = Tag::from(t); - tags.push(tag) - } + // Get root event if exist + let root = match root { + Some(id) => { + let root_id = EventId::parse(&id).map_err(|err| err.to_string())?; + (client.database().event_by_id(&root_id).await).unwrap_or_default() } + None => None, }; - match client.publish_text_note(content, tags).await { - Ok(event_id) => Ok(event_id.to_bech32().map_err(|err| err.to_string())?), + let builder = EventBuilder::text_note_reply(content, &reply_to, root.as_ref(), None) + .add_tags(tags) + .pow(DEFAULT_DIFFICULTY); + + // Sign event + let event = client + .sign_event_builder(builder) + .await + .map_err(|err| err.to_string())?; + + // Save to local database + match client.database().save_event(&event).await { + Ok(status) => { + // Add event to queue to broadcast it later. + state.send_queue.lock().unwrap().insert(event); + // Return + Ok(status) + } Err(err) => Err(err.to_string()), } } #[tauri::command] #[specta::specta] -pub async fn repost(raw: String, state: State<'_, Nostr>) -> Result { +pub async fn repost(raw: String, state: State<'_, Nostr>) -> Result { let client = &state.client; let event = Event::from_json(raw).map_err(|err| err.to_string())?; + let builder = EventBuilder::repost(&event, None); - match client.repost(&event, None).await { - Ok(event_id) => Ok(event_id.to_string()), + // Sign event + let event = client + .sign_event_builder(builder) + .await + .map_err(|err| err.to_string())?; + + // Save to local database + match client.database().save_event(&event).await { + Ok(status) => { + // Add event to queue to broadcast it later. + state.send_queue.lock().unwrap().insert(event); + // Return + Ok(status) + } Err(err) => Err(err.to_string()), } } diff --git a/src-tauri/src/commands/metadata.rs b/src-tauri/src/commands/metadata.rs index 0577baad..8827d25e 100644 --- a/src-tauri/src/commands/metadata.rs +++ b/src-tauri/src/commands/metadata.rs @@ -32,50 +32,12 @@ pub struct Mention { #[tauri::command] #[specta::specta] -pub async fn get_profile(id: Option, state: State<'_, Nostr>) -> Result { +pub async fn get_profile(id: String, state: State<'_, Nostr>) -> Result { let client = &state.client; + let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?; - let public_key: PublicKey = match id { - Some(user_id) => PublicKey::parse(&user_id).map_err(|e| e.to_string())?, - None => { - let signer = client.signer().await.map_err(|e| e.to_string())?; - signer.public_key().await.map_err(|e| e.to_string())? - } - }; - - let filter = Filter::new() - .author(public_key) - .kind(Kind::Metadata) - .limit(1); - - match client.database().query(vec![filter.clone()]).await { - Ok(events) => { - if let Some(event) = get_latest_event(&events) { - if let Ok(metadata) = Metadata::from_json(&event.content) { - Ok(metadata.as_json()) - } else { - Err("Parse metadata failed".into()) - } - } else { - match client - .fetch_events(vec![filter], Some(Duration::from_secs(10))) - .await - { - Ok(events) => { - if let Some(event) = get_latest_event(&events) { - if let Ok(metadata) = Metadata::from_json(&event.content) { - Ok(metadata.as_json()) - } else { - Err("Metadata is not valid.".into()) - } - } else { - Err("Not found.".into()) - } - } - Err(e) => Err(e.to_string()), - } - } - } + match client.database().profile(public_key).await { + Ok(profile) => Ok(profile.metadata().as_json()), Err(e) => Err(e.to_string()), } } @@ -237,7 +199,7 @@ pub async fn set_group( users: Vec, state: State<'_, Nostr>, handle: tauri::AppHandle, -) -> Result { +) -> Result { let client = &state.client; let public_keys: Vec = users .iter() @@ -257,8 +219,19 @@ pub async fn set_group( let builder = EventBuilder::follow_set(label, public_keys.clone()).add_tags(tags); - match client.send_event_builder(builder).await { - Ok(report) => { + // Sign event + let event = client + .sign_event_builder(builder) + .await + .map_err(|err| err.to_string())?; + + // Save to local database + match client.database().save_event(&event).await { + Ok(status) => { + // Add event to queue to broadcast it later. + state.send_queue.lock().unwrap().insert(event); + + // Sync event tauri::async_runtime::spawn(async move { let state = handle.state::(); let client = &state.client; @@ -274,9 +247,10 @@ pub async fn set_group( }; }); - Ok(report.id().to_hex()) + // Return + Ok(status) } - Err(e) => Err(e.to_string()), + Err(err) => Err(err.to_string()), } } @@ -328,7 +302,7 @@ pub async fn set_interest( hashtags: Vec, state: State<'_, Nostr>, handle: tauri::AppHandle, -) -> Result { +) -> Result { let client = &state.client; let label = title.to_lowercase().replace(" ", "-"); let mut tags: Vec = vec![Tag::title(title)]; @@ -344,8 +318,19 @@ pub async fn set_interest( let builder = EventBuilder::interest_set(label, hashtags.clone()).add_tags(tags); - match client.send_event_builder(builder).await { - Ok(report) => { + // Sign event + let event = client + .sign_event_builder(builder) + .await + .map_err(|err| err.to_string())?; + + // Save to local database + match client.database().save_event(&event).await { + Ok(status) => { + // Add event to queue to broadcast it later. + state.send_queue.lock().unwrap().insert(event); + + // Sync event tauri::async_runtime::spawn(async move { let state = handle.state::(); let client = &state.client; @@ -361,9 +346,10 @@ pub async fn set_interest( }; }); - Ok(report.id().to_hex()) + // Return + Ok(status) } - Err(e) => Err(e.to_string()), + Err(err) => Err(err.to_string()), } } @@ -448,7 +434,7 @@ pub async fn set_wallet(uri: &str, state: State<'_, Nostr>) -> Result) -> Result<(), String> { if client.zapper().await.is_err() { let keyring = - Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?; + Entry::new("Lume Safe Storage", "Bitcoin Connect").map_err(|e| e.to_string())?; match keyring.get_password() { Ok(val) => { @@ -486,8 +472,7 @@ pub async fn load_wallet(state: State<'_, Nostr>) -> Result<(), String> { #[specta::specta] pub async fn remove_wallet(state: State<'_, Nostr>) -> Result<(), String> { let client = &state.client; - let keyring = - Entry::new("Lume Secret Storage", "Bitcoin Connect").map_err(|e| e.to_string())?; + let keyring = Entry::new("Lume Safe Storage", "Bitcoin Connect").map_err(|e| e.to_string())?; match keyring.delete_credential() { Ok(_) => { diff --git a/src-tauri/src/common.rs b/src-tauri/src/common.rs index a1c9bc5d..fea4cb7e 100644 --- a/src-tauri/src/common.rs +++ b/src-tauri/src/common.rs @@ -134,7 +134,7 @@ pub fn create_tags(content: &str) -> Vec { pub fn get_all_accounts() -> Vec { let search = Search::new().expect("Unexpected."); - let results = search.by_service("Lume Secret Storage"); + let results = search.by_service("Lume Safe Storage"); let list = List::list_credentials(&results, Limit::All); let accounts: HashSet = list .split_whitespace() diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f182f208..ee869542 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,27 +5,21 @@ #[cfg(target_os = "macos")] use border::WebviewWindowExt as BorderWebviewWindowExt; -use commands::{ - account::*, - event::*, - metadata::*, - relay::*, - sync::{run_fast_sync, NegentropyEvent}, - window::*, -}; +use commands::{account::*, event::*, metadata::*, relay::*, sync::NegentropyEvent, window::*}; use common::{get_all_accounts, parse_event}; use nostr_sdk::prelude::{Profile as DatabaseProfile, *}; use serde::{Deserialize, Serialize}; use specta::Type; use specta_typescript::Typescript; use std::{ + collections::HashSet, fs, io::{self, BufRead}, str::FromStr, sync::Mutex, time::Duration, }; -use tauri::{path::BaseDirectory, Emitter, EventTarget, Manager}; +use tauri::{path::BaseDirectory, Emitter, EventTarget, Manager, WindowEvent}; use tauri_plugin_decorum::WebviewWindowExt; use tauri_plugin_notification::{NotificationExt, PermissionState}; use tauri_specta::{collect_commands, collect_events, Builder, Event as TauriEvent}; @@ -37,8 +31,9 @@ pub struct Nostr { client: Client, settings: Mutex, accounts: Mutex>, - subscriptions: Mutex>, bootstrap_relays: Mutex>, + subscriptions: Mutex>, + send_queue: Mutex>, } #[derive(Clone, Serialize, Deserialize, Type)] @@ -73,7 +68,7 @@ impl Default for Settings { } #[derive(Serialize, Deserialize, Type)] -enum SubKind { +enum SubscriptionMethod { Subscribe, Unsubscribe, } @@ -81,13 +76,15 @@ enum SubKind { #[derive(Serialize, Deserialize, Type, TauriEvent)] struct Subscription { label: String, - kind: SubKind, + kind: SubscriptionMethod, event_id: Option, contacts: Option>, } -#[derive(Serialize, Deserialize, Type, Clone, TauriEvent)] -struct NewSettings(Settings); +#[derive(Serialize, Deserialize, Type, TauriEvent)] +struct Sync { + id: String, +} pub const DEFAULT_DIFFICULTY: u8 = 21; pub const FETCH_LIMIT: usize = 50; @@ -95,7 +92,6 @@ pub const NOTIFICATION_SUB_ID: &str = "lume_notification"; fn main() { let builder = Builder::::new() - // Then register them (separated by a comma) .commands(collect_commands![ get_relays, connect_relay, @@ -161,7 +157,7 @@ fn main() { reopen_lume, quit ]) - .events(collect_events![Subscription, NewSettings, NegentropyEvent]); + .events(collect_events![Subscription, NegentropyEvent]); #[cfg(debug_assertions)] builder @@ -179,7 +175,6 @@ fn main() { let handle = app.handle(); let handle_clone = handle.clone(); let handle_clone_child = handle_clone.clone(); - let handle_clone_sync = handle_clone_child.clone(); let main_window = app.get_webview_window("main").unwrap(); let config_dir = handle @@ -201,16 +196,6 @@ fn main() { #[cfg(target_os = "macos")] main_window.set_traffic_lights_inset(7.0, 10.0).unwrap(); - #[cfg(target_os = "macos")] - let win = main_window.clone(); - - #[cfg(target_os = "macos")] - main_window.on_window_event(move |event| { - if let tauri::WindowEvent::ThemeChanged(_) = event { - win.set_traffic_lights_inset(7.0, 10.0).unwrap(); - } - }); - let (client, bootstrap_relays) = tauri::async_runtime::block_on(async move { // Setup database let database = NostrLMDB::open(config_dir.join("nostr")) @@ -260,17 +245,9 @@ fn main() { } } - if let Err(e) = client.add_discovery_relay("wss://purplepag.es/").await { - println!("Add discovery relay failed: {}", e) - } - - if let Err(e) = client.add_discovery_relay("wss://directory.yabu.me/").await { - println!("Add discovery relay failed: {}", e) - } - - if let Err(e) = client.add_discovery_relay("wss://user.kindpag.es/").await { - println!("Add discovery relay failed: {}", e) - } + let _ = client.add_discovery_relay("wss://purplepag.es/").await; + let _ = client.add_discovery_relay("wss://directory.yabu.me/").await; + let _ = client.add_discovery_relay("wss://user.kindpag.es/").await; // Connect client.connect_with_timeout(Duration::from_secs(10)).await; @@ -283,19 +260,18 @@ fn main() { }); let accounts = get_all_accounts(); - // Run fast sync for all accounts - run_fast_sync(accounts.clone(), handle_clone_sync); // Create global state app.manage(Nostr { client, accounts: Mutex::new(accounts), settings: Mutex::new(Settings::default()), - subscriptions: Mutex::new(Vec::new()), bootstrap_relays: Mutex::new(bootstrap_relays), + subscriptions: Mutex::new(HashSet::new()), + send_queue: Mutex::new(HashSet::new()), }); - // Handle subscription + // Handle subscription request Subscription::listen_any(app, move |event| { let handle = handle_clone_child.to_owned(); let payload = event.payload; @@ -305,7 +281,7 @@ fn main() { let client = &state.client; match payload.kind { - SubKind::Subscribe => { + SubscriptionMethod::Subscribe => { let subscription_id = SubscriptionId::new(payload.label); if !client @@ -319,7 +295,7 @@ fn main() { .subscriptions .lock() .unwrap() - .push(subscription_id.clone()); + .insert(subscription_id.clone()); println!( "Total subscriptions: {}", @@ -371,22 +347,16 @@ fn main() { } } } - SubKind::Unsubscribe => { + SubscriptionMethod::Unsubscribe => { let subscription_id = SubscriptionId::new(payload.label); - let mut sub_state = state.subscriptions.lock().unwrap().clone(); - - if let Some(pos) = sub_state.iter().position(|x| *x == subscription_id) - { - sub_state.remove(pos); - state.subscriptions.lock().unwrap().clone_from(&sub_state) - } println!( "Total subscriptions: {}", state.subscriptions.lock().unwrap().len() ); - client.unsubscribe(subscription_id).await + state.subscriptions.lock().unwrap().remove(&subscription_id); + client.unsubscribe(subscription_id).await; } } }); @@ -570,6 +540,39 @@ fn main() { Ok(()) }) + .on_window_event(|window, event| match event { + WindowEvent::CloseRequested { api, .. } => { + api.prevent_close(); + // Just hide window not close + window.hide().unwrap(); + + let state = window.state::(); + let client = &state.client; + let queue: Vec = state + .send_queue + .lock() + .unwrap() + .clone() + .into_iter() + .collect(); + + if !queue.is_empty() { + tauri::async_runtime::block_on(async { + println!("Sending total {} events to relays", queue.len()); + match client.batch_event(queue, RelaySendOptions::default()).await { + Ok(_) => window.destroy().unwrap(), + Err(_) => window.emit("batch-event", ()).unwrap(), + } + }); + } else { + window.destroy().unwrap() + } + } + WindowEvent::Focused(_focused) => { + // TODO + } + _ => {} + }) .plugin(prevent_default()) .plugin(tauri_plugin_theme::init(ctx.config_mut())) .plugin(tauri_plugin_decorum::init()) diff --git a/src/commands.gen.ts b/src/commands.gen.ts index 63647f13..35f68f24 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -48,15 +48,15 @@ async saveBootstrapRelays(relays: string) : Promise> { async getAccounts() : Promise { return await TAURI_INVOKE("get_accounts"); }, -async watchAccount(key: string) : Promise> { +async watchAccount(id: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("watch_account", { key }) }; + return { status: "ok", data: await TAURI_INVOKE("watch_account", { id }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, -async importAccount(key: string, password: string) : Promise> { +async importAccount(key: string, password: string | null) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("import_account", { key, password }) }; } catch (e) { @@ -104,15 +104,15 @@ async hasSigner(id: string) : Promise> { else return { status: "error", error: e as any }; } }, -async setSigner(account: string, password: string) : Promise> { +async setSigner(id: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("set_signer", { account, password }) }; + return { status: "ok", data: await TAURI_INVOKE("set_signer", { id }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; } }, -async getProfile(id: string | null) : Promise> { +async getProfile(id: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("get_profile", { id }) }; } catch (e) { @@ -168,7 +168,7 @@ async getAllProfiles() : Promise> { else return { status: "error", error: e as any }; } }, -async setGroup(title: string, description: string | null, image: string | null, users: string[]) : Promise> { +async setGroup(title: string, description: string | null, image: string | null, users: string[]) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("set_group", { title, description, image, users }) }; } catch (e) { @@ -192,7 +192,7 @@ async getAllGroups() : Promise> { else return { status: "error", error: e as any }; } }, -async setInterest(title: string, description: string | null, image: string | null, hashtags: string[]) : Promise> { +async setInterest(title: string, description: string | null, image: string | null, hashtags: string[]) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("set_interest", { title, description, image, hashtags }) }; } catch (e) { @@ -384,7 +384,7 @@ async search(query: string) : Promise> { else return { status: "error", error: e as any }; } }, -async publish(content: string, warning: string | null, difficulty: number | null) : Promise> { +async publish(content: string, warning: string | null, difficulty: number | null) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("publish", { content, warning, difficulty }) }; } catch (e) { @@ -392,7 +392,7 @@ async publish(content: string, warning: string | null, difficulty: number | null else return { status: "error", error: e as any }; } }, -async reply(content: string, to: string, root: string | null) : Promise> { +async reply(content: string, to: string, root: string | null) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("reply", { content, to, root }) }; } catch (e) { @@ -400,7 +400,7 @@ async reply(content: string, to: string, root: string | null) : Promise> { +async repost(raw: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("repost", { raw }) }; } catch (e) { @@ -501,11 +501,9 @@ async quit() : Promise { export const events = __makeEvents__<{ negentropyEvent: NegentropyEvent, -newSettings: NewSettings, subscription: Subscription }>({ negentropyEvent: "negentropy-event", -newSettings: "new-settings", subscription: "subscription" }) @@ -520,13 +518,12 @@ export type Mention = { pubkey: string; avatar: string; display_name: string; na export type Meta = { content: string; images: string[]; events: string[]; mentions: string[]; hashtags: string[] } export type NegentropyEvent = { kind: NegentropyKind; total_event: number } export type NegentropyKind = "Profile" | "Metadata" | "Events" | "EventIds" | "Global" | "Notification" | "Others" -export type NewSettings = Settings export type Profile = { name: string; display_name: string; about: string | null; picture: string; banner: string | null; nip05: string | null; lud16: string | null; website: string | null } export type Relays = { connected: string[]; read: string[] | null; write: string[] | null; both: string[] | null } export type RichEvent = { raw: string; parsed: Meta | null } export type Settings = { proxy: string | null; image_resize_service: string | null; use_relay_hint: boolean; content_warning: boolean; trusted_only: boolean; display_avatar: boolean; display_zap_button: boolean; display_repost_button: boolean; display_media: boolean; transparent: boolean } -export type SubKind = "Subscribe" | "Unsubscribe" -export type Subscription = { label: string; kind: SubKind; event_id: string | null; contacts: string[] | null } +export type Subscription = { label: string; kind: SubscriptionMethod; event_id: string | null; contacts: string[] | null } +export type SubscriptionMethod = "Subscribe" | "Unsubscribe" export type Window = { label: string; title: string; url: string; width: number; height: number; maximizable: boolean; minimizable: boolean; hidden_title: boolean; closable: boolean } /** tauri-specta globals **/ diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 313834f3..d468a39f 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -20,6 +20,7 @@ export const Route = createRootRouteWithContext()({ function Screen() { const { queryClient } = Route.useRouteContext(); + /* useEffect(() => { const unlisten = events.newSettings.listen((data) => { appSettings.setState((state) => { @@ -31,6 +32,7 @@ function Screen() { unlisten.then((f) => f()); }; }, []); + */ useEffect(() => { const unlisten = events.negentropyEvent.listen(async (data) => { diff --git a/src/routes/_layout.lazy.tsx b/src/routes/_layout.lazy.tsx index dc7d2307..8de76b7d 100644 --- a/src/routes/_layout.lazy.tsx +++ b/src/routes/_layout.lazy.tsx @@ -9,7 +9,7 @@ import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router"; import { listen } from "@tauri-apps/api/event"; import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu"; import { writeText } from "@tauri-apps/plugin-clipboard-manager"; -import { memo, useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect } from "react"; export const Route = createLazyFileRoute("/_layout")({ component: Layout, @@ -80,41 +80,6 @@ function Topbar() { ); } -const NegentropyBadge = memo(function NegentropyBadge() { - const [process, setProcess] = useState(null); - - useEffect(() => { - const unlisten = listen("negentropy", async (data) => { - if (data.payload === "Ok") { - setProcess(null); - } else { - setProcess(data.payload); - } - }); - - return () => { - unlisten.then((f) => f()); - }; - }, []); - - if (!process) { - return null; - } - - return ( -
- {process ? ( - - {process.message} - {process.total_event > 0 ? ` / ${process.total_event}` : null} - - ) : ( - "Syncing" - )} -
- ); -}); - function Account({ pubkey }: { pubkey: string }) { const navigate = Route.useNavigate(); const context = Route.useRouteContext(); @@ -183,6 +148,16 @@ function Account({ pubkey }: { pubkey: string }) { [pubkey], ); + useEffect(() => { + const unlisten = listen("signer-updated", async () => { + await context.queryClient.invalidateQueries({ queryKey: ["signer"] }); + }); + + return () => { + unlisten.then((f) => f()); + }; + }, []); + return ( - {key.length ? ( + {key.startsWith("ncryptsec") ? (
{ - const until = pageParam > 0 ? pageParam.toString() : undefined; + const until = pageParam > 0 ? pageParam.toString() : null; const res = await commands.getAllEventsByAuthors(contacts, until); if (res.status === "error") { @@ -76,9 +76,9 @@ export function Screen() { useEffect(() => { events.subscription .emit({ - label: search.label, + label: search.label as string, kind: "Subscribe", - event_id: undefined, + event_id: null, contacts, }) .then(() => console.log("Subscribe: ", search.label)); @@ -86,9 +86,9 @@ export function Screen() { return () => { events.subscription .emit({ - label: search.label, + label: search.label as string, kind: "Unsubscribe", - event_id: undefined, + event_id: null, contacts, }) .then(() => console.log("Unsubscribe: ", search.label));