diff --git a/apps/desktop2/src/components/col.tsx b/apps/desktop2/src/components/col.tsx index c6e06b8a..4dbf28f2 100644 --- a/apps/desktop2/src/components/col.tsx +++ b/apps/desktop2/src/components/col.tsx @@ -78,7 +78,7 @@ export function Col({ }); } }; - }, [webview]); + }, []); return (
; + } + + return
()); diff --git a/packages/utils/src/invoice.ts b/packages/utils/src/invoice.ts new file mode 100644 index 00000000..39363d71 --- /dev/null +++ b/packages/utils/src/invoice.ts @@ -0,0 +1,17 @@ +import { decode } from "light-bolt11-decoder"; +import { getBitcoinDisplayValues } from "./formater"; + +export function decodeZapInvoice(tags?: string[][]) { + const invoice = tags.find((tag) => tag[0] === "bolt11")?.[1]; + if (!invoice) return; + + const decodedInvoice = decode(invoice); + const amountSection = decodedInvoice.sections.find( + (s: any) => s.name === "amount", + ); + + const amount = parseInt(amountSection.value); + const displayValue = getBitcoinDisplayValues(amount); + + return displayValue; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a0263f4..9a74365d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -448,6 +448,9 @@ importers: dayjs: specifier: ^1.11.11 version: 1.11.11 + light-bolt11-decoder: + specifier: ^3.1.1 + version: 3.1.1 nostr-tools: specifier: ^2.5.1 version: 2.5.1(typescript@5.4.5) @@ -4329,6 +4332,12 @@ packages: engines: {node: '>=6'} dev: false + /light-bolt11-decoder@3.1.1: + resolution: {integrity: sha512-sLg/KCwYkgsHWkefWd6KqpCHrLFWWaXTOX3cf6yD2hAzL0SLpX+lFcaFK2spkjbgzG6hhijKfORDc9WoUHwX0A==} + dependencies: + '@scure/base': 1.1.1 + dev: false + /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json index 4b369687..b11fa0b2 100644 --- a/src-tauri/capabilities/main.json +++ b/src-tauri/capabilities/main.json @@ -1,70 +1,75 @@ { - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "desktop-capability", - "description": "Capability for the desktop", - "platforms": ["linux", "macOS", "windows"], - "windows": [ - "main", - "splash", - "settings", - "search", - "nwc", - "zap-*", - "event-*", - "user-*", - "editor-*", - "column-*" - ], - "permissions": [ - "path:default", - "event:default", - "window:default", - "app:default", - "resources:default", - "menu:default", - "tray:default", - "notification:allow-is-permission-granted", - "notification:allow-request-permission", - "notification:default", - "os:allow-locale", - "os:allow-platform", - "updater:allow-check", - "updater:default", - "window:allow-start-dragging", - "window:allow-create", - "window:allow-close", - "window:allow-set-focus", - "clipboard-manager:allow-write", - "clipboard-manager:allow-read", - "webview:allow-create-webview-window", - "webview:allow-create-webview", - "webview:allow-set-webview-size", - "webview:allow-set-webview-position", - "webview:allow-webview-close", - "dialog:allow-open", - "fs:allow-read-file", - "shell:allow-open", - { - "identifier": "http:default", - "allow": [ - { - "url": "http://**/" - }, - { - "url": "https://**/" - } - ] - }, - { - "identifier": "fs:allow-read-text-file", - "allow": [ - { - "path": "$RESOURCE/locales/*" - }, - { - "path": "$RESOURCE/resources/*" - } - ] - } - ] + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "desktop-capability", + "description": "Capability for the desktop", + "platforms": [ + "linux", + "macOS", + "windows" + ], + "windows": [ + "main", + "splash", + "settings", + "search", + "nwc", + "activity", + "zap-*", + "event-*", + "user-*", + "editor-*", + "column-*" + ], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "resources:default", + "menu:default", + "tray:default", + "notification:allow-is-permission-granted", + "notification:allow-request-permission", + "notification:default", + "os:allow-locale", + "os:allow-platform", + "updater:allow-check", + "updater:default", + "window:allow-start-dragging", + "window:allow-create", + "window:allow-close", + "window:allow-set-focus", + "clipboard-manager:allow-write", + "clipboard-manager:allow-read", + "webview:allow-create-webview-window", + "webview:allow-create-webview", + "webview:allow-set-webview-size", + "webview:allow-set-webview-position", + "webview:allow-webview-close", + "dialog:allow-open", + "fs:allow-read-file", + "shell:allow-open", + { + "identifier": "http:default", + "allow": [ + { + "url": "http://**/" + }, + { + "url": "https://**/" + } + ] + }, + { + "identifier": "fs:allow-read-text-file", + "allow": [ + { + "path": "$RESOURCE/locales/*" + }, + { + "path": "$RESOURCE/resources/*" + } + ] + } + ] } diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index 1b3fef92..b68d1eae 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","splash","settings","search","nwc","zap-*","event-*","user-*","editor-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","window:allow-create","window:allow-close","window:allow-set-focus","clipboard-manager:allow-write","clipboard-manager:allow-read","webview:allow-create-webview-window","webview:allow-create-webview","webview:allow-set-webview-size","webview:allow-set-webview-position","webview:allow-webview-close","dialog:allow-open","fs:allow-read-file","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]}} \ No newline at end of file +{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","local":true,"windows":["main","splash","settings","search","nwc","activity","zap-*","event-*","user-*","editor-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform","updater:allow-check","updater:default","window:allow-start-dragging","window:allow-create","window:allow-close","window:allow-set-focus","clipboard-manager:allow-write","clipboard-manager:allow-read","webview:allow-create-webview-window","webview:allow-create-webview","webview:allow-set-webview-size","webview:allow-set-webview-position","webview:allow-webview-close","dialog:allow-open","fs:allow-read-file","shell:allow-open",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"},{"path":"$RESOURCE/resources/*"}]}],"platforms":["linux","macOS","windows"]}} \ No newline at end of file diff --git a/src-tauri/src/commands/window.rs b/src-tauri/src/commands/window.rs index af1706af..4ee154b7 100644 --- a/src-tauri/src/commands/window.rs +++ b/src-tauri/src/commands/window.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use tauri::utils::config::WindowEffectsConfig; use tauri::window::Effect; use tauri::TitleBarStyle; +use tauri::Url; use tauri::WebviewWindowBuilder; use tauri::{LogicalPosition, LogicalSize, Manager, WebviewUrl}; @@ -54,6 +55,30 @@ pub fn close_column(label: &str, app_handle: tauri::AppHandle) -> ResultResult { + match app_handle.get_webview(label) { + Some(webview) => Ok(webview.url().to_string()), + None => Err("Webview not found".into()), + } +} + +#[tauri::command] +pub fn navigate(label: &str, url: &str, app_handle: tauri::AppHandle) -> Result<(), String> { + match app_handle.get_webview(label) { + Some(mut webview) => { + if let Ok(new_url) = Url::parse(url) { + println!("navigate to: {}", new_url); + webview.navigate(new_url); + Ok(()) + } else { + Err("URL is not valid".into()) + } + } + None => Err("Webview not found".into()), + } +} + #[tauri::command] pub fn reposition_column( label: &str, @@ -125,6 +150,11 @@ pub fn open_window( }) .build() .unwrap(); + + // [macOS] Custom traffic light possition + // #[cfg(target_os = "macos")] + // setup_traffic_light_positioner(app_handle.get_window(label).unwrap()); + #[cfg(not(target_os = "macos"))] let _ = WebviewWindowBuilder::new(&app_handle, label, WebviewUrl::App(PathBuf::from(url))) .title(title) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e21c0cf7..e9b4315b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -29,20 +29,18 @@ pub struct Nostr { fn main() { tauri::Builder::default() .setup(|app| { - #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Regular); - #[cfg(target_os = "macos")] setup_traffic_light_positioner(app.get_window("main").unwrap()); - let _tray = tray::create_tray(app.handle()).unwrap(); + // Setup app tray let handle = app.handle().clone(); - let home_dir = handle.path().home_dir().unwrap(); + let _ = tray::create_tray(app.handle()).unwrap(); - // create data folder if not exist + // Create data folder if not exist + let home_dir = app.path().home_dir().unwrap(); fs::create_dir_all(home_dir.join("Lume/")).unwrap(); - tauri::async_runtime::spawn(async move { + tauri::async_runtime::block_on(async move { // Create nostr database connection let sqlite = SQLiteDatabase::open(home_dir.join("Lume/lume.db")).await; @@ -59,13 +57,9 @@ fn main() { .await .expect("Cannot connect to relay.nostr.net, please try again later."); client - .add_relay("wss://relay.nostr.band") + .add_relay("wss://bostr.nokotaro.work/") .await - .expect("Cannot connect to relay.nostr.band, please try again later."); - client - .add_relay("wss://welcome.nostr.wine") - .await - .expect("Cannot connect to welcome.nostr.wine, please try again later."); + .expect("Cannot connect to bostr.nokotaro.work, please try again later."); // Connect client.connect().await; @@ -111,7 +105,8 @@ fn main() { nostr::keys::user_to_bech32, nostr::keys::to_npub, nostr::keys::verify_nip05, - nostr::metadata::connect_user_relays, + nostr::metadata::run_notification, + nostr::metadata::get_activities, nostr::metadata::get_current_user_profile, nostr::metadata::get_profile, nostr::metadata::get_contact_list, @@ -141,7 +136,9 @@ fn main() { commands::window::close_column, commands::window::reposition_column, commands::window::resize_column, - commands::window::open_window + commands::window::open_window, + commands::window::get_path, + commands::window::navigate ]) .run(tauri::generate_context!()) .expect("error while running tauri application") diff --git a/src-tauri/src/nostr/event.rs b/src-tauri/src/nostr/event.rs index 1985af59..b04a2341 100644 --- a/src-tauri/src/nostr/event.rs +++ b/src-tauri/src/nostr/event.rs @@ -54,7 +54,7 @@ pub async fn get_events_from( }; let filter = Filter::new() .kinds(vec![Kind::TextNote, Kind::Repost]) - .authors(vec![author]) + .author(author) .limit(limit) .until(until); diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs index 383cbf39..7d460542 100644 --- a/src-tauri/src/nostr/keys.rs +++ b/src-tauri/src/nostr/keys.rs @@ -172,6 +172,41 @@ pub async fn load_selected_account(npub: &str, state: State<'_, Nostr>) -> Resul client.set_signer(Some(signer)).await; } + // Verify signer + let signer = client.signer().await.unwrap(); + let public_key = signer.public_key().await.unwrap(); + + // Connect to user's relay + let filter = Filter::new() + .author(public_key) + .kind(Kind::RelayList) + .limit(1); + + match client + .get_events_of(vec![filter], Some(Duration::from_secs(10))) + .await + { + Ok(events) => { + if let Some(event) = events.first() { + let relay_list = nip65::extract_relay_list(&event); + for item in relay_list.into_iter() { + println!("connecting to relay: {}", item.0.to_string()); + // Add relay to pool + let _ = client + .add_relay(item.0.to_string()) + .await + .unwrap_or_default(); + // Connect relay + let _ = client + .connect_relay(item.0.to_string()) + .await + .unwrap_or_default(); + } + } + } + Err(_) => todo!(), + }; + Ok(true) } Err(err) => Err(err.to_string()), diff --git a/src-tauri/src/nostr/metadata.rs b/src-tauri/src/nostr/metadata.rs index b4e4298f..a015e613 100644 --- a/src-tauri/src/nostr/metadata.rs +++ b/src-tauri/src/nostr/metadata.rs @@ -2,7 +2,7 @@ use crate::Nostr; use keyring::Entry; use nostr_sdk::prelude::*; use std::{str::FromStr, time::Duration}; -use tauri::State; +use tauri::{Manager, State}; use url::Url; #[derive(serde::Serialize)] @@ -12,37 +12,80 @@ pub struct CacheContact { } #[tauri::command] -pub async fn connect_user_relays(state: State<'_, Nostr>) -> Result<(), ()> { - let client = &state.client; - let signer = client.signer().await.unwrap(); - let public_key = signer.public_key().await.unwrap(); +pub fn run_notification(accounts: Vec , app: tauri::AppHandle) -> Result<(), ()> { + tauri::async_runtime::spawn(async move { + let window = app.get_window("main").unwrap(); + let state = window.state:: (); + let client = &state.client; + let pubkeys: Vec = accounts + .into_iter() + .map(|f| PublicKey::from_bech32(f).unwrap()) + .collect(); + let subscription = Filter::new() + .pubkeys(pubkeys) + .kinds(vec![ + Kind::TextNote, + Kind::Repost, + Kind::ZapReceipt, + Kind::EncryptedDirectMessage, + ]) + .since(Timestamp::now()); + let activity_id = SubscriptionId::new("activity"); - // Get user's relay list - let filter = Filter::new() - .author(public_key) - .kind(Kind::RelayList) - .limit(1); - let query = client - .get_events_of(vec![filter], Some(Duration::from_secs(10))) - .await; + // Create a subscription for activity + client + .subscribe_with_id(activity_id.clone(), vec![subscription], None) + .await; - // Connect user's relay list - if let Ok(events) = query { - if let Some(event) = events.first() { - let list = nip65::extract_relay_list(&event); - for item in list.into_iter() { - println!("connecting to relay: {}", item.0.to_string()); - client - .connect_relay(item.0.to_string()) - .await - .unwrap_or_default(); - } - } - } + // Handle notifications + let _ = client + .handle_notifications(|notification| async { + if let RelayPoolNotification::Event { + subscription_id, + event, + .. + } = notification + { + if subscription_id == activity_id { + let _ = app.emit_to("main", "activity", event.as_json()); + } + } + Ok(false) + }) + .await; + }); Ok(()) } +#[tauri::command] +pub async fn get_activities( + account: &str, + kind: &str, + state: State<'_, Nostr>, +) -> Result , String> { + let client = &state.client; + + if let Ok(pubkey) = PublicKey::from_str(account) { + if let Ok(kind) = Kind::from_str(kind) { + let filter = Filter::new() + .pubkey(pubkey) + .kind(kind) + .limit(100) + .until(Timestamp::now()); + + match client.get_events_of(vec![filter], None).await { + Ok(events) => Ok(events), + Err(err) => Err(err.to_string()), + } + } else { + Err("Kind is not valid, please check again.".into()) + } + } else { + Err("Public Key is not valid, please check again.".into()) + } +} + #[tauri::command] pub async fn get_current_user_profile(state: State<'_, Nostr>) -> Result { let client = &state.client;