add NIP-07 browser extension signer support for wasm
This commit is contained in:
@@ -28,6 +28,7 @@ sha2.workspace = true
|
||||
data-encoding.workspace = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
nostr-browser-signer.workspace = true
|
||||
nostr-memory.workspace = true
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
use anyhow::Result;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use common::web_store;
|
||||
use gpui::{App, Task};
|
||||
|
||||
use crate::USER_KEYRING;
|
||||
|
||||
/// Read the stored credential as `(username, secret)`
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn read(cx: &App) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
return cx.read_credentials(USER_KEYRING);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = cx;
|
||||
Task::ready(
|
||||
web_store::get(USER_KEYRING)
|
||||
.map(|stored| stored.map(|content| (String::new(), content.into_bytes()))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the stored credential
|
||||
pub(crate) fn write(cx: &App, username: &str, secret: &[u8]) -> Task<Result<()>> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
return cx.write_credentials(USER_KEYRING, username, secret);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = (cx, username);
|
||||
let content = match std::str::from_utf8(secret) {
|
||||
Ok(content) => content,
|
||||
Err(e) => return Task::ready(Err(anyhow::anyhow!("credential is not UTF-8: {e}"))),
|
||||
};
|
||||
|
||||
Task::ready(web_store::set(USER_KEYRING, content))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the stored credential
|
||||
pub(crate) fn delete(cx: &App) -> Task<Result<()>> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
return cx.delete_credentials(USER_KEYRING);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = cx;
|
||||
Task::ready(web_store::remove(USER_KEYRING))
|
||||
}
|
||||
}
|
||||
+61
-13
@@ -9,6 +9,8 @@ use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, TaskExt
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use gpui_tokio::Tokio;
|
||||
use instant::Duration;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use nostr_browser_signer::BrowserSigner;
|
||||
use nostr_connect::prelude::*;
|
||||
use nostr_gossip_memory::prelude::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -19,6 +21,7 @@ use nostr_sdk::prelude::*;
|
||||
|
||||
mod blossom;
|
||||
mod constants;
|
||||
mod credentials;
|
||||
mod file;
|
||||
mod nip05;
|
||||
mod nip4e;
|
||||
@@ -138,9 +141,7 @@ impl NostrRegistry {
|
||||
let _ = entity.update(cx, |this, cx| {
|
||||
this.connect_bootstrap_relays(cx);
|
||||
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
cx.emit(StateEvent::NoSigner);
|
||||
} else if let Some(secret) = cli_key {
|
||||
if let Some(secret) = cli_key {
|
||||
// Use CLI-provided key -- same path as restore_session
|
||||
let keys = Keys::new(secret);
|
||||
this.set_signer(keys, cx);
|
||||
@@ -242,7 +243,7 @@ impl NostrRegistry {
|
||||
|
||||
/// Restore the signer from the stored credential
|
||||
fn restore_session(&mut self, cx: &mut Context<Self>) {
|
||||
let user_keyring = cx.read_credentials(USER_KEYRING);
|
||||
let user_keyring = credentials::read(cx);
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
let result = async {
|
||||
@@ -279,6 +280,11 @@ impl NostrRegistry {
|
||||
this.update(cx, |this, cx| {
|
||||
this.connect_proxy(cx);
|
||||
})?;
|
||||
} else if content == "extension" {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
this.update(cx, |this, cx| {
|
||||
this.connect_extension(cx);
|
||||
})?;
|
||||
} else {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(StateEvent::NoSigner);
|
||||
@@ -314,7 +320,7 @@ impl NostrRegistry {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<PublicKey, Error>> {
|
||||
let password = password.to_owned();
|
||||
let user_keyring = cx.read_credentials(USER_KEYRING);
|
||||
let user_keyring = credentials::read(cx);
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let content = user_keyring
|
||||
@@ -373,9 +379,8 @@ impl NostrRegistry {
|
||||
let (keys, ncryptsec) = job.await?;
|
||||
let public_key = keys.public_key();
|
||||
|
||||
let write = cx.update(|cx| {
|
||||
cx.write_credentials(USER_KEYRING, &public_key.to_hex(), ncryptsec.as_bytes())
|
||||
});
|
||||
let write =
|
||||
cx.update(|cx| credentials::write(cx, &public_key.to_hex(), ncryptsec.as_bytes()));
|
||||
write.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -412,9 +417,8 @@ impl NostrRegistry {
|
||||
let keys = decrypt_task.await?;
|
||||
let public_key = keys.public_key();
|
||||
|
||||
let write = cx.update(|cx| {
|
||||
cx.write_credentials(USER_KEYRING, &public_key.to_hex(), stored.as_bytes())
|
||||
});
|
||||
let write =
|
||||
cx.update(|cx| credentials::write(cx, &public_key.to_hex(), stored.as_bytes()));
|
||||
write.await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
@@ -440,7 +444,7 @@ impl NostrRegistry {
|
||||
|
||||
let keys = Keys::generate();
|
||||
let credential = with_master_key(&uri_string, &keys);
|
||||
let write = cx.write_credentials(USER_KEYRING, "bunker", credential.as_bytes());
|
||||
let write = credentials::write(cx, "bunker", credential.as_bytes());
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let signer = NostrConnect::builder(connect_uri, keys)
|
||||
@@ -464,12 +468,56 @@ impl NostrRegistry {
|
||||
self.current_user = None;
|
||||
self.passphrase_required = false;
|
||||
|
||||
cx.delete_credentials(USER_KEYRING).detach_and_log_err(cx);
|
||||
credentials::delete(cx).detach_and_log_err(cx);
|
||||
cx.emit(StateEvent::SignerChanged);
|
||||
cx.emit(StateEvent::NoSigner);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Connect the browser extension signer (NIP-07), remembering the choice so
|
||||
/// reloads reconnect automatically
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn connect_extension(&mut self, cx: &mut Context<Self>) {
|
||||
let signer = match BrowserSigner::new() {
|
||||
Ok(signer) => signer,
|
||||
Err(e) => {
|
||||
cx.emit(StateEvent::error(format!(
|
||||
"No Nostr extension found: {e}. Install a NIP-07 extension like Alby or nos2x."
|
||||
)));
|
||||
cx.emit(StateEvent::NoSigner);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
match signer.get_public_key_async().await {
|
||||
Ok(public_key) => {
|
||||
// Remember the choice so reloads reconnect automatically
|
||||
this.update(cx, |this, cx| {
|
||||
credentials::write(cx, "extension", b"extension").detach_and_log_err(cx);
|
||||
this.signer.swap_inner(signer);
|
||||
this.current_user = Some(public_key);
|
||||
this.passphrase_required = false;
|
||||
|
||||
cx.emit(StateEvent::SignerChanged);
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
Err(e) => {
|
||||
// Drop the remembered choice so the next visit does not
|
||||
// retry a signer that is no longer answering
|
||||
this.update(cx, |_, cx| {
|
||||
credentials::delete(cx).detach_and_log_err(cx);
|
||||
cx.emit(StateEvent::error(e.to_string()));
|
||||
cx.emit(StateEvent::NoSigner);
|
||||
})?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Start the browser proxy
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn connect_proxy(&mut self, cx: &mut Context<Self>) {
|
||||
|
||||
Reference in New Issue
Block a user