feat: improve performance (#234)
* feat: use negentropy as much as possible * update * update
This commit is contained in:
@@ -3,7 +3,12 @@ use keyring_search::{Limit, List, Search};
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use std::{collections::HashSet, str::FromStr, time::Duration};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::{self, File},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
use tauri::{Emitter, Manager, State};
|
||||
|
||||
use crate::{
|
||||
@@ -133,7 +138,7 @@ pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<Str
|
||||
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).await {
|
||||
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
|
||||
@@ -204,6 +209,17 @@ pub fn delete_account(id: String) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn is_account_sync(id: String, handle: tauri::AppHandle) -> bool {
|
||||
let config_dir = handle
|
||||
.path()
|
||||
.app_config_dir()
|
||||
.expect("Error: app config directory not found.");
|
||||
|
||||
fs::metadata(config_dir.join(id)).is_ok()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn login(
|
||||
@@ -244,7 +260,7 @@ pub async fn login(
|
||||
let public_key = uri.signer_public_key().unwrap().to_bech32().unwrap();
|
||||
let app_keys = Keys::from_str(&account.password).map_err(|e| e.to_string())?;
|
||||
|
||||
match Nip46Signer::new(uri, app_keys, Duration::from_secs(120), None).await {
|
||||
match Nip46Signer::new(uri, app_keys, Duration::from_secs(120), None) {
|
||||
Ok(signer) => {
|
||||
// Update signer
|
||||
client.set_signer(Some(signer.into())).await;
|
||||
@@ -259,6 +275,11 @@ pub async fn login(
|
||||
init_nip65(client, &public_key).await;
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let config_dir = handle
|
||||
.path()
|
||||
.app_config_dir()
|
||||
.expect("Error: app config directory not found.");
|
||||
|
||||
let state = handle.state::<Nostr>();
|
||||
let client = &state.client;
|
||||
|
||||
@@ -298,64 +319,85 @@ pub async fn login(
|
||||
state.settings.lock().await.clone_from(&settings);
|
||||
};
|
||||
|
||||
let contact_list = client
|
||||
.get_contact_list(Some(Duration::from_secs(5)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Get user's contact list
|
||||
if let Ok(contacts) = client.get_contact_list(Some(Duration::from_secs(5))).await {
|
||||
state.contact_list.lock().await.clone_from(&contacts);
|
||||
if !contact_list.is_empty() {
|
||||
let authors: Vec<PublicKey> = contact_list.iter().map(|f| f.public_key).collect();
|
||||
|
||||
if !contacts.is_empty() {
|
||||
let pubkeys: Vec<PublicKey> = contacts.iter().map(|f| f.public_key).collect();
|
||||
let metadata = Filter::new()
|
||||
.authors(authors.clone())
|
||||
.kind(Kind::Metadata)
|
||||
.limit(authors.len());
|
||||
|
||||
let newsfeed = Filter::new()
|
||||
.authors(pubkeys.clone())
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(NEWSFEED_NEG_LIMIT);
|
||||
if let Ok(report) = client
|
||||
.reconcile(metadata, NegentropyOptions::default())
|
||||
.await
|
||||
{
|
||||
println!("received [metadata]: {}", report.received.len())
|
||||
}
|
||||
|
||||
if client
|
||||
.reconcile(newsfeed, NegentropyOptions::default())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
handle.emit("synchronized", ()).unwrap();
|
||||
}
|
||||
let newsfeed = Filter::new()
|
||||
.authors(authors.clone())
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(NEWSFEED_NEG_LIMIT);
|
||||
|
||||
if client
|
||||
.reconcile(newsfeed, NegentropyOptions::default())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
// Save state
|
||||
let _ = File::create(config_dir.join(public_key.to_bech32().unwrap()));
|
||||
// Update frontend
|
||||
handle.emit("synchronized", ()).unwrap();
|
||||
}
|
||||
|
||||
let contacts = Filter::new()
|
||||
.authors(authors.clone())
|
||||
.kind(Kind::ContactList)
|
||||
.limit(authors.len() * 1000);
|
||||
|
||||
if let Ok(report) = client
|
||||
.reconcile(contacts, NegentropyOptions::default())
|
||||
.await
|
||||
{
|
||||
println!("received [contact list]: {}", report.received.len())
|
||||
}
|
||||
|
||||
for author in authors.into_iter() {
|
||||
let filter = Filter::new()
|
||||
.authors(pubkeys.clone())
|
||||
.author(author)
|
||||
.kind(Kind::ContactList)
|
||||
.limit(4000);
|
||||
.limit(1);
|
||||
|
||||
if client
|
||||
.reconcile(filter, NegentropyOptions::default())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
for pubkey in pubkeys.into_iter() {
|
||||
let mut list: Vec<PublicKey> = Vec::new();
|
||||
let f = Filter::new()
|
||||
.author(pubkey)
|
||||
.kind(Kind::ContactList)
|
||||
.limit(1);
|
||||
let mut circles = state.circles.lock().await;
|
||||
let mut list: Vec<PublicKey> = Vec::new();
|
||||
|
||||
if let Ok(events) = client.database().query(vec![f]).await {
|
||||
if let Some(event) = events.into_iter().next() {
|
||||
for tag in event.tags.into_iter() {
|
||||
if let Some(TagStandard::PublicKey {
|
||||
public_key,
|
||||
uppercase: false,
|
||||
..
|
||||
}) = tag.to_standardized()
|
||||
{
|
||||
list.push(public_key)
|
||||
}
|
||||
}
|
||||
|
||||
if !list.is_empty() {
|
||||
state.circles.lock().await.insert(pubkey, list);
|
||||
};
|
||||
if let Ok(events) = client.database().query(vec![filter]).await {
|
||||
if let Some(event) = events.into_iter().next() {
|
||||
for tag in event.tags.into_iter() {
|
||||
if let Some(TagStandard::PublicKey {
|
||||
public_key,
|
||||
uppercase: false,
|
||||
..
|
||||
}) = tag.to_standardized()
|
||||
{
|
||||
list.push(public_key)
|
||||
}
|
||||
}
|
||||
|
||||
if !list.is_empty() {
|
||||
circles.insert(author, list);
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
handle.emit("synchronized", ()).unwrap();
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -28,13 +28,7 @@ pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result<RichEvent,
|
||||
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
||||
let filter = Filter::new().id(event_id);
|
||||
|
||||
match client
|
||||
.get_events_of(
|
||||
vec![filter],
|
||||
EventSource::both(Some(Duration::from_secs(5))),
|
||||
)
|
||||
.await
|
||||
{
|
||||
match client.database().query(vec![filter.clone()]).await {
|
||||
Ok(events) => {
|
||||
if let Some(event) = events.first() {
|
||||
let raw = event.as_json();
|
||||
@@ -46,7 +40,29 @@ pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result<RichEvent,
|
||||
|
||||
Ok(RichEvent { raw, parsed })
|
||||
} else {
|
||||
Err("Cannot found this event with current relay list".into())
|
||||
match client
|
||||
.get_events_of(
|
||||
vec![filter],
|
||||
EventSource::relays(Some(Duration::from_secs(5))),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(events) => {
|
||||
if let Some(event) = events.first() {
|
||||
let raw = event.as_json();
|
||||
let parsed = if event.kind == Kind::TextNote {
|
||||
Some(parse_event(&event.content).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(RichEvent { raw, parsed })
|
||||
} else {
|
||||
Err("Cannot found this event with current relay list".into())
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err.to_string()),
|
||||
@@ -191,15 +207,12 @@ pub async fn get_events_by(
|
||||
let author = PublicKey::parse(&public_key).map_err(|err| err.to_string())?;
|
||||
|
||||
let filter = Filter::new()
|
||||
.kinds(vec![Kind::TextNote])
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.author(author)
|
||||
.limit(limit as usize);
|
||||
|
||||
match client
|
||||
.get_events_of(
|
||||
vec![filter],
|
||||
EventSource::both(Some(Duration::from_secs(5))),
|
||||
)
|
||||
.get_events_of(vec![filter], EventSource::Database)
|
||||
.await
|
||||
{
|
||||
Ok(events) => {
|
||||
@@ -224,17 +237,11 @@ pub async fn get_events_by(
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_events_from_contacts(
|
||||
pub async fn get_local_events(
|
||||
until: Option<&str>,
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<Vec<RichEvent>, String> {
|
||||
let client = &state.client;
|
||||
let contact_list = state.contact_list.lock().await;
|
||||
let authors: Vec<PublicKey> = contact_list.iter().map(|f| f.public_key).collect();
|
||||
|
||||
if authors.is_empty() {
|
||||
return Err("Contact List is empty.".into());
|
||||
}
|
||||
|
||||
let as_of = match until {
|
||||
Some(until) => Timestamp::from_str(until).map_err(|err| err.to_string())?,
|
||||
@@ -244,8 +251,7 @@ pub async fn get_events_from_contacts(
|
||||
let filter = Filter::new()
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(FETCH_LIMIT)
|
||||
.until(as_of)
|
||||
.authors(authors);
|
||||
.until(as_of);
|
||||
|
||||
match client.database().query(vec![filter]).await {
|
||||
Ok(events) => {
|
||||
@@ -295,7 +301,7 @@ pub async fn get_group_events(
|
||||
|
||||
let filter = Filter::new()
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(FETCH_LIMIT)
|
||||
.limit(20)
|
||||
.until(as_of)
|
||||
.authors(authors);
|
||||
|
||||
@@ -341,7 +347,7 @@ pub async fn get_global_events(
|
||||
|
||||
let filter = Filter::new()
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(FETCH_LIMIT)
|
||||
.limit(20)
|
||||
.until(as_of);
|
||||
|
||||
match client
|
||||
@@ -385,7 +391,7 @@ pub async fn get_hashtag_events(
|
||||
};
|
||||
let filter = Filter::new()
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(FETCH_LIMIT)
|
||||
.limit(20)
|
||||
.until(as_of)
|
||||
.hashtags(hashtags);
|
||||
|
||||
|
||||
@@ -46,10 +46,7 @@ pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<
|
||||
.kind(Kind::Metadata)
|
||||
.limit(1);
|
||||
|
||||
match client
|
||||
.get_events_of(vec![filter], EventSource::Database)
|
||||
.await
|
||||
{
|
||||
match client.database().query(vec![filter.clone()]).await {
|
||||
Ok(events) => {
|
||||
if let Some(event) = events.first() {
|
||||
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
||||
@@ -58,7 +55,26 @@ pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<
|
||||
Err("Parse metadata failed".into())
|
||||
}
|
||||
} else {
|
||||
Ok(Metadata::new().as_json())
|
||||
match client
|
||||
.get_events_of(
|
||||
vec![filter],
|
||||
EventSource::relays(Some(Duration::from_secs(5))),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(events) => {
|
||||
if let Some(event) = events.first() {
|
||||
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
||||
Ok(metadata.as_json())
|
||||
} else {
|
||||
Err("Parse metadata failed".into())
|
||||
}
|
||||
} else {
|
||||
Ok(Metadata::new().as_json())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
@@ -80,9 +96,6 @@ pub async fn set_contact_list(
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Update local state
|
||||
state.contact_list.lock().await.clone_from(&contact_list);
|
||||
|
||||
match client.set_contact_list(contact_list).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(err) => Err(err.to_string()),
|
||||
@@ -94,18 +107,13 @@ pub async fn set_contact_list(
|
||||
pub async fn get_contact_list(state: State<'_, Nostr>) -> Result<Vec<String>, String> {
|
||||
let client = &state.client;
|
||||
|
||||
match client.get_contact_list(Some(Duration::from_secs(10))).await {
|
||||
match client.get_contact_list(Some(Duration::from_secs(5))).await {
|
||||
Ok(contact_list) => {
|
||||
if !contact_list.is_empty() {
|
||||
let list = contact_list
|
||||
.into_iter()
|
||||
.map(|f| f.public_key.to_hex())
|
||||
.collect();
|
||||
|
||||
Ok(list)
|
||||
} else {
|
||||
Err("Empty.".into())
|
||||
}
|
||||
let list = contact_list
|
||||
.into_iter()
|
||||
.map(|f| f.public_key.to_hex())
|
||||
.collect();
|
||||
Ok(list)
|
||||
}
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
@@ -144,16 +152,14 @@ pub async fn set_profile(profile: Profile, state: State<'_, Nostr>) -> Result<St
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn is_contact_list_empty(state: State<'_, Nostr>) -> Result<bool, ()> {
|
||||
Ok(state.contact_list.lock().await.is_empty())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn check_contact(hex: String, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||
let contact_list = state.contact_list.lock().await;
|
||||
let client = &state.client;
|
||||
let contact_list = client
|
||||
.get_contact_list(Some(Duration::from_secs(5)))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
match PublicKey::parse(&hex) {
|
||||
Ok(public_key) => match contact_list.iter().position(|x| x.public_key == public_key) {
|
||||
@@ -190,9 +196,6 @@ pub async fn toggle_contact(
|
||||
}
|
||||
}
|
||||
|
||||
// Update local state
|
||||
state.contact_list.lock().await.clone_from(&contact_list);
|
||||
|
||||
// Publish
|
||||
match client.set_contact_list(contact_list).await {
|
||||
Ok(event_id) => Ok(event_id.to_string()),
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::Settings;
|
||||
pub struct Meta {
|
||||
pub content: String,
|
||||
pub images: Vec<String>,
|
||||
pub videos: Vec<String>,
|
||||
pub events: Vec<String>,
|
||||
pub mentions: Vec<String>,
|
||||
pub hashtags: Vec<String>,
|
||||
@@ -44,7 +43,6 @@ const NOSTR_MENTIONS: [&str; 10] = [
|
||||
"Nostr:naddr1",
|
||||
];
|
||||
const IMAGES: [&str; 7] = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"];
|
||||
const VIDEOS: [&str; 5] = ["mp4", "mov", "avi", "webm", "mkv"];
|
||||
|
||||
pub fn get_latest_event(events: &[Event]) -> Option<&Event> {
|
||||
events.iter().next()
|
||||
@@ -115,7 +113,6 @@ pub async fn parse_event(content: &str) -> Meta {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut images = Vec::new();
|
||||
let mut videos = Vec::new();
|
||||
let mut text = content.to_string();
|
||||
|
||||
if !urls.is_empty() {
|
||||
@@ -135,12 +132,6 @@ pub async fn parse_event(content: &str) -> Meta {
|
||||
// Process the next item.
|
||||
continue;
|
||||
}
|
||||
if VIDEOS.contains(&ext) {
|
||||
text = text.replace(url_str, "");
|
||||
videos.push(url_str.to_string());
|
||||
// Process the next item.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the content type of URL via HEAD request
|
||||
@@ -167,7 +158,6 @@ pub async fn parse_event(content: &str) -> Meta {
|
||||
mentions,
|
||||
hashtags,
|
||||
images,
|
||||
videos,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +252,7 @@ pub async fn init_nip65(client: &Client, public_key: &str) {
|
||||
if let Ok(events) = client
|
||||
.get_events_of(
|
||||
vec![filter],
|
||||
EventSource::both(Some(Duration::from_secs(5))),
|
||||
EventSource::relays(Some(Duration::from_secs(5))),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -284,8 +274,6 @@ pub async fn init_nip65(client: &Client, public_key: &str) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Failed to get events for RelayList.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +332,6 @@ mod tests {
|
||||
|
||||
assert_eq!(meta.content, "Check this image: #cool @npub1");
|
||||
assert_eq!(meta.images, vec!["https://example.com/image.jpg"]);
|
||||
assert_eq!(meta.videos, Vec::<String>::new());
|
||||
assert_eq!(meta.hashtags, vec!["#cool"]);
|
||||
assert_eq!(meta.mentions, vec!["@npub1"]);
|
||||
}
|
||||
@@ -356,7 +343,6 @@ mod tests {
|
||||
|
||||
assert_eq!(meta.content, "Check this video: #cool @npub1");
|
||||
assert_eq!(meta.images, Vec::<String>::new());
|
||||
assert_eq!(meta.videos, vec!["https://example.com/video.mp4"]);
|
||||
assert_eq!(meta.hashtags, vec!["#cool"]);
|
||||
assert_eq!(meta.mentions, vec!["@npub1"]);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ pub mod common;
|
||||
|
||||
pub struct Nostr {
|
||||
client: Client,
|
||||
contact_list: Mutex<Vec<Contact>>,
|
||||
settings: Mutex<Settings>,
|
||||
circles: Mutex<HashMap<PublicKey, Vec<PublicKey>>>,
|
||||
}
|
||||
@@ -56,7 +55,7 @@ impl Default for Settings {
|
||||
image_resize_service: Some("https://wsrv.nl".to_string()),
|
||||
use_relay_hint: true,
|
||||
content_warning: true,
|
||||
trusted_only: true,
|
||||
trusted_only: false,
|
||||
display_avatar: true,
|
||||
display_zap_button: true,
|
||||
display_repost_button: true,
|
||||
@@ -83,8 +82,8 @@ struct Subscription {
|
||||
struct NewSettings(Settings);
|
||||
|
||||
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
||||
pub const FETCH_LIMIT: usize = 20;
|
||||
pub const NEWSFEED_NEG_LIMIT: usize = 256;
|
||||
pub const FETCH_LIMIT: usize = 100;
|
||||
pub const NEWSFEED_NEG_LIMIT: usize = 512;
|
||||
pub const NOTIFICATION_NEG_LIMIT: usize = 64;
|
||||
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
||||
|
||||
@@ -107,12 +106,12 @@ fn main() {
|
||||
get_private_key,
|
||||
delete_account,
|
||||
reset_password,
|
||||
is_account_sync,
|
||||
login,
|
||||
get_profile,
|
||||
set_profile,
|
||||
get_contact_list,
|
||||
set_contact_list,
|
||||
is_contact_list_empty,
|
||||
check_contact,
|
||||
toggle_contact,
|
||||
get_mention_list,
|
||||
@@ -135,7 +134,7 @@ fn main() {
|
||||
get_replies,
|
||||
subscribe_to,
|
||||
get_events_by,
|
||||
get_events_from_contacts,
|
||||
get_local_events,
|
||||
get_group_events,
|
||||
get_global_events,
|
||||
get_hashtag_events,
|
||||
@@ -222,9 +221,9 @@ fn main() {
|
||||
.gossip(true)
|
||||
.max_avg_latency(Duration::from_millis(500))
|
||||
.automatic_authentication(false)
|
||||
.connection_timeout(Some(Duration::from_secs(5)))
|
||||
.send_timeout(Some(Duration::from_secs(5)))
|
||||
.timeout(Duration::from_secs(5));
|
||||
.connection_timeout(Some(Duration::from_secs(20)))
|
||||
.send_timeout(Some(Duration::from_secs(10)))
|
||||
.timeout(Duration::from_secs(20));
|
||||
|
||||
// Setup nostr client
|
||||
let client = ClientBuilder::default()
|
||||
@@ -277,7 +276,6 @@ fn main() {
|
||||
// Create global state
|
||||
app.manage(Nostr {
|
||||
client,
|
||||
contact_list: Mutex::new(vec![]),
|
||||
settings: Mutex::new(Settings::default()),
|
||||
circles: Mutex::new(HashMap::new()),
|
||||
});
|
||||
@@ -308,7 +306,11 @@ fn main() {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let contact_list = state.contact_list.lock().await;
|
||||
let contact_list = client
|
||||
.get_contact_list(Some(Duration::from_secs(5)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if !contact_list.is_empty() {
|
||||
let authors: Vec<PublicKey> =
|
||||
contact_list.iter().map(|f| f.public_key).collect();
|
||||
@@ -459,6 +461,39 @@ fn main() {
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|window, event| {
|
||||
if let tauri::WindowEvent::Focused(focused) = event {
|
||||
if !focused {
|
||||
let handle = window.app_handle().to_owned();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let state = handle.state::<Nostr>();
|
||||
let client = &state.client;
|
||||
|
||||
if client.signer().await.is_ok() {
|
||||
if let Ok(contact_list) =
|
||||
client.get_contact_list(Some(Duration::from_secs(5))).await
|
||||
{
|
||||
let authors: Vec<PublicKey> =
|
||||
contact_list.iter().map(|f| f.public_key).collect();
|
||||
let newsfeed = Filter::new()
|
||||
.authors(authors)
|
||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||
.limit(NEWSFEED_NEG_LIMIT);
|
||||
|
||||
if client
|
||||
.reconcile(newsfeed, NegentropyOptions::default())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
handle.emit("synchronized", ()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.plugin(prevent_default())
|
||||
.plugin(tauri_plugin_decorum::init())
|
||||
.plugin(tauri_plugin_store::Builder::default().build())
|
||||
|
||||
Reference in New Issue
Block a user