From 881363e7035172bd983df8158b0fc5e3241e9544 Mon Sep 17 00:00:00 2001 From: Bytemalte Date: Fri, 16 Jan 2026 22:00:19 +0100 Subject: [PATCH] Fixed the message loading for allready sendet messages (bug for loading self sended messages) --- src-tauri/easy-nostr | 2 +- src-tauri/src/chat.rs | 75 ++++++++++++++------------ src/pages/chat.rs | 120 ++++++++++++++++++++++++------------------ 3 files changed, 113 insertions(+), 84 deletions(-) diff --git a/src-tauri/easy-nostr b/src-tauri/easy-nostr index 9c50d8a..7cebc49 160000 --- a/src-tauri/easy-nostr +++ b/src-tauri/easy-nostr @@ -1 +1 @@ -Subproject commit 9c50d8a3bab56687c8de2e0f190b24a65ba7084b +Subproject commit 7cebc490cad3d809425ea114b4b7c05393d064e9 diff --git a/src-tauri/src/chat.rs b/src-tauri/src/chat.rs index fbb2f3c..d7b115e 100644 --- a/src-tauri/src/chat.rs +++ b/src-tauri/src/chat.rs @@ -1,8 +1,8 @@ use easy_nostr::EasyNostr; use serde::Serialize; +use std::sync::Arc; use tauri::State; use tokio::sync::Mutex; -use std::sync::Arc; use tokio::time::{sleep, Duration}; pub struct NostrState(pub Mutex>>); @@ -10,6 +10,7 @@ pub struct NostrState(pub Mutex>>); #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct ChatMessage { + pub id: String, pub content: String, pub is_incoming: bool, pub created_at: u64, @@ -18,26 +19,27 @@ pub struct ChatMessage { async fn get_client(state: &State<'_, NostrState>) -> Result, String> { let mut guard = state.0.lock().await; if guard.is_none() { - println!("[BACKEND] Verbinde zu Nostr Relays..."); - let easy = EasyNostr::new("nsec1rz87pcjnhcl9yfkyq2pn3mlptluvxdgdgw6fyhhg3zmzw2zpwn0sm2q82f") - .await - .map_err(|e| e.to_string())?; - - // Nutze eine breite Auswahl an stabilen Relays + println!("[BACKEND] Initializing EasyNostr..."); + // Use your private key + let easy = + EasyNostr::new("nsec1rz87pcjnhcl9yfkyq2pn3mlptluvxdgdgw6fyhhg3zmzw2zpwn0sm2q82f") + .await + .map_err(|e| e.to_string())?; + easy.add_relays(vec![ "wss://relay.damus.io", "wss://nos.lol", "wss://relay.snort.social", - "wss://relay.malxte.de", - "wss://relay.0xchat.com", - "wss://nostr.wine" - ]).await.map_err(|e| e.to_string())?; + "wss://relay.primal.net", + "wss://nostr.wine", + ]) + .await + .map_err(|e| e.to_string())?; *guard = Some(Arc::new(easy)); - - // WICHTIG: Erhöhte Wartezeit beim Kaltstart, damit Relays DMs senden können - println!("[BACKEND] Warte auf Relay-Handshake..."); - sleep(Duration::from_millis(2000)).await; + + // Short sleep to allow initial connection + sleep(Duration::from_millis(1500)).await; } Ok(guard.as_ref().unwrap().clone()) } @@ -48,26 +50,30 @@ pub async fn get_nostr_messages( state: State<'_, NostrState>, ) -> Result, String> { let clean_npub = receiver_npub.trim().trim_matches('#').to_string(); - if clean_npub.is_empty() { return Ok(vec![]); } - - let client = get_client(&state).await?; - - // Wir versuchen es bis zu 2 mal, falls das erste Mal 0 zurückkommt (Nostr-Latenz) - let mut messages = client.get_private_messages(&clean_npub).await.unwrap_or_default(); - - if messages.is_empty() { - sleep(Duration::from_millis(500)).await; - messages = client.get_private_messages(&clean_npub).await.unwrap_or_default(); + if clean_npub.is_empty() { + return Ok(vec![]); } - let mut chat_msgs: Vec = messages.into_iter().map(|m| ChatMessage { - content: m.content, - is_incoming: m.is_incoming, - created_at: m.created_at.as_secs(), - }).collect(); + let client = get_client(&state).await?; - chat_msgs.sort_by_key(|m| m.created_at); - println!("[BACKEND] {} Nachrichten für {} geladen.", chat_msgs.len(), clean_npub); + // easy-nostr now handles the 10s timeout and heavy filtering internally + let messages = client + .get_private_messages(&clean_npub) + .await + .map_err(|e| e.to_string())?; + + // Map to frontend structure + let chat_msgs: Vec = messages + .into_iter() + .map(|m| ChatMessage { + id: m.id.to_string(), + content: m.content, + is_incoming: m.is_incoming, + created_at: m.created_at.as_secs(), // Convert Timestamp to u64 + }) + .collect(); + + // Sorting is already done in easy-nostr, but we ensure it here too Ok(chat_msgs) } @@ -79,8 +85,11 @@ pub async fn send_nostr_message( ) -> Result { let clean_npub = receiver_npub.trim().trim_matches('#').to_string(); let client = get_client(&state).await?; - let event_id = client.send_private_message(&clean_npub, &content) + + let event_id = client + .send_private_message(&clean_npub, &content) .await .map_err(|e| e.to_string())?; + Ok(event_id.to_string()) } diff --git a/src/pages/chat.rs b/src/pages/chat.rs index c816423..411c18d 100644 --- a/src/pages/chat.rs +++ b/src/pages/chat.rs @@ -1,10 +1,10 @@ +use gloo_timers::callback::Interval; +use js_sys::Date; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; use web_sys::HtmlInputElement; use yew::prelude::*; -use gloo_timers::callback::Interval; -use js_sys::Date; #[wasm_bindgen] extern "C" { @@ -15,6 +15,7 @@ extern "C" { #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] #[serde(rename_all = "camelCase")] pub struct ChatMessage { + pub id: Option, // ID ist None für optimistische Nachrichten pub content: String, pub is_incoming: bool, pub created_at: u64, @@ -22,58 +23,81 @@ pub struct ChatMessage { #[derive(Serialize)] #[serde(rename_all = "camelCase")] -struct ChatArgs { content: String, receiver_npub: String } +struct ChatArgs { + content: String, + receiver_npub: String, +} #[derive(Serialize)] #[serde(rename_all = "camelCase")] -struct GetArgs { receiver_npub: String } +struct GetArgs { + receiver_npub: String, +} #[function_component(Chat)] pub fn chat() -> Html { let messages = use_state(|| Vec::::new()); - let relay_connected = use_state(|| false); // Status für den Punkt + let relay_connected = use_state(|| false); let recipient_ref = use_node_ref(); let input_ref = use_node_ref(); let chat_bottom_ref = use_node_ref(); - // Polling Logik & Status Check + // --- Polling Logic mit Smart Merge --- { let messages = messages.clone(); let relay_connected = relay_connected.clone(); let recipient_ref = recipient_ref.clone(); + use_effect(move || { let interval = Interval::new(3000, move || { let messages = messages.clone(); let relay_connected = relay_connected.clone(); - + if let Some(recipient) = recipient_ref.cast::() { - let npub = recipient.value(); - let npub_clean = npub.trim().to_string(); + let npub = recipient.value().trim().to_string(); + if npub.is_empty() { + return; + } spawn_local(async move { - if !npub_clean.is_empty() { - let args = serde_wasm_bindgen::to_value(&GetArgs { - receiver_npub: npub_clean - }).unwrap(); - - let fetched: JsValue = invoke("get_nostr_messages", args).await; - - if let Ok(mut new_msgs) = serde_wasm_bindgen::from_value::>(fetched) { - new_msgs.sort_by_key(|m| m.created_at); - - // WICHTIG FÜR NEUSTART: - // Wenn lokal 0, aber Relay > 0 -> Sofort laden - let current_len = (*messages).len(); - if current_len == 0 && !new_msgs.is_empty() { - messages.set(new_msgs); - relay_connected.set(true); - } else if new_msgs.len() > current_len { - messages.set(new_msgs); - relay_connected.set(true); - } else if !new_msgs.is_empty() { - relay_connected.set(true); + let args = serde_wasm_bindgen::to_value(&GetArgs { + receiver_npub: npub, + }) + .unwrap(); + let fetched: JsValue = invoke("get_nostr_messages", args).await; + + if let Ok(relay_msgs) = + serde_wasm_bindgen::from_value::>(fetched) + { + if !relay_msgs.is_empty() { + relay_connected.set(true); + } + + let current_local_msgs = (*messages).clone(); + + // SMART MERGE LOGIC: + // 1. Take all messages from the relay. + // 2. Add local "optimistic" messages (id == None) that haven't appeared in relay_msgs yet. + let mut merged = relay_msgs.clone(); + + for local_msg in current_local_msgs.iter().filter(|m| m.id.is_none()) { + // Check if this message is now confirmed (by matching content) + let already_confirmed = + relay_msgs.iter().any(|rm| rm.content == local_msg.content); + if !already_confirmed { + merged.push(local_msg.clone()); } } + + merged.sort_by_key(|m| m.created_at); + + // Only update state if something actually changed to avoid flickering + if merged.len() != current_local_msgs.len() + || merged.last().map(|m| &m.id) + != current_local_msgs.last().map(|m| &m.id) + { + messages.set(merged); + } } }); } @@ -82,7 +106,7 @@ pub fn chat() -> Html { }); } - // Autoscroll + // --- Autoscroll --- { let chat_bottom_ref = chat_bottom_ref.clone(); let messages_len = messages.len(); @@ -102,13 +126,15 @@ pub fn chat() -> Html { e.prevent_default(); let input = input_ref.cast::().unwrap(); let recipient = recipient_ref.cast::().unwrap(); - + let content = input.value(); let receiver_npub = recipient.value(); if !content.trim().is_empty() && !receiver_npub.trim().is_empty() { + // Optimistic UI Update: Nachricht sofort ohne ID anzeigen let mut current = (*messages).clone(); current.push(ChatMessage { + id: None, content: content.clone(), is_incoming: false, created_at: (Date::now() / 1000.0) as u64, @@ -117,7 +143,11 @@ pub fn chat() -> Html { input.set_value(""); spawn_local(async move { - let args = serde_wasm_bindgen::to_value(&ChatArgs { content, receiver_npub }).unwrap(); + let args = serde_wasm_bindgen::to_value(&ChatArgs { + content, + receiver_npub, + }) + .unwrap(); let _ = invoke("send_nostr_message", args).await; }); } @@ -133,36 +163,26 @@ pub fn chat() -> Html { {if *relay_connected { " Relays aktiv" } else { " Verbinde..." }} -
- +
-
{ for (*messages).iter().map(|msg| html! { -
+
{ if msg.is_incoming { "Empfangen" } else { "Du" } } + { if msg.id.is_none() { " (sendet...)" } else { "" } }
{ &msg.content }
}) }
-
- +