feat: use negentropy as much as possible
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
wss://relay.damus.io,
|
wss://relay.damus.io,
|
||||||
wss://relay.nostr.net,
|
wss://relay.nostr.net,
|
||||||
wss://nos.lol,
|
wss://nostr.fmt.wiz.biz,
|
||||||
|
wss://offchain.pub,
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tauri::{Emitter, Manager, State};
|
use tauri::{Emitter, Manager, State};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
use crate::{common::init_nip65, Nostr, NOTIFICATION_SUB_ID};
|
use crate::{
|
||||||
|
common::{get_latest_event, init_nip65},
|
||||||
|
Nostr, NOTIFICATION_SUB_ID,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
struct Account {
|
struct Account {
|
||||||
@@ -217,6 +221,17 @@ pub fn is_account_sync(id: String, handle: tauri::AppHandle) -> bool {
|
|||||||
fs::metadata(config_dir.join(id)).is_ok()
|
fs::metadata(config_dir.join(id)).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub fn create_sync_file(id: String, handle: tauri::AppHandle) -> bool {
|
||||||
|
let config_dir = handle
|
||||||
|
.path()
|
||||||
|
.app_config_dir()
|
||||||
|
.expect("Error: app config directory not found.");
|
||||||
|
|
||||||
|
File::create(config_dir.join(id)).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
@@ -273,93 +288,27 @@ pub async fn login(
|
|||||||
|
|
||||||
// NIP-03: Get user's contact list
|
// NIP-03: Get user's contact list
|
||||||
let contact_list = {
|
let contact_list = {
|
||||||
let contacts = client.get_contact_list(None).await.unwrap();
|
if let Ok(contacts) = client.get_contact_list(Some(Duration::from_secs(5))).await {
|
||||||
// Update app's state
|
state.contact_list.lock().await.clone_from(&contacts);
|
||||||
state.contact_list.lock().await.clone_from(&contacts);
|
contacts
|
||||||
// Return
|
} else {
|
||||||
contacts
|
Vec::new()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run seperate thread for syncing data
|
let public_key_clone = public_key.clone();
|
||||||
let pk = public_key.clone();
|
|
||||||
|
// Run seperate thread for sync
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let config_dir = handle.path().app_config_dir().unwrap();
|
|
||||||
let state = handle.state::<Nostr>();
|
let state = handle.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
let author = PublicKey::from_str(&public_key).unwrap();
|
||||||
// Convert current user to PublicKey
|
|
||||||
let author = PublicKey::from_str(&pk).unwrap();
|
|
||||||
|
|
||||||
// Fetching user's metadata
|
|
||||||
if let Ok(report) = client
|
|
||||||
.reconcile(
|
|
||||||
Filter::new()
|
|
||||||
.author(author)
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::Metadata,
|
|
||||||
Kind::ContactList,
|
|
||||||
Kind::MuteList,
|
|
||||||
Kind::Bookmarks,
|
|
||||||
Kind::Interests,
|
|
||||||
Kind::InterestSet,
|
|
||||||
Kind::FollowSet,
|
|
||||||
Kind::PinList,
|
|
||||||
Kind::EventDeletion,
|
|
||||||
])
|
|
||||||
.limit(1000),
|
|
||||||
NegentropyOptions::default(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
println!("Received: {}", report.received.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetching user's events
|
|
||||||
if let Ok(report) = client
|
|
||||||
.reconcile(
|
|
||||||
Filter::new()
|
|
||||||
.author(author)
|
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
|
||||||
.limit(200),
|
|
||||||
NegentropyOptions::default(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
println!("Received: {}", report.received.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetching user's notification
|
|
||||||
if let Ok(report) = client
|
|
||||||
.reconcile(
|
|
||||||
Filter::new()
|
|
||||||
.pubkey(author)
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Reaction,
|
|
||||||
Kind::ZapReceipt,
|
|
||||||
])
|
|
||||||
.limit(200),
|
|
||||||
NegentropyOptions::default(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
println!("Received: {}", report.received.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe for new notification
|
// Subscribe for new notification
|
||||||
if let Ok(e) = client
|
if let Ok(e) = client
|
||||||
.subscribe_with_id(
|
.subscribe_with_id(
|
||||||
SubscriptionId::new(NOTIFICATION_SUB_ID),
|
SubscriptionId::new(NOTIFICATION_SUB_ID),
|
||||||
vec![Filter::new()
|
vec![Filter::new().pubkey(author).since(Timestamp::now())],
|
||||||
.pubkey(author)
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Reaction,
|
|
||||||
Kind::ZapReceipt,
|
|
||||||
])
|
|
||||||
.since(Timestamp::now())],
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -371,13 +320,82 @@ pub async fn login(
|
|||||||
if !contact_list.is_empty() {
|
if !contact_list.is_empty() {
|
||||||
let authors: Vec<PublicKey> = contact_list.iter().map(|f| f.public_key).collect();
|
let authors: Vec<PublicKey> = contact_list.iter().map(|f| f.public_key).collect();
|
||||||
|
|
||||||
// Fetching contact's metadata
|
// Syncing all metadata events from contact list
|
||||||
if let Ok(report) = client
|
if let Ok(report) = client
|
||||||
.reconcile(
|
.reconcile(
|
||||||
Filter::new()
|
Filter::new()
|
||||||
.authors(authors.clone())
|
.authors(authors.clone())
|
||||||
.kinds(vec![Kind::Metadata, Kind::ContactList, Kind::MuteList])
|
.kinds(vec![Kind::Metadata, Kind::ContactList])
|
||||||
.limit(3000),
|
.limit(authors.len() * 10),
|
||||||
|
NegentropyOptions::default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Received: {}", report.received.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syncing all events from contact list
|
||||||
|
if let Ok(report) = client
|
||||||
|
.reconcile(
|
||||||
|
Filter::new()
|
||||||
|
.authors(authors.clone())
|
||||||
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
|
.limit(authors.len() * 50),
|
||||||
|
NegentropyOptions::default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Received: {}", report.received.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the trusted public key list from contact list
|
||||||
|
// TODO: create a cached file
|
||||||
|
let mut trusted_list: HashSet<PublicKey> = HashSet::new();
|
||||||
|
|
||||||
|
for author in authors.into_iter() {
|
||||||
|
trusted_list.insert(author);
|
||||||
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.author(author)
|
||||||
|
.kind(Kind::ContactList)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if let Ok(events) = client.database().query(vec![filter]).await {
|
||||||
|
if let Some(event) = get_latest_event(&events) {
|
||||||
|
for tag in event.tags.iter() {
|
||||||
|
if let Some(TagStandard::PublicKey {
|
||||||
|
public_key,
|
||||||
|
uppercase: false,
|
||||||
|
..
|
||||||
|
}) = tag.to_owned().to_standardized()
|
||||||
|
{
|
||||||
|
trusted_list.insert(public_key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update app's state
|
||||||
|
state.trusted_list.lock().await.clone_from(&trusted_list);
|
||||||
|
|
||||||
|
// Syncing all user's events
|
||||||
|
if let Ok(report) = client
|
||||||
|
.reconcile(Filter::new().author(author), NegentropyOptions::default())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Received: {}", report.received.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syncing all tagged events for current user
|
||||||
|
if let Ok(report) = client
|
||||||
|
.reconcile(
|
||||||
|
Filter::new().pubkey(author).kinds(vec![
|
||||||
|
Kind::TextNote,
|
||||||
|
Kind::Repost,
|
||||||
|
Kind::Reaction,
|
||||||
|
Kind::ZapReceipt,
|
||||||
|
]),
|
||||||
NegentropyOptions::default(),
|
NegentropyOptions::default(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -385,57 +403,30 @@ pub async fn login(
|
|||||||
println!("Received: {}", report.received.len())
|
println!("Received: {}", report.received.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetching contact's events
|
// Syncing all events for trusted list
|
||||||
|
let trusted: Vec<PublicKey> = trusted_list.into_iter().collect();
|
||||||
if let Ok(report) = client
|
if let Ok(report) = client
|
||||||
.reconcile(
|
.reconcile(
|
||||||
Filter::new()
|
Filter::new()
|
||||||
.authors(authors.clone())
|
.authors(trusted)
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::Metadata, Kind::TextNote, Kind::Repost])
|
||||||
.limit(1000),
|
.limit(20000),
|
||||||
NegentropyOptions::default(),
|
NegentropyOptions::default(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
println!("Received: {}", report.received.len());
|
println!("Received: {}", report.received.len())
|
||||||
|
|
||||||
// Save the process status
|
|
||||||
let _ = File::create(config_dir.join(author.to_bech32().unwrap()));
|
|
||||||
// Update frontend
|
|
||||||
handle.emit("synchronized", ()).unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
for author in authors.into_iter() {
|
|
||||||
let filter = Filter::new()
|
|
||||||
.author(author)
|
|
||||||
.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![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();
|
// Wait a little longer
|
||||||
|
// TODO: remove?
|
||||||
|
sleep(Duration::from_secs(5)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle
|
||||||
|
.emit("neg_synchronized", ())
|
||||||
|
.expect("Something wrong!");
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(public_key)
|
Ok(public_key_clone)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<
|
|||||||
|
|
||||||
match client.database().query(vec![filter.clone()]).await {
|
match client.database().query(vec![filter.clone()]).await {
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
if let Some(event) = events.first() {
|
if let Some(event) = get_latest_event(&events) {
|
||||||
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
||||||
Ok(metadata.as_json())
|
Ok(metadata.as_json())
|
||||||
} else {
|
} else {
|
||||||
@@ -63,7 +63,7 @@ pub async fn get_profile(id: Option<String>, state: State<'_, Nostr>) -> Result<
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(events) => {
|
Ok(events) => {
|
||||||
if let Some(event) = events.first() {
|
if let Some(event) = get_latest_event(&events) {
|
||||||
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
if let Ok(metadata) = Metadata::from_json(&event.content) {
|
||||||
Ok(metadata.as_json())
|
Ok(metadata.as_json())
|
||||||
} else {
|
} else {
|
||||||
@@ -652,9 +652,8 @@ pub async fn verify_nip05(id: String, nip05: &str) -> Result<bool, String> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn is_trusted_user(id: String, state: State<'_, Nostr>) -> Result<bool, String> {
|
pub async fn is_trusted_user(id: String, state: State<'_, Nostr>) -> Result<bool, String> {
|
||||||
let circles = &state.circles.lock().await;
|
let trusted_list = &state.trusted_list.lock().await;
|
||||||
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
||||||
let trusted = circles.values().any(|v| v.contains(&public_key));
|
|
||||||
|
|
||||||
Ok(trusted)
|
Ok(trusted_list.contains(&public_key))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,12 @@
|
|||||||
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
use border::WebviewWindowExt as BorderWebviewWindowExt;
|
||||||
use commands::{account::*, event::*, metadata::*, relay::*, window::*};
|
use commands::{account::*, event::*, metadata::*, relay::*, window::*};
|
||||||
use common::parse_event;
|
use common::parse_event;
|
||||||
use nostr_relay_builder::prelude::*;
|
use nostr_sdk::prelude::{Profile as DatabaseProfile, *};
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use specta_typescript::Typescript;
|
use specta_typescript::Typescript;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashSet,
|
||||||
fs,
|
fs,
|
||||||
io::{self, BufRead},
|
io::{self, BufRead},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@@ -32,7 +31,7 @@ pub struct Nostr {
|
|||||||
client: Client,
|
client: Client,
|
||||||
settings: Mutex<Settings>,
|
settings: Mutex<Settings>,
|
||||||
contact_list: Mutex<Vec<Contact>>,
|
contact_list: Mutex<Vec<Contact>>,
|
||||||
circles: Mutex<HashMap<PublicKey, Vec<PublicKey>>>,
|
trusted_list: Mutex<HashSet<PublicKey>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Type)]
|
#[derive(Clone, Serialize, Deserialize, Type)]
|
||||||
@@ -84,7 +83,6 @@ struct NewSettings(Settings);
|
|||||||
|
|
||||||
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
pub const DEFAULT_DIFFICULTY: u8 = 21;
|
||||||
pub const FETCH_LIMIT: usize = 100;
|
pub const FETCH_LIMIT: usize = 100;
|
||||||
pub const NEWSFEED_NEG_LIMIT: usize = 512;
|
|
||||||
pub const NOTIFICATION_NEG_LIMIT: usize = 64;
|
pub const NOTIFICATION_NEG_LIMIT: usize = 64;
|
||||||
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
pub const NOTIFICATION_SUB_ID: &str = "lume_notification";
|
||||||
|
|
||||||
@@ -108,6 +106,7 @@ fn main() {
|
|||||||
delete_account,
|
delete_account,
|
||||||
reset_password,
|
reset_password,
|
||||||
is_account_sync,
|
is_account_sync,
|
||||||
|
create_sync_file,
|
||||||
login,
|
login,
|
||||||
get_profile,
|
get_profile,
|
||||||
set_profile,
|
set_profile,
|
||||||
@@ -274,7 +273,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect
|
// Connect
|
||||||
client.connect().await;
|
client.connect_with_timeout(Duration::from_secs(20)).await;
|
||||||
|
|
||||||
client
|
client
|
||||||
});
|
});
|
||||||
@@ -284,7 +283,7 @@ fn main() {
|
|||||||
client,
|
client,
|
||||||
settings: Mutex::new(Settings::default()),
|
settings: Mutex::new(Settings::default()),
|
||||||
contact_list: Mutex::new(Vec::new()),
|
contact_list: Mutex::new(Vec::new()),
|
||||||
circles: Mutex::new(HashMap::new()),
|
trusted_list: Mutex::new(HashSet::new()),
|
||||||
});
|
});
|
||||||
|
|
||||||
Subscription::listen_any(app, move |event| {
|
Subscription::listen_any(app, move |event| {
|
||||||
@@ -432,14 +431,15 @@ fn main() {
|
|||||||
// Send native notification
|
// Send native notification
|
||||||
if allow_notification {
|
if allow_notification {
|
||||||
let author = client
|
let author = client
|
||||||
.fetch_metadata(
|
.database()
|
||||||
event.pubkey,
|
.profile(event.pubkey)
|
||||||
Some(Duration::from_secs(3)),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|_| Metadata::new());
|
.unwrap_or_else(|_| {
|
||||||
|
DatabaseProfile::new(event.pubkey, Metadata::new())
|
||||||
|
});
|
||||||
|
let metadata = author.metadata();
|
||||||
|
|
||||||
send_event_notification(&event, author, &handle_clone);
|
send_event_notification(&event, metadata, &handle_clone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,30 +472,36 @@ fn main() {
|
|||||||
if let tauri::WindowEvent::Focused(focused) = event {
|
if let tauri::WindowEvent::Focused(focused) = event {
|
||||||
if !focused {
|
if !focused {
|
||||||
let handle = window.app_handle().to_owned();
|
let handle = window.app_handle().to_owned();
|
||||||
|
let config_dir = handle.path().app_config_dir().unwrap();
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = handle.state::<Nostr>();
|
let state = handle.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
if client.signer().await.is_ok() {
|
if let Ok(signer) = client.signer().await {
|
||||||
if let Ok(contact_list) =
|
let public_key = signer.public_key().await.unwrap();
|
||||||
client.get_contact_list(Some(Duration::from_secs(5))).await
|
let bech32 = public_key.to_bech32().unwrap();
|
||||||
{
|
|
||||||
let authors: Vec<PublicKey> =
|
|
||||||
contact_list.iter().map(|f| f.public_key).collect();
|
|
||||||
|
|
||||||
if client
|
if fs::metadata(config_dir.join(bech32)).is_ok() {
|
||||||
.reconcile(
|
if let Ok(contact_list) =
|
||||||
Filter::new()
|
client.get_contact_list(Some(Duration::from_secs(5))).await
|
||||||
.authors(authors)
|
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
|
||||||
.limit(NEWSFEED_NEG_LIMIT),
|
|
||||||
NegentropyOptions::default(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
{
|
{
|
||||||
handle.emit("synchronized", ()).unwrap();
|
let authors: Vec<PublicKey> =
|
||||||
|
contact_list.iter().map(|f| f.public_key).collect();
|
||||||
|
|
||||||
|
if client
|
||||||
|
.reconcile(
|
||||||
|
Filter::new()
|
||||||
|
.authors(authors)
|
||||||
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
|
.limit(1000),
|
||||||
|
NegentropyOptions::default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
handle.emit("synchronized", ()).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ async resetPassword(key: string, password: string) : Promise<Result<null, string
|
|||||||
async isAccountSync(id: string) : Promise<boolean> {
|
async isAccountSync(id: string) : Promise<boolean> {
|
||||||
return await TAURI_INVOKE("is_account_sync", { id });
|
return await TAURI_INVOKE("is_account_sync", { id });
|
||||||
},
|
},
|
||||||
|
async createSyncFile(id: string) : Promise<boolean> {
|
||||||
|
return await TAURI_INVOKE("create_sync_file", { id });
|
||||||
|
},
|
||||||
async login(account: string, password: string) : Promise<Result<string, string>> {
|
async login(account: string, password: string) : Promise<Result<string, string>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("login", { account, password }) };
|
return { status: "ok", data: await TAURI_INVOKE("login", { account, password }) };
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const RepostNote = memo(function RepostNote({
|
|||||||
event: LumeEvent;
|
event: LumeEvent;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const { isLoading, isError, data } = useEvent(event.repostId);
|
const { isLoading, isError, data } = useEvent(event.repostId, event.content);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Note.Root className={cn("", className)}>
|
<Note.Root className={cn("", className)}>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { appSettings } from "@/commons";
|
|||||||
import { Spinner } from "@/components";
|
import { Spinner } from "@/components";
|
||||||
import type { QueryClient } from "@tanstack/react-query";
|
import type { QueryClient } from "@tanstack/react-query";
|
||||||
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
|
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import type { OsType } from "@tauri-apps/plugin-os";
|
import type { OsType } from "@tauri-apps/plugin-os";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
@@ -17,6 +18,8 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
|
const { queryClient } = Route.useRouteContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = events.newSettings.listen((data) => {
|
const unlisten = events.newSettings.listen((data) => {
|
||||||
appSettings.setState((state) => {
|
appSettings.setState((state) => {
|
||||||
@@ -29,6 +32,16 @@ function Screen() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = listen("synchronized", async () => {
|
||||||
|
await queryClient.invalidateQueries();
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return <Outlet />;
|
return <Outlet />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { useCallback, useRef } from "react";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({
|
||||||
@@ -18,7 +17,6 @@ export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({
|
|||||||
export function Screen() {
|
export function Screen() {
|
||||||
const group = Route.useLoaderData();
|
const group = Route.useLoaderData();
|
||||||
const params = Route.useParams();
|
const params = Route.useParams();
|
||||||
const { queryClient } = Route.useRouteContext();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -84,16 +82,6 @@ export function Screen() {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unlisten = listen("synchronized", async () => {
|
|
||||||
await queryClient.invalidateQueries({ queryKey: ["groups", params.id] });
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlisten.then((f) => f());
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { useCallback, useRef } from "react";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({
|
||||||
@@ -18,7 +17,6 @@ export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({
|
|||||||
export function Screen() {
|
export function Screen() {
|
||||||
const hashtags = Route.useLoaderData();
|
const hashtags = Route.useLoaderData();
|
||||||
const params = Route.useParams();
|
const params = Route.useParams();
|
||||||
const { queryClient } = Route.useRouteContext();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -84,18 +82,6 @@ export function Screen() {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unlisten = listen("synchronized", async () => {
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: ["hashtags", params.id],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlisten.then((f) => f());
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ArrowDown, ArrowUp } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { type InfiniteData, useInfiniteQuery } from "@tanstack/react-query";
|
import { type InfiniteData, useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
@@ -30,7 +29,6 @@ export const Route = createLazyFileRoute("/columns/_layout/newsfeed")({
|
|||||||
|
|
||||||
export function Screen() {
|
export function Screen() {
|
||||||
const contacts = Route.useLoaderData();
|
const contacts = Route.useLoaderData();
|
||||||
const { queryClient } = Route.useRouteContext();
|
|
||||||
const { label, account } = Route.useSearch();
|
const { label, account } = Route.useSearch();
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -95,16 +93,6 @@ export function Screen() {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unlisten = listen("synchronized", async () => {
|
|
||||||
await queryClient.invalidateQueries({ queryKey: [label, account] });
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unlisten.then((f) => f());
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { commands } from "@/commands.gen";
|
||||||
import { Frame, Spinner } from "@/components";
|
import { Frame, Spinner } from "@/components";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
@@ -21,13 +22,19 @@ function Screen() {
|
|||||||
const search = Route.useSearch();
|
const search = Route.useSearch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = listen("synchronized", () => {
|
const unlisten = listen("neg_synchronized", async () => {
|
||||||
navigate({
|
const status = await commands.createSyncFile(search.account);
|
||||||
to: "/$account/home",
|
|
||||||
// @ts-ignore, this is tanstack router bug
|
if (status) {
|
||||||
params: { account: search.account },
|
navigate({
|
||||||
replace: true,
|
to: "/$account/home",
|
||||||
});
|
// @ts-ignore, this is tanstack router bug
|
||||||
|
params: { account: search.account },
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error("System error.");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -43,7 +50,7 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<p className="text-sm text-neutral-600 dark:text-neutral-40">
|
<p className="text-sm text-neutral-600 dark:text-neutral-40">
|
||||||
Fetching necessary data for the first time login...
|
Syncing all necessary data for the first time login...
|
||||||
</p>
|
</p>
|
||||||
</Frame>
|
</Frame>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,11 +4,22 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { LumeEvent } from "../event";
|
import { LumeEvent } from "../event";
|
||||||
|
|
||||||
export function useEvent(id: string) {
|
export function useEvent(id: string, repost?: string) {
|
||||||
const { isLoading, isError, error, data } = useQuery({
|
const { isLoading, isError, error, data } = useQuery({
|
||||||
queryKey: ["event", id],
|
queryKey: ["event", id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
|
if (repost?.length) {
|
||||||
|
const nostrEvent: NostrEvent = JSON.parse(repost);
|
||||||
|
const res = await commands.getEventMeta(nostrEvent.content);
|
||||||
|
|
||||||
|
if (res.status === "ok") {
|
||||||
|
nostrEvent.meta = res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LumeEvent(nostrEvent);
|
||||||
|
}
|
||||||
|
|
||||||
// Validate ID
|
// Validate ID
|
||||||
let normalizeId: string = id
|
let normalizeId: string = id
|
||||||
.replace("nostr:", "")
|
.replace("nostr:", "")
|
||||||
|
|||||||
Reference in New Issue
Block a user