From a3703bc3485c020a58d6407bc9f791e4464cd7ac Mon Sep 17 00:00:00 2001 From: reya <123083837+reyamir@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:00:21 +0700 Subject: [PATCH] feat: improve relay management --- src-tauri/resources/relays.txt | 2 +- src-tauri/src/commands/account.rs | 12 ++++--- src-tauri/src/commands/relay.rs | 48 ++++++++++++++------------ src-tauri/src/main.rs | 22 ++++++++---- src/commands.ts | 4 +-- src/main.tsx | 12 ++++++- src/routes/$account.chats.$id.lazy.tsx | 9 ----- src/routes/$account.chats.$id.tsx | 22 +++++++++++- src/routes/$account.relays.tsx | 2 +- src/routes/__root.tsx | 2 ++ 10 files changed, 87 insertions(+), 48 deletions(-) diff --git a/src-tauri/resources/relays.txt b/src-tauri/resources/relays.txt index d9119ab..336148f 100644 --- a/src-tauri/resources/relays.txt +++ b/src-tauri/resources/relays.txt @@ -1,4 +1,4 @@ wss://purplepag.es/, wss://directory.yabu.me/, wss://user.kindpag.es/, -wss://relay.nos.social/, +wss://bostr.online/, diff --git a/src-tauri/src/commands/account.rs b/src-tauri/src/commands/account.rs index c799aca..b1918d2 100644 --- a/src-tauri/src/commands/account.rs +++ b/src-tauri/src/commands/account.rs @@ -29,10 +29,13 @@ pub fn get_accounts() -> Vec { #[specta::specta] pub async fn get_metadata(user_id: String, state: State<'_, Nostr>) -> Result { let client = &state.client; + let bootstrap_relays = state.bootstrap_relays.lock().await.clone(); + let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?; let filter = Filter::new().author(public_key).kind(Kind::Metadata).limit(1); - match client.get_events_of(vec![filter], Some(Duration::from_secs(2))).await { + match client.get_events_from(bootstrap_relays, vec![filter], Some(Duration::from_secs(2))).await + { Ok(events) => { if let Some(event) = events.first() { Ok(Metadata::from_json(&event.content).unwrap_or(Metadata::new()).as_json()) @@ -197,7 +200,7 @@ pub async fn login( let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1); - if let Ok(events) = client.get_events_of(vec![inbox], Some(Duration::from_secs(5))).await { + if let Ok(events) = client.get_events_of(vec![inbox], Some(Duration::from_secs(3))).await { if let Some(event) = events.into_iter().next() { let urls = event .tags() @@ -212,8 +215,9 @@ pub async fn login( .collect::>(); for url in urls.iter() { - let _ = client.add_relay(url).await; - let _ = client.connect_relay(url).await; + if let Err(e) = client.add_relay(url).await { + println!("Connect relay failed: {}", e) + } } // Workaround for https://github.com/rust-nostr/nostr/issues/509 diff --git a/src-tauri/src/commands/relay.rs b/src-tauri/src/commands/relay.rs index 1008c1d..6d7051c 100644 --- a/src-tauri/src/commands/relay.rs +++ b/src-tauri/src/commands/relay.rs @@ -8,13 +8,14 @@ use tauri::{Manager, State}; use crate::Nostr; -async fn get_nip65_list(public_key: PublicKey, client: &Client) -> Vec { +async fn connect_nip65_relays(public_key: PublicKey, client: &Client) -> Vec { let filter = Filter::new().author(public_key).kind(Kind::RelayList).limit(1); let mut relay_list: Vec = Vec::new(); - if let Ok(events) = client.get_events_of(vec![filter], Some(Duration::from_secs(10))).await { + if let Ok(events) = client.get_events_of(vec![filter], Some(Duration::from_secs(2))).await { if let Some(event) = events.first() { for (url, ..) in nip65::extract_relay_list(event) { + let _ = client.add_relay(url).await; relay_list.push(url.to_string()) } } @@ -23,6 +24,14 @@ async fn get_nip65_list(public_key: PublicKey, client: &Client) -> Vec { relay_list } +async fn disconnect_nip65_relays(relays: Vec, client: &Client) { + for relay in relays.iter() { + if let Err(e) = client.disconnect_relay(relay).await { + println!("Disconnect failed: {}", e) + } + } +} + #[tauri::command] #[specta::specta] pub fn get_bootstrap_relays(app: tauri::AppHandle) -> Result, String> { @@ -51,7 +60,7 @@ pub fn set_bootstrap_relays(relays: String, app: tauri::AppHandle) -> Result<(), #[tauri::command] #[specta::specta] -pub async fn get_inbox_relays( +pub async fn collect_inbox_relays( user_id: String, state: State<'_, Nostr>, ) -> Result, String> { @@ -106,12 +115,16 @@ pub async fn connect_inbox_relays( ) -> Result, String> { let client = &state.client; let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?; + + // let nip65_relays = connect_nip65_relays(public_key, client).await; let mut inbox_relays = state.inbox_relays.lock().await; if !ignore_cache { if let Some(relays) = inbox_relays.get(&public_key) { for relay in relays { - let _ = client.connect_relay(relay).await; + if let Err(e) = client.connect_relay(relay).await { + println!("Connect relay failed: {}", e) + } } return Ok(relays.to_owned()); }; @@ -127,33 +140,22 @@ pub async fn connect_inbox_relays( for tag in &event.tags { if let Some(TagStandard::Relay(relay)) = tag.as_standardized() { let url = relay.to_string(); - let _ = client.add_relay(&url).await; - let _ = client.connect_relay(&url).await; + + if let Err(e) = client.add_relay(&url).await { + println!("Connect relay failed: {}", e) + }; relays.push(url) } } + // Update state inbox_relays.insert(public_key, relays.clone()); + + // Disconnect user's nip65 relays to save bandwidth + // disconnect_nip65_relays(nip65_relays, client).await; } - // Workaround for https://github.com/rust-nostr/nostr/issues/509 - // TODO: remove this - // let relays_clone = relays.clone(); - /*tauri::async_runtime::spawn(async move { - let state = handle.state::(); - let client = &state.client; - - client - .get_events_from( - relays_clone, - vec![Filter::new().kind(Kind::TextNote).limit(0)], - Some(Duration::from_secs(5)), - ) - .await - }); - */ - Ok(relays) } Err(e) => Err(e.to_string()), diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4abdb95..29f6644 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -24,6 +24,7 @@ mod commands; pub struct Nostr { client: Client, + bootstrap_relays: Mutex>, inbox_relays: Mutex>>, } @@ -34,7 +35,7 @@ fn main() { let builder = Builder::::new().commands(collect_commands![ get_bootstrap_relays, set_bootstrap_relays, - get_inbox_relays, + collect_inbox_relays, set_inbox_relays, connect_inbox_relays, disconnect_inbox_relays, @@ -97,7 +98,7 @@ fn main() { #[cfg(target_os = "macos")] main_window.add_border(None); - let client = tauri::async_runtime::block_on(async move { + let (client, bootstrap_relays) = tauri::async_runtime::block_on(async move { // Create data folder if not exist let dir = handle.path().app_config_dir().expect("App config directory not found."); let _ = fs::create_dir_all(dir.clone()); @@ -107,6 +108,7 @@ fn main() { // Setup nostr client let opts = Options::new() + .autoconnect(true) .timeout(Duration::from_secs(40)) .send_timeout(Some(Duration::from_secs(10))) .connection_timeout(Some(Duration::from_secs(10))); @@ -143,14 +145,22 @@ fn main() { } } - // Connect - client.connect().await; + let bootstrap_relays = client + .relays() + .await + .keys() + .map(|item| item.to_string()) + .collect::>(); - client + (client, bootstrap_relays) }); // Create global state - app.manage(Nostr { client, inbox_relays: Mutex::new(HashMap::new()) }); + app.manage(Nostr { + client, + bootstrap_relays: Mutex::new(bootstrap_relays), + inbox_relays: Mutex::new(HashMap::new()), + }); Ok(()) }) diff --git a/src/commands.ts b/src/commands.ts index a15b9f1..4c2d738 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -21,9 +21,9 @@ async setBootstrapRelays(relays: string) : Promise> { else return { status: "error", error: e as any }; } }, -async getInboxRelays(userId: string) : Promise> { +async collectInboxRelays(userId: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("get_inbox_relays", { userId }) }; + return { status: "ok", data: await TAURI_INVOKE("collect_inbox_relays", { userId }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; diff --git a/src/main.tsx b/src/main.tsx index edd2679..2d7679f 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,19 +1,29 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { RouterProvider, createRouter } from "@tanstack/react-router"; import { type } from "@tauri-apps/plugin-os"; +import { LRUCache } from "lru-cache"; import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; import "./global.css"; +import { commands } from "./commands"; // Import the generated route tree import { routeTree } from "./routes.gen"; -const queryClient = new QueryClient(); const platform = type(); +const queryClient = new QueryClient(); +const chatManager = new LRUCache({ + max: 3, + dispose: async (v, _) => { + console.log("disconnect: ", v); + await commands.disconnectInboxRelays(v); + }, +}); const router = createRouter({ routeTree, context: { queryClient, + chatManager, platform, }, }); diff --git a/src/routes/$account.chats.$id.lazy.tsx b/src/routes/$account.chats.$id.lazy.tsx index ee48f8f..15453cc 100644 --- a/src/routes/$account.chats.$id.lazy.tsx +++ b/src/routes/$account.chats.$id.lazy.tsx @@ -33,17 +33,8 @@ type EventPayload = { export const Route = createLazyFileRoute("/$account/chats/$id")({ component: Screen, - pendingComponent: Pending, }); -function Pending() { - return ( -
- -
- ); -} - function Screen() { return (
diff --git a/src/routes/$account.chats.$id.tsx b/src/routes/$account.chats.$id.tsx index 132033a..db71fbe 100644 --- a/src/routes/$account.chats.$id.tsx +++ b/src/routes/$account.chats.$id.tsx @@ -1,14 +1,34 @@ import { commands } from "@/commands"; +import { Spinner } from "@/components/spinner"; import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/$account/chats/$id")({ - loader: async ({ params }) => { + loader: async ({ params, context }) => { const res = await commands.connectInboxRelays(params.id, false); if (res.status === "ok") { + // Add id to chat manager to unsubscribe later. + context.chatManager.set(params.id, params.id); + return res.data; } else { return []; } }, + pendingComponent: Pending, + pendingMs: 200, + pendingMinMs: 100, }); + +function Pending() { + return ( +
+
+ + + Connection in progress. Please wait ... + +
+
+ ); +} diff --git a/src/routes/$account.relays.tsx b/src/routes/$account.relays.tsx index 899ab05..16a17b5 100644 --- a/src/routes/$account.relays.tsx +++ b/src/routes/$account.relays.tsx @@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router"; export const Route = createFileRoute("/$account/relays")({ loader: async ({ params }) => { - const res = await commands.getInboxRelays(params.account); + const res = await commands.collectInboxRelays(params.account); if (res.status === "ok") { return res.data; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index d48cfd4..631c607 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -2,9 +2,11 @@ import { cn } from "@/commons"; import type { QueryClient } from "@tanstack/react-query"; import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"; import type { OsType } from "@tauri-apps/plugin-os"; +import type { LRUCache } from "lru-cache"; interface RouterContext { queryClient: QueryClient; + chatManager: LRUCache; platform: OsType; }