wip: rework multi account

This commit is contained in:
2024-10-24 07:59:41 +07:00
parent c032dbea1a
commit 469296790e
11 changed files with 245 additions and 304 deletions

View File

@@ -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<String>,
}
@@ -21,74 +21,58 @@ pub fn get_accounts() -> Vec<String> {
#[tauri::command]
#[specta::specta]
pub async fn watch_account(key: String, state: State<'_, Nostr>) -> Result<String, String> {
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<String, String> {
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<String>,
state: State<'_, Nostr>,
) -> Result<String, String> {
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<Str
url.query_pairs_mut().clear().extend_pairs(&query);
let key = format!("{}_nostrconnect", remote_npub);
let keyring = Entry::new("Lume Secret Storage", &key).unwrap();
let keyring = Entry::new("Lume Safe Storage", &key).unwrap();
let account = Account {
password: app_secret,
secret_key: app_secret,
nostr_connect: Some(url.to_string()),
};
let j = serde_json::to_string(&account).map_err(|e| e.to_string())?;
let _ = keyring.set_password(&j);
// Run sync for this account
// run_sync_for(public_key, app_handle);
// 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;
// Update state
state.accounts.lock().unwrap().push(remote_npub.clone());
@@ -158,9 +140,9 @@ pub async fn reset_password(key: String, password: String) -> 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<String, 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 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<bool, Str
match client.signer().await {
Ok(signer) => {
// 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<bool, Str
#[tauri::command]
#[specta::specta]
pub async fn set_signer(
account: String,
password: String,
id: String,
state: State<'_, Nostr>,
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) => {

View File

@@ -290,10 +290,10 @@ pub async fn publish(
warning: Option<String>,
difficulty: Option<u8>,
state: State<'_, Nostr>,
) -> Result<String, String> {
) -> Result<bool, String> {
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<String>,
state: State<'_, Nostr>,
) -> Result<String, String> {
) -> Result<bool, String> {
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<String, String> {
pub async fn repost(raw: String, state: State<'_, Nostr>) -> Result<bool, String> {
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()),
}
}

View File

@@ -32,50 +32,12 @@ pub struct Mention {
#[tauri::command]
#[specta::specta]
pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<String, String> {
pub async fn get_profile(id: String, state: State<'_, Nostr>) -> Result<String, String> {
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<String>,
state: State<'_, Nostr>,
handle: tauri::AppHandle,
) -> Result<String, String> {
) -> Result<bool, String> {
let client = &state.client;
let public_keys: Vec<PublicKey> = 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::<Nostr>();
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<String>,
state: State<'_, Nostr>,
handle: tauri::AppHandle,
) -> Result<String, String> {
) -> Result<bool, String> {
let client = &state.client;
let label = title.to_lowercase().replace(" ", "-");
let mut tags: Vec<Tag> = 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::<Nostr>();
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<bool, Stri
if let Ok(nwc_uri) = NostrWalletConnectURI::from_str(uri) {
let nwc = NWC::new(nwc_uri);
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())?;
keyring.set_password(uri).map_err(|e| e.to_string())?;
client.set_zapper(nwc).await;
@@ -466,7 +452,7 @@ pub async fn load_wallet(state: State<'_, Nostr>) -> 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(_) => {

View File

@@ -134,7 +134,7 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
pub fn get_all_accounts() -> Vec<String> {
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<String> = list
.split_whitespace()

View File

@@ -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<Settings>,
accounts: Mutex<Vec<String>>,
subscriptions: Mutex<Vec<SubscriptionId>>,
bootstrap_relays: Mutex<Vec<Url>>,
subscriptions: Mutex<HashSet<SubscriptionId>>,
send_queue: Mutex<HashSet<Event>>,
}
#[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<String>,
contacts: Option<Vec<String>>,
}
#[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::<tauri::Wry>::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::<Nostr>();
let client = &state.client;
let queue: Vec<Event> = 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())