From a5574bef6c2aef1c9c70437abbc1d2157f9cefcc Mon Sep 17 00:00:00 2001 From: reya Date: Sun, 22 Sep 2024 09:40:07 +0700 Subject: [PATCH] feat: add notification for nip42 --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/main.rs | 76 +++++++++++++++---- src/components/note/user.tsx | 34 ++++++++- src/components/reply.tsx | 62 ++++++++++++++- src/components/user/avatar.tsx | 29 +------ .../columns/_layout/events.$id.lazy.tsx | 3 +- src/routes/index.lazy.tsx | 1 + src/system/hooks/useEvent.ts | 2 - src/system/hooks/useProfile.ts | 3 - 10 files changed, 156 insertions(+), 56 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4337dbb4..16237cfa 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -47,6 +47,7 @@ dependencies = [ "tauri-plugin-window-state", "tauri-specta", "tokio", + "tracing-subscriber", "url", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8e826d42..e66b2cf3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -47,6 +47,7 @@ linkify = "0.10.0" regex = "1.10.4" keyring = { version = "3", features = ["apple-native", "windows-native"] } keyring-search = "1.2.0" +tracing-subscriber = "0.3.18" [target.'cfg(target_os = "macos")'.dependencies] cocoa = "0.25.0" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cbf4c371..f01fa5d2 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -77,12 +77,15 @@ struct Subscription { #[derive(Serialize, Deserialize, Type, Clone, TauriEvent)] struct NewSettings(Settings); -pub const FETCH_LIMIT: usize = 44; +pub const FETCH_LIMIT: usize = 20; pub const NEWSFEED_NEG_LIMIT: usize = 256; pub const NOTIFICATION_NEG_LIMIT: usize = 64; pub const NOTIFICATION_SUB_ID: &str = "lume_notification"; fn main() { + #[cfg(debug_assertions)] + tracing_subscriber::fmt::init(); + let builder = Builder::::new() // Then register them (separated by a comma) .commands(collect_commands![ @@ -204,9 +207,10 @@ fn main() { let opts = Options::new() .gossip(true) .max_avg_latency(Duration::from_millis(500)) - .automatic_authentication(true) + .automatic_authentication(false) .connection_timeout(Some(Duration::from_secs(5))) - .timeout(Duration::from_secs(20)); + .send_timeout(Some(Duration::from_secs(5))) + .timeout(Duration::from_secs(5)); // Setup nostr client let client = ClientBuilder::default() @@ -321,11 +325,52 @@ fn main() { }; let notification_id = SubscriptionId::new(NOTIFICATION_SUB_ID); + let mut notifications = client.pool().notifications(); - client - .handle_notifications(|notification| async { - if let RelayPoolNotification::Message { message, .. } = notification { - if let RelayMessage::Event { + while let Ok(notification) = notifications.recv().await { + match notification { + RelayPoolNotification::Message { relay_url, message } => { + if let RelayMessage::Auth { challenge } = message { + match client.auth(challenge, relay_url.clone()).await { + Ok(..) => { + if let Ok(relay) = client.relay(&relay_url).await { + let msg = + format!("Authenticated to {} relay.", relay_url); + let opts = RelaySendOptions::new() + .skip_send_confirmation(true); + + if let Err(e) = relay.resubscribe(opts).await { + println!("Error: {}", e); + } + + if allow_notification { + if let Err(e) = &handle_clone + .notification() + .builder() + .body(&msg) + .title("Lume") + .show() + { + println!("Error: {}", e); + } + } + } + } + Err(e) => { + if allow_notification { + if let Err(e) = &handle_clone + .notification() + .builder() + .body(e.to_string()) + .title("Lume") + .show() + { + println!("Error: {}", e); + } + } + } + } + } else if let RelayMessage::Event { subscription_id, event, } = message @@ -337,12 +382,12 @@ fn main() { let author = client .fetch_metadata( event.pubkey, - Some(Duration::from_secs(5)), + Some(Duration::from_secs(3)), ) .await .unwrap_or_else(|_| Metadata::new()); - send_notification(&event, author, &handle_clone); + send_event_notification(&event, author, &handle_clone); } } @@ -361,13 +406,12 @@ fn main() { RichEvent { raw, parsed }, ) .unwrap(); - } else { - println!("new message: {}", message.as_json()) - } + }; } - Ok(false) - }) - .await + RelayPoolNotification::Shutdown => break, + _ => (), + } + } }); Ok(()) @@ -403,7 +447,7 @@ fn prevent_default() -> tauri::plugin::TauriPlugin { tauri_plugin_prevent_default::Builder::new().build() } -fn send_notification(event: &Event, author: Metadata, handle: &tauri::AppHandle) { +fn send_event_notification(event: &Event, author: Metadata, handle: &tauri::AppHandle) { match event.kind { Kind::TextNote => { if let Err(e) = handle diff --git a/src/components/note/user.tsx b/src/components/note/user.tsx index a238b701..02044147 100644 --- a/src/components/note/user.tsx +++ b/src/components/note/user.tsx @@ -1,15 +1,47 @@ import { cn } from "@/commons"; +import { LumeWindow } from "@/system"; +import { Menu, MenuItem } from "@tauri-apps/api/menu"; +import { writeText } from "@tauri-apps/plugin-clipboard-manager"; +import { useCallback } from "react"; import { User } from "../user"; import { useNoteContext } from "./provider"; export function NoteUser({ className }: { className?: string }) { const event = useNoteContext(); + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); + + const menuItems = await Promise.all([ + MenuItem.new({ + text: "View Profile", + action: () => LumeWindow.openProfile(event.pubkey), + }), + MenuItem.new({ + text: "Copy Public Key", + action: async () => { + const pubkey = await event.pubkeyAsBech32(); + await writeText(pubkey); + }, + }), + ]); + + const menu = await Menu.new({ + items: menuItems, + }); + + await menu.popup().catch((e) => console.error(e)); + }, []); + return (
-
diff --git a/src/components/reply.tsx b/src/components/reply.tsx index a152b02d..317bb5c1 100644 --- a/src/components/reply.tsx +++ b/src/components/reply.tsx @@ -1,9 +1,11 @@ import { cn, replyTime } from "@/commons"; import { Note } from "@/components/note"; -import type { LumeEvent } from "@/system"; +import { type LumeEvent, LumeWindow } from "@/system"; import { CaretDown } from "@phosphor-icons/react"; import { Link, useSearch } from "@tanstack/react-router"; -import { memo } from "react"; +import { Menu, MenuItem } from "@tauri-apps/api/menu"; +import { writeText } from "@tauri-apps/plugin-clipboard-manager"; +import { memo, useCallback } from "react"; import { User } from "./user"; export const ReplyNote = memo(function ReplyNote({ @@ -15,12 +17,38 @@ export const ReplyNote = memo(function ReplyNote({ }) { const search = useSearch({ strict: false }); + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); + + const menuItems = await Promise.all([ + MenuItem.new({ + text: "View Profile", + action: () => LumeWindow.openProfile(event.pubkey), + }), + MenuItem.new({ + text: "Copy Public Key", + action: async () => { + const pubkey = await event.pubkeyAsBech32(); + await writeText(pubkey); + }, + }), + ]); + + const menu = await Menu.new({ + items: menuItems, + }); + + await menu.popup().catch((e) => console.error(e)); + }, []); + return ( - +
@@ -74,13 +102,39 @@ export const ReplyNote = memo(function ReplyNote({ function ChildReply({ event }: { event: LumeEvent }) { const search = useSearch({ strict: false }); + const showContextMenu = useCallback(async (e: React.MouseEvent) => { + e.preventDefault(); + + const menuItems = await Promise.all([ + MenuItem.new({ + text: "View Profile", + action: () => LumeWindow.openProfile(event.pubkey), + }), + MenuItem.new({ + text: "Copy Public Key", + action: async () => { + const pubkey = await event.pubkeyAsBech32(); + await writeText(pubkey); + }, + }), + ]); + + const menu = await Menu.new({ + items: menuItems, + }); + + await menu.popup().catch((e) => console.error(e)); + }, []); + return (
- +
{event.content} diff --git a/src/components/user/avatar.tsx b/src/components/user/avatar.tsx index fd845d1d..7e526658 100644 --- a/src/components/user/avatar.tsx +++ b/src/components/user/avatar.tsx @@ -1,11 +1,8 @@ import { appSettings, cn } from "@/commons"; -import { LumeWindow } from "@/system"; import * as Avatar from "@radix-ui/react-avatar"; import { useStore } from "@tanstack/react-store"; -import { Menu, MenuItem } from "@tauri-apps/api/menu"; -import { writeText } from "@tauri-apps/plugin-clipboard-manager"; import { minidenticon } from "minidenticons"; -import { useCallback, useMemo } from "react"; +import { useMemo } from "react"; import { useUserContext } from "./provider"; export function UserAvatar({ className }: { className?: string }) { @@ -33,32 +30,8 @@ export function UserAvatar({ className }: { className?: string }) { [user.pubkey], ); - const showContextMenu = useCallback(async (e: React.MouseEvent) => { - e.preventDefault(); - - const menuItems = await Promise.all([ - MenuItem.new({ - text: "View Profile", - action: () => LumeWindow.openProfile(user.pubkey), - }), - MenuItem.new({ - text: "Copy Public Key", - action: async () => { - await writeText(user.pubkey); - }, - }), - ]); - - const menu = await Menu.new({ - items: menuItems, - }); - - await menu.popup().catch((e) => console.error(e)); - }, []); - return ( showContextMenu(e)} className={cn( "shrink-0 block overflow-hidden bg-neutral-200 dark:bg-neutral-800", className, diff --git a/src/routes/columns/_layout/events.$id.lazy.tsx b/src/routes/columns/_layout/events.$id.lazy.tsx index 53aeb848..6625a31e 100644 --- a/src/routes/columns/_layout/events.$id.lazy.tsx +++ b/src/routes/columns/_layout/events.$id.lazy.tsx @@ -182,7 +182,7 @@ function ReplyList() { useEffect(() => { events.subscription - .emit({ label, kind: "Subscribe", event_id: id, local_only: undefined }) + .emit({ label, kind: "Subscribe", event_id: id }) .then(() => console.log("Subscribe: ", label)); return () => { @@ -191,7 +191,6 @@ function ReplyList() { label, kind: "Unsubscribe", event_id: id, - local_only: undefined, }) .then(() => console.log("Unsubscribe: ", label)); }; diff --git a/src/routes/index.lazy.tsx b/src/routes/index.lazy.tsx index e1e455e9..5b8c96a4 100644 --- a/src/routes/index.lazy.tsx +++ b/src/routes/index.lazy.tsx @@ -147,6 +147,7 @@ function Screen() { onKeyDown={(e) => { if (e.key === "Enter") loginWith(); }} + disabled={isPending} placeholder="Password" className="px-3 rounded-full w-full h-10 bg-transparent border border-neutral-200 dark:border-neutral-500 focus:border-blue-500 focus:outline-none placeholder:text-neutral-400" /> diff --git a/src/system/hooks/useEvent.ts b/src/system/hooks/useEvent.ts index 9122d70d..84c7ec04 100644 --- a/src/system/hooks/useEvent.ts +++ b/src/system/hooks/useEvent.ts @@ -27,8 +27,6 @@ export function useEvent(id: string) { } } - console.log(relayHint); - // Build query if (relayHint?.length) { query = await commands.getEventFrom(normalizeId, relayHint); diff --git a/src/system/hooks/useProfile.ts b/src/system/hooks/useProfile.ts index 18e00bfa..1e1f348b 100644 --- a/src/system/hooks/useProfile.ts +++ b/src/system/hooks/useProfile.ts @@ -17,18 +17,15 @@ export function useProfile(pubkey: string, embed?: string) { } let normalizeId = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, ""); - let relayHint: string; if (normalizeId.startsWith("nprofile")) { const decoded = nip19.decode(normalizeId); if (decoded.type === "nprofile") { - relayHint = decoded.data.relays[0]; normalizeId = decoded.data.pubkey; } } - console.log(relayHint); const query = await commands.getProfile(normalizeId); if (query.status === "ok") {