diff --git a/apps/desktop2/src/routes/auth/privkey.lazy.tsx b/apps/desktop2/src/routes/auth/privkey.lazy.tsx
index afca9f25..b59a38e0 100644
--- a/apps/desktop2/src/routes/auth/privkey.lazy.tsx
+++ b/apps/desktop2/src/routes/auth/privkey.lazy.tsx
@@ -1,5 +1,5 @@
import { Spinner } from "@lume/ui";
-import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
+import { createLazyFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import { toast } from "sonner";
@@ -9,7 +9,7 @@ export const Route = createLazyFileRoute("/auth/privkey")({
function Screen() {
const { ark } = Route.useRouteContext();
- const navigate = useNavigate();
+ const navigate = Route.useNavigate();
const [key, setKey] = useState("");
const [password, setPassword] = useState("");
@@ -20,23 +20,23 @@ function Screen() {
return toast.warning(
"You need to enter a valid private key starts with nsec or ncryptsec",
);
- if (key.length < 30)
- return toast.warning("You need to enter a valid private key");
-
- setLoading(true);
try {
+ setLoading(true);
+
const npub = await ark.save_account(key, password);
- navigate({
- to: "/auth/settings",
- search: { account: npub, new: false },
- replace: true,
- });
+
+ if (npub) {
+ navigate({
+ to: "/auth/settings",
+ search: { account: npub },
+ replace: true,
+ });
+ }
} catch (e) {
+ setLoading(false);
toast.error(e);
}
-
- setLoading(false);
};
return (
diff --git a/apps/desktop2/src/routes/auth/remote.lazy.tsx b/apps/desktop2/src/routes/auth/remote.lazy.tsx
index 74bd94bd..54890bcb 100644
--- a/apps/desktop2/src/routes/auth/remote.lazy.tsx
+++ b/apps/desktop2/src/routes/auth/remote.lazy.tsx
@@ -1,9 +1,74 @@
+import { Spinner } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router";
+import { useState } from "react";
+import { toast } from "sonner";
export const Route = createLazyFileRoute("/auth/remote")({
component: Screen,
});
function Screen() {
- return
#todo
;
+ const { ark } = Route.useRouteContext();
+ const navigate = Route.useNavigate();
+
+ const [uri, setUri] = useState("");
+ const [loading, setLoading] = useState(false);
+
+ const submit = async () => {
+ if (!uri.startsWith("bunker://"))
+ return toast.warning(
+ "You need to enter a valid Connect URI starts with bunker://",
+ );
+
+ try {
+ setLoading(true);
+
+ const npub = await ark.nostr_connect(uri);
+
+ if (npub) {
+ navigate({
+ to: "/auth/settings",
+ search: { account: npub },
+ replace: true,
+ });
+ }
+ } catch (e) {
+ setLoading(false);
+ toast.error(e);
+ }
+ };
+
+ return (
+
+
+
Continue with Nostr Connect
+
+
+
+
+ setUri(e.target.value)}
+ className="h-11 rounded-lg border-transparent bg-neutral-100 px-3 placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:bg-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800"
+ />
+
+
+
+
+ );
}
diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts
index a627db67..24410f8c 100644
--- a/packages/ark/src/ark.ts
+++ b/packages/ark/src/ark.ts
@@ -51,6 +51,7 @@ export class Ark {
const cmd: boolean = await invoke("load_selected_account", {
npub,
});
+ await invoke("connect_user_relays");
return cmd;
} catch (e) {
@@ -58,12 +59,19 @@ export class Ark {
}
}
- public async create_guest_account() {
+ public async nostr_connect(uri: string) {
try {
- const keys = await this.create_keys();
- await this.save_account(keys.nsec, "");
+ const remoteKey = uri.replace("bunker://", "").split("?")[0];
+ const npub: string = await invoke("to_npub", { hex: remoteKey });
- return keys.npub;
+ if (npub) {
+ const connect: string = await invoke("nostr_connect", {
+ npub,
+ uri,
+ });
+
+ return connect;
+ }
} catch (e) {
throw new Error(String(e));
}
@@ -105,10 +113,7 @@ export class Ark {
public async get_event(id: string) {
try {
- const eventId: string = id
- .replace("nostr:", "")
- .split("'")[0]
- .split(".")[0];
+ const eventId: string = id.replace("nostr:", "").replace(/[^\w\s]/gi, "");
const cmd: string = await invoke("get_event", { id: eventId });
const event: Event = JSON.parse(cmd);
return event;
@@ -395,12 +400,7 @@ export class Ark {
public async get_profile(pubkey: string) {
try {
- const id = pubkey
- .replace("nostr:", "")
- .split("'")[0]
- .split(".")[0]
- .split(",")[0]
- .split("?")[0];
+ const id = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
const cmd: Metadata = await invoke("get_profile", { id });
return cmd;
diff --git a/packages/ark/src/hooks/useProfile.ts b/packages/ark/src/hooks/useProfile.ts
index 36cc688b..34873fb7 100644
--- a/packages/ark/src/hooks/useProfile.ts
+++ b/packages/ark/src/hooks/useProfile.ts
@@ -11,12 +11,7 @@ export function useProfile(pubkey: string) {
queryKey: ["user", pubkey],
queryFn: async () => {
try {
- const id = pubkey
- .replace("nostr:", "")
- .split("'")[0]
- .split(".")[0]
- .split(",")[0]
- .split("?")[0];
+ const id = pubkey.replace("nostr:", "").replace(/[^\w\s]/gi, "");
const cmd: Metadata = await invoke("get_profile", { id });
return cmd;
} catch (e) {
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index c0498275..ef6dd5b8 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -101,11 +101,14 @@ fn main() {
nostr::keys::save_key,
nostr::keys::get_encrypted_key,
nostr::keys::get_stored_nsec,
+ nostr::keys::nostr_connect,
nostr::keys::verify_signer,
nostr::keys::load_selected_account,
nostr::keys::event_to_bech32,
nostr::keys::user_to_bech32,
+ nostr::keys::to_npub,
nostr::keys::verify_nip05,
+ nostr::metadata::connect_user_relays,
nostr::metadata::get_current_user_profile,
nostr::metadata::get_profile,
nostr::metadata::get_contact_list,
diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs
index a7d40eed..fbc1b82a 100644
--- a/src-tauri/src/nostr/keys.rs
+++ b/src-tauri/src/nostr/keys.rs
@@ -74,6 +74,39 @@ pub async fn save_key(
}
}
+#[tauri::command]
+pub async fn nostr_connect(
+ npub: &str,
+ uri: &str,
+ app_handle: tauri::AppHandle,
+ state: State<'_, Nostr>,
+) -> Result {
+ let client = &state.client;
+ let app_keys = Keys::generate();
+
+ match NostrConnectURI::parse(uri) {
+ Ok(bunker_uri) => {
+ println!("connecting... {}", uri);
+
+ match Nip46Signer::new(bunker_uri, app_keys, Duration::from_secs(120), None).await {
+ Ok(signer) => {
+ let home_dir = app_handle.path().home_dir().unwrap();
+ let app_dir = home_dir.join("Lume/");
+ let file_path = npub.to_owned() + ".npub";
+ let keyring = Entry::new("Lume Secret Storage", npub).unwrap();
+ let _ = File::create(app_dir.join(file_path)).unwrap();
+ let _ = keyring.set_password(uri);
+ let _ = client.set_signer(Some(signer.into())).await;
+
+ Ok(npub.into())
+ }
+ Err(err) => Err(err.to_string()),
+ }
+ }
+ Err(err) => Err(err.to_string()),
+ }
+}
+
#[tauri::command]
pub async fn verify_signer(state: State<'_, Nostr>) -> Result {
let client = &state.client;
@@ -119,40 +152,28 @@ pub async fn load_selected_account(npub: &str, state: State<'_, Nostr>) -> Resul
let client = &state.client;
let keyring = Entry::new("Lume Secret Storage", npub).unwrap();
- if let Ok(nsec) = keyring.get_password() {
- // Build nostr signer
- let secret_key = SecretKey::from_bech32(nsec).expect("Get secret key failed");
- let keys = Keys::new(secret_key);
- let public_key = keys.public_key();
- let signer = NostrSigner::Keys(keys);
+ if let Ok(password) = keyring.get_password() {
+ if password.starts_with("bunker://") {
+ let app_keys = Keys::generate();
+ 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;
+ // Done
+ Ok(true)
+ } else {
+ let secret_key = SecretKey::from_bech32(password).expect("Get secret key failed");
+ let keys = Keys::new(secret_key);
+ let signer = NostrSigner::Keys(keys);
- // 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;
-
- // 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();
- }
- }
+ // Update signer
+ client.set_signer(Some(signer)).await;
+ // Done
+ Ok(true)
}
-
- Ok(true)
} else {
Err("nsec not found".into())
}
@@ -174,6 +195,14 @@ pub fn user_to_bech32(key: &str, relays: Vec) -> Result {
Ok(profile.to_bech32().unwrap())
}
+#[tauri::command]
+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(async)]
pub async fn verify_nip05(key: &str, nip05: &str) -> Result {
let public_key = PublicKey::from_str(key).unwrap();
diff --git a/src-tauri/src/nostr/metadata.rs b/src-tauri/src/nostr/metadata.rs
index 5c187339..89a9cd68 100644
--- a/src-tauri/src/nostr/metadata.rs
+++ b/src-tauri/src/nostr/metadata.rs
@@ -11,6 +11,38 @@ pub struct CacheContact {
profile: Metadata,
}
+#[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();
+
+ // 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;
+
+ // 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();
+ }
+ }
+ }
+
+ Ok(())
+}
+
#[tauri::command]
pub async fn get_current_user_profile(state: State<'_, Nostr>) -> Result {
let client = &state.client;