From 41d0de539d554460a1afd2daaed840be29008a48 Mon Sep 17 00:00:00 2001 From: reya <123083837+reyamir@users.noreply.github.com> Date: Thu, 30 May 2024 15:21:33 +0700 Subject: [PATCH] feat: revamp nostr connect flow --- .../src/routes/auth/$account.backup.tsx | 3 +- .../{settings.tsx => $account.settings.tsx} | 59 +---- .../desktop2/src/routes/auth/privkey.lazy.tsx | 3 +- apps/desktop2/src/routes/auth/remote.lazy.tsx | 9 +- packages/system/src/account.ts | 15 +- packages/system/src/commands.ts | 12 +- src-tauri/src/main.rs | 3 +- src-tauri/src/nostr/keys.rs | 225 +++++++----------- 8 files changed, 113 insertions(+), 216 deletions(-) rename apps/desktop2/src/routes/auth/{settings.tsx => $account.settings.tsx} (70%) diff --git a/apps/desktop2/src/routes/auth/$account.backup.tsx b/apps/desktop2/src/routes/auth/$account.backup.tsx index 982aeb3c..bbed1c9d 100644 --- a/apps/desktop2/src/routes/auth/$account.backup.tsx +++ b/apps/desktop2/src/routes/auth/$account.backup.tsx @@ -33,7 +33,8 @@ function Screen() { } return navigate({ - to: "/auth/settings", + to: "/auth/$account/settings", + params: { account }, }); } diff --git a/apps/desktop2/src/routes/auth/settings.tsx b/apps/desktop2/src/routes/auth/$account.settings.tsx similarity index 70% rename from apps/desktop2/src/routes/auth/settings.tsx rename to apps/desktop2/src/routes/auth/$account.settings.tsx index d0f30050..6c91a4c1 100644 --- a/apps/desktop2/src/routes/auth/settings.tsx +++ b/apps/desktop2/src/routes/auth/$account.settings.tsx @@ -1,14 +1,13 @@ import { LaurelIcon } from "@lume/icons"; -import { NostrQuery } from "@lume/system"; +import { NostrAccount, NostrQuery } from "@lume/system"; import { Spinner } from "@lume/ui"; import * as Switch from "@radix-ui/react-switch"; import { createFileRoute } from "@tanstack/react-router"; -import { requestPermission } from "@tauri-apps/plugin-notification"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -export const Route = createFileRoute("/auth/settings")({ +export const Route = createFileRoute("/auth/$account/settings")({ beforeLoad: async () => { const settings = await NostrQuery.getSettings(); return { settings }; @@ -19,6 +18,7 @@ export const Route = createFileRoute("/auth/settings")({ function Screen() { const { settings } = Route.useRouteContext(); + const { account } = Route.useParams(); const { t } = useTranslation(); const [newSettings, setNewSettings] = useState(settings); @@ -26,21 +26,6 @@ function Screen() { const navigate = Route.useNavigate(); - const toggleNofitication = async () => { - await requestPermission(); - setNewSettings((prev) => ({ - ...prev, - notification: !newSettings.notification, - })); - }; - - const toggleAutoUpdate = () => { - setNewSettings((prev) => ({ - ...prev, - autoUpdate: !newSettings.autoUpdate, - })); - }; - const toggleEnhancedPrivacy = () => { setNewSettings((prev) => ({ ...prev, @@ -69,10 +54,13 @@ function Screen() { // publish settings const eventId = await NostrQuery.setSettings(newSettings); + const allAccounts = await NostrAccount.getAccounts(); if (eventId) { return navigate({ - to: "/", + to: "/$account/home", + params: { account }, + search: { accounts: [...new Set([account, ...allAccounts])] }, replace: true, }); } @@ -99,22 +87,6 @@ function Screen() {
-
-
-

Push Notification

-

- Enabling push notifications will allow you to receive - notifications from Lume. -

-
- toggleNofitication()} - className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/20" - > - - -

Enhanced Privacy

@@ -131,21 +103,6 @@ function Screen() {
-
-
-

Auto Update

-

- Automatically download and install new version. -

-
- toggleAutoUpdate()} - className="relative mt-1 h-7 w-12 shrink-0 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-white/20" - > - - -

Zap

@@ -185,7 +142,7 @@ function Screen() { disabled={loading} className="mb-1 inline-flex h-11 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600 disabled:opacity-50" > - {t("global.continue")} + {loading ? : t("global.continue")}
diff --git a/apps/desktop2/src/routes/auth/privkey.lazy.tsx b/apps/desktop2/src/routes/auth/privkey.lazy.tsx index 91b5481e..15daa529 100644 --- a/apps/desktop2/src/routes/auth/privkey.lazy.tsx +++ b/apps/desktop2/src/routes/auth/privkey.lazy.tsx @@ -28,7 +28,8 @@ function Screen() { if (npub) { navigate({ - to: "/auth/settings", + to: "/auth/$account/settings", + params: { account: npub }, replace: true, }); } diff --git a/apps/desktop2/src/routes/auth/remote.lazy.tsx b/apps/desktop2/src/routes/auth/remote.lazy.tsx index 50636dd1..e853ab2e 100644 --- a/apps/desktop2/src/routes/auth/remote.lazy.tsx +++ b/apps/desktop2/src/routes/auth/remote.lazy.tsx @@ -23,11 +23,12 @@ function Screen() { try { setLoading(true); - const npub = await NostrAccount.connectRemoteAccount(uri); + const remoteAccount = await NostrAccount.connectRemoteAccount(uri); - if (npub) { - navigate({ - to: "/auth/settings", + if (remoteAccount?.length) { + return navigate({ + to: "/auth/$account/settings", + params: { account: remoteAccount }, replace: true, }); } diff --git a/packages/system/src/account.ts b/packages/system/src/account.ts index 9b46ac27..a72516db 100644 --- a/packages/system/src/account.ts +++ b/packages/system/src/account.ts @@ -62,19 +62,12 @@ export class NostrAccount { } static async connectRemoteAccount(uri: string) { - const remoteKey = uri.replace("bunker://", "").split("?")[0]; - const npub = await commands.toNpub(remoteKey); + const connect = await commands.connectRemoteAccount(uri); - if (npub.status === "ok") { - const connect = await commands.nostrConnect(npub.data, uri); - - if (connect.status === "ok") { - return connect.data; - } else { - throw new Error(connect.error); - } + if (connect.status === "ok") { + return connect.data; } else { - throw new Error(npub.error); + throw new Error(connect.error); } } diff --git a/packages/system/src/commands.ts b/packages/system/src/commands.ts index b7fd656a..659862da 100644 --- a/packages/system/src/commands.ts +++ b/packages/system/src/commands.ts @@ -57,9 +57,9 @@ try { else return { status: "error", error: e as any }; } }, -async nostrConnect(npub: string, uri: string) : Promise> { +async connectRemoteAccount(uri: string) : Promise> { try { - return { status: "ok", data: await TAURI_INVOKE("nostr_connect", { npub, uri }) }; + return { status: "ok", data: await TAURI_INVOKE("connect_remote_account", { uri }) }; } catch (e) { if(e instanceof Error) throw e; else return { status: "error", error: e as any }; @@ -89,14 +89,6 @@ try { else return { status: "error", error: e as any }; } }, -async toNpub(hex: string) : Promise> { -try { - return { status: "ok", data: await TAURI_INVOKE("to_npub", { hex }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, async verifyNip05(key: string, nip05: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("verify_nip05", { key, nip05 }) }; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 395b90cc..dfcc8ff9 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -34,11 +34,10 @@ fn main() { nostr::keys::create_account, nostr::keys::save_account, nostr::keys::get_encrypted_key, - nostr::keys::nostr_connect, + nostr::keys::connect_remote_account, nostr::keys::load_account, nostr::keys::event_to_bech32, nostr::keys::user_to_bech32, - nostr::keys::to_npub, nostr::keys::verify_nip05, nostr::metadata::get_activities, nostr::metadata::get_current_user_profile, diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs index b6b193ea..ac616c21 100644 --- a/src-tauri/src/nostr/keys.rs +++ b/src-tauri/src/nostr/keys.rs @@ -108,152 +108,114 @@ pub async fn load_account( let client = &state.client; let keyring = Entry::new(npub, "nostr_secret").unwrap(); - match keyring.get_password() { - Ok(password) => { - if password.starts_with("bunker://") { - let local_keyring = Entry::new(npub, "bunker_local_account").unwrap(); + if let Ok(password) = keyring.get_password() { + let keys = Keys::parse(password).expect("Secret Key is modified, please check again."); + let signer = NostrSigner::Keys(keys); - match local_keyring.get_password() { - Ok(local_password) => { - let secret_key = SecretKey::from_bech32(local_password).unwrap(); - let app_keys = Keys::new(secret_key); - let bunker_uri = NostrConnectURI::parse(password).unwrap(); - let signer = Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(60), None) - .await - .unwrap(); + // Update signer + client.set_signer(Some(signer)).await; - // Update signer - client.set_signer(Some(signer.into())).await; - } - Err(_) => todo!(), - } - } else { - let secret_key = SecretKey::from_bech32(password).expect("Get secret key failed"); - let keys = Keys::new(secret_key); - let signer = NostrSigner::Keys(keys); + // Verify signer + let signer = client.signer().await.unwrap(); + let public_key = signer.public_key().await.unwrap(); - // Update signer - client.set_signer(Some(signer)).await; - } + let filter = Filter::new() + .author(public_key) + .kind(Kind::RelayList) + .limit(1); - // Verify signer - let signer = client.signer().await.unwrap(); - let public_key = signer.public_key().await.unwrap(); + // Connect to user's relay (NIP-65) + // #TODO: Let rust-nostr handle it + if let Ok(events) = client + .get_events_of(vec![filter], Some(Duration::from_secs(10))) + .await + { + 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, item.1); - let filter = Filter::new() - .author(public_key) - .kind(Kind::RelayList) - .limit(1); - - // Connect to user's relay (NIP-65) - // #TODO: Let rust-nostr handle it - 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, item.1); - - let relay_url = item.0.to_string(); - let opts = match item.1 { - Some(val) => { - if val == &RelayMetadata::Read { - RelayOptions::new().read(true).write(false) - } else { - RelayOptions::new().write(true).read(false) - } - } - None => RelayOptions::new(), - }; - - // Add relay to relay pool - let _ = client - .add_relay_with_opts(relay_url.clone(), opts) - .await - .unwrap_or_default(); - - // Connect relay - client.connect_relay(relay_url).await.unwrap_or_default(); - } - } - } - Err(_) => todo!(), - }; - - // Run notification service - tauri::async_runtime::spawn(async move { - let window = app.get_window("main").unwrap(); - let state = window.state::(); - let client = &state.client; - let subscription = Filter::new() - .pubkey(public_key) - .kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt]) - .since(Timestamp::now()); - let activity_id = SubscriptionId::new("activity"); - - // Create a subscription for activity - client - .subscribe_with_id(activity_id.clone(), vec![subscription], None) - .await; - - // Handle notifications - let _ = client - .handle_notifications(|notification| async { - if let RelayPoolNotification::Event { - subscription_id, - event, - .. - } = notification - { - if subscription_id == activity_id { - let _ = app.emit("activity", event.as_json()); + let relay_url = item.0.to_string(); + let opts = match item.1 { + Some(val) => { + if val == &RelayMetadata::Read { + RelayOptions::new().read(true).write(false) + } else { + RelayOptions::new().write(true).read(false) } } - Ok(false) - }) - .await; - }); + None => RelayOptions::new(), + }; - Ok(true) - } - Err(err) => Err(err.to_string()), + // Add relay to relay pool + let _ = client + .add_relay_with_opts(relay_url.clone(), opts) + .await + .unwrap_or_default(); + + // Connect relay + client.connect_relay(relay_url).await.unwrap_or_default(); + } + } + }; + + // Run notification service + tauri::async_runtime::spawn(async move { + let window = app.get_window("main").unwrap(); + let state = window.state::(); + let client = &state.client; + let subscription = Filter::new() + .pubkey(public_key) + .kinds(vec![Kind::TextNote, Kind::Repost, Kind::ZapReceipt]) + .since(Timestamp::now()); + let activity_id = SubscriptionId::new("activity"); + + // Create a subscription for activity + client + .subscribe_with_id(activity_id.clone(), vec![subscription], None) + .await; + + // Handle notifications + let _ = client + .handle_notifications(|notification| async { + if let RelayPoolNotification::Event { + subscription_id, + event, + .. + } = notification + { + if subscription_id == activity_id { + let _ = app.emit("activity", event.as_json()); + } + } + Ok(false) + }) + .await; + }); + + Ok(true) + } else { + Err("Key not found.".into()) } } #[tauri::command] #[specta::specta] -pub async fn nostr_connect( - npub: &str, - uri: &str, - state: State<'_, Nostr>, -) -> Result { +pub async fn connect_remote_account(uri: &str, state: State<'_, Nostr>) -> Result { let client = &state.client; - let local_key = Keys::generate(); match NostrConnectURI::parse(uri) { Ok(bunker_uri) => { - match Nip46Signer::new( - bunker_uri, - local_key.clone(), - Duration::from_secs(120), - None, - ) - .await - { + let app_keys = Keys::generate(); + + // Get remote user + let remote_user = bunker_uri.signer_public_key().unwrap(); + let remote_npub = remote_user.to_bech32().unwrap(); + + match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None).await { Ok(signer) => { - let local_secret = local_key.secret_key().unwrap().to_bech32().unwrap(); - let secret_keyring = Entry::new(&npub, "nostr_secret").unwrap(); - let account_keyring = Entry::new(&npub, "bunker_local_account").unwrap(); - let _ = secret_keyring.set_password(uri); - let _ = account_keyring.set_password(&local_secret); - - // Update signer let _ = client.set_signer(Some(signer.into())).await; - - Ok(npub.into()) + Ok(remote_npub.into()) } Err(err) => Err(err.to_string()), } @@ -299,15 +261,6 @@ pub fn user_to_bech32(key: &str, relays: Vec) -> Result { Ok(profile.to_bech32().unwrap()) } -#[tauri::command] -#[specta::specta] -pub fn to_npub(hex: &str) -> Result { - let public_key = PublicKey::from_str(hex).unwrap(); - let npub = Nip19::Pubkey(public_key); - - Ok(npub.to_bech32().unwrap()) -} - #[tauri::command] #[specta::specta] pub async fn verify_nip05(key: &str, nip05: &str) -> Result {