add NIP-07 browser extension signer support for wasm
This commit is contained in:
Generated
+27
-11
@@ -1223,6 +1223,7 @@ dependencies = [
|
|||||||
name = "common"
|
name = "common"
|
||||||
version = "2.0.0-alpha2"
|
version = "2.0.0-alpha2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"futures",
|
"futures",
|
||||||
@@ -1232,6 +1233,7 @@ dependencies = [
|
|||||||
"nostr",
|
"nostr",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"regex",
|
"regex",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1824,7 +1826,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users 0.5.3",
|
"redox_users 0.5.3",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2052,7 +2054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4900,6 +4902,19 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nostr-browser-signer"
|
||||||
|
version = "0.45.1"
|
||||||
|
source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"nostr",
|
||||||
|
"opaquerr 0.2.0",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.45.2"
|
version = "0.45.2"
|
||||||
@@ -5047,7 +5062,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6134,7 +6149,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6707,7 +6722,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6775,7 +6790,7 @@ dependencies = [
|
|||||||
"security-framework",
|
"security-framework",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs",
|
"webpki-root-certs",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7381,7 +7396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7434,6 +7449,7 @@ dependencies = [
|
|||||||
"mime_guess",
|
"mime_guess",
|
||||||
"nostr",
|
"nostr",
|
||||||
"nostr-blossom",
|
"nostr-blossom",
|
||||||
|
"nostr-browser-signer",
|
||||||
"nostr-connect",
|
"nostr-connect",
|
||||||
"nostr-gossip-memory",
|
"nostr-gossip-memory",
|
||||||
"nostr-lmdb",
|
"nostr-lmdb",
|
||||||
@@ -7702,7 +7718,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8233,7 +8249,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9031,7 +9047,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9656,7 +9672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ nostr-memory = { git = "https://github.com/rust-nostr/nostr" }
|
|||||||
nostr-blossom = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-blossom = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
|
nostr-browser-signer = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "nip49", "nip44", "nip98" ] }
|
nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "nip49", "nip44", "nip98" ] }
|
||||||
|
|
||||||
|
|||||||
@@ -16,3 +16,7 @@ futures.workspace = true
|
|||||||
|
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
|
anyhow.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
web-sys = { version = "0.3", features = ["Window", "Storage"] }
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
pub use debounced_delay::*;
|
|
||||||
pub use display::*;
|
|
||||||
pub use event::*;
|
|
||||||
pub use media_extractor::*;
|
|
||||||
pub use parser::*;
|
|
||||||
pub use paths::*;
|
|
||||||
|
|
||||||
mod debounced_delay;
|
mod debounced_delay;
|
||||||
mod display;
|
mod display;
|
||||||
mod event;
|
mod event;
|
||||||
mod media_extractor;
|
mod media_extractor;
|
||||||
mod parser;
|
mod parser;
|
||||||
mod paths;
|
mod paths;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub mod web_store;
|
||||||
|
|
||||||
|
pub use debounced_delay::*;
|
||||||
|
pub use display::*;
|
||||||
|
pub use event::*;
|
||||||
|
pub use media_extractor::*;
|
||||||
|
pub use parser::*;
|
||||||
|
pub use paths::*;
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use anyhow::{Error, anyhow};
|
||||||
|
|
||||||
|
const LOCAL_STORAGE: &str = "localStorage";
|
||||||
|
|
||||||
|
fn storage() -> Result<web_sys::Storage, Error> {
|
||||||
|
web_sys::window()
|
||||||
|
.ok_or_else(|| anyhow!("no browser window"))?
|
||||||
|
.local_storage()
|
||||||
|
.map_err(|e| anyhow!("{LOCAL_STORAGE} unavailable: {e:?}"))?
|
||||||
|
.ok_or_else(|| anyhow!("{LOCAL_STORAGE} unavailable"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a value previously written with [`set`]
|
||||||
|
pub fn get(key: &str) -> Result<Option<String>, Error> {
|
||||||
|
storage()?
|
||||||
|
.get_item(key)
|
||||||
|
.map_err(|e| anyhow!("{LOCAL_STORAGE} read failed: {e:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a value, replacing any previous one
|
||||||
|
pub fn set(key: &str, value: &str) -> Result<(), Error> {
|
||||||
|
storage()?
|
||||||
|
.set_item(key, value)
|
||||||
|
.map_err(|e| anyhow!("{LOCAL_STORAGE} write failed: {e:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a value if it exists
|
||||||
|
pub fn remove(key: &str) -> Result<(), Error> {
|
||||||
|
storage()?
|
||||||
|
.remove_item(key)
|
||||||
|
.map_err(|e| anyhow!("{LOCAL_STORAGE} delete failed: {e:?}"))
|
||||||
|
}
|
||||||
+32
-18
@@ -1,12 +1,17 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Error, anyhow};
|
use anyhow::{Error, anyhow};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use common::config_dir;
|
use common::config_dir;
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, ImageSource, Subscription, Task, px};
|
use gpui::{App, AppContext, Context, Entity, Global, ImageSource, Subscription, Task, px};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
|
|
||||||
|
/// Browser storage key for the settings JSON
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
const SETTINGS_KEY: &str = "coop.settings";
|
||||||
|
|
||||||
/// The user's appearance preference, persisted with the rest of the settings.
|
/// The user's appearance preference, persisted with the rest of the settings.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||||
pub enum AppearanceMode {
|
pub enum AppearanceMode {
|
||||||
@@ -63,21 +68,21 @@ pub fn init(cx: &mut App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read the persisted appearance mode synchronously.
|
/// Read the persisted appearance mode synchronously.
|
||||||
///
|
|
||||||
/// The async load fills the global settings later; the native theme must be
|
|
||||||
/// applied before the first window opens, so boot reads the mode straight
|
|
||||||
/// from the settings file instead.
|
|
||||||
fn persisted_settings() -> Option<Settings> {
|
fn persisted_settings() -> Option<Settings> {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
let content = persisted_content();
|
||||||
{
|
content.and_then(|content| serde_json::from_str(&content).ok())
|
||||||
let path = config_dir().join(".settings");
|
}
|
||||||
if let Ok(content) = std::fs::read_to_string(path)
|
|
||||||
&& let Ok(settings) = serde_json::from_str::<Settings>(&content)
|
/// The persisted settings JSON, from the config file or browser storage
|
||||||
{
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
return Some(settings);
|
fn persisted_content() -> Option<String> {
|
||||||
}
|
let path = config_dir().join(".settings");
|
||||||
}
|
std::fs::read_to_string(path).ok()
|
||||||
None
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn persisted_content() -> Option<String> {
|
||||||
|
common::web_store::get(SETTINGS_KEY).ok().flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persisted_appearance_mode() -> AppearanceMode {
|
pub fn persisted_appearance_mode() -> AppearanceMode {
|
||||||
@@ -87,10 +92,6 @@ pub fn persisted_appearance_mode() -> AppearanceMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read the persisted theme tuning synchronously.
|
/// Read the persisted theme tuning synchronously.
|
||||||
///
|
|
||||||
/// Like [`persisted_appearance_mode`], the native theme is applied before the
|
|
||||||
/// async settings load completes, so boot reads the tuning straight from the
|
|
||||||
/// settings file instead.
|
|
||||||
pub fn persisted_theme() -> ThemeSettings {
|
pub fn persisted_theme() -> ThemeSettings {
|
||||||
persisted_settings()
|
persisted_settings()
|
||||||
.map(|settings| settings.theme)
|
.map(|settings| settings.theme)
|
||||||
@@ -357,6 +358,14 @@ impl AppSettings {
|
|||||||
return Ok(serde_json::from_str(&content)?);
|
return Ok(serde_json::from_str(&content)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
if let Some(content) = common::web_store::get(SETTINGS_KEY)? {
|
||||||
|
return Ok(serde_json::from_str(&content)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Err(anyhow!("Not found"))
|
Err(anyhow!("Not found"))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -389,6 +398,11 @@ impl AppSettings {
|
|||||||
smol::fs::write(&path, content).await.ok();
|
smol::fs::write(&path, content).await.ok();
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
if let Err(e) = common::web_store::set(SETTINGS_KEY, &content) {
|
||||||
|
log::warn!("failed to persist settings: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ sha2.workspace = true
|
|||||||
data-encoding.workspace = true
|
data-encoding.workspace = true
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
nostr-browser-signer.workspace = true
|
||||||
nostr-memory.workspace = true
|
nostr-memory.workspace = true
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use gpui_tokio::Tokio;
|
use gpui_tokio::Tokio;
|
||||||
use instant::Duration;
|
use instant::Duration;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use nostr_browser_signer::BrowserSigner;
|
||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
use nostr_gossip_memory::prelude::*;
|
use nostr_gossip_memory::prelude::*;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -19,6 +21,7 @@ use nostr_sdk::prelude::*;
|
|||||||
|
|
||||||
mod blossom;
|
mod blossom;
|
||||||
mod constants;
|
mod constants;
|
||||||
|
mod credentials;
|
||||||
mod file;
|
mod file;
|
||||||
mod nip05;
|
mod nip05;
|
||||||
mod nip4e;
|
mod nip4e;
|
||||||
@@ -138,9 +141,7 @@ impl NostrRegistry {
|
|||||||
let _ = entity.update(cx, |this, cx| {
|
let _ = entity.update(cx, |this, cx| {
|
||||||
this.connect_bootstrap_relays(cx);
|
this.connect_bootstrap_relays(cx);
|
||||||
|
|
||||||
if cfg!(target_arch = "wasm32") {
|
if let Some(secret) = cli_key {
|
||||||
cx.emit(StateEvent::NoSigner);
|
|
||||||
} else if let Some(secret) = cli_key {
|
|
||||||
// Use CLI-provided key -- same path as restore_session
|
// Use CLI-provided key -- same path as restore_session
|
||||||
let keys = Keys::new(secret);
|
let keys = Keys::new(secret);
|
||||||
this.set_signer(keys, cx);
|
this.set_signer(keys, cx);
|
||||||
@@ -242,7 +243,7 @@ impl NostrRegistry {
|
|||||||
|
|
||||||
/// Restore the signer from the stored credential
|
/// Restore the signer from the stored credential
|
||||||
fn restore_session(&mut self, cx: &mut Context<Self>) {
|
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| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
let result = async {
|
let result = async {
|
||||||
@@ -279,6 +280,11 @@ impl NostrRegistry {
|
|||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.connect_proxy(cx);
|
this.connect_proxy(cx);
|
||||||
})?;
|
})?;
|
||||||
|
} else if content == "extension" {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.connect_extension(cx);
|
||||||
|
})?;
|
||||||
} else {
|
} else {
|
||||||
this.update(cx, |_, cx| {
|
this.update(cx, |_, cx| {
|
||||||
cx.emit(StateEvent::NoSigner);
|
cx.emit(StateEvent::NoSigner);
|
||||||
@@ -314,7 +320,7 @@ impl NostrRegistry {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<PublicKey, Error>> {
|
) -> Task<Result<PublicKey, Error>> {
|
||||||
let password = password.to_owned();
|
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| {
|
cx.spawn(async move |this, cx| {
|
||||||
let content = user_keyring
|
let content = user_keyring
|
||||||
@@ -373,9 +379,8 @@ impl NostrRegistry {
|
|||||||
let (keys, ncryptsec) = job.await?;
|
let (keys, ncryptsec) = job.await?;
|
||||||
let public_key = keys.public_key();
|
let public_key = keys.public_key();
|
||||||
|
|
||||||
let write = cx.update(|cx| {
|
let write =
|
||||||
cx.write_credentials(USER_KEYRING, &public_key.to_hex(), ncryptsec.as_bytes())
|
cx.update(|cx| credentials::write(cx, &public_key.to_hex(), ncryptsec.as_bytes()));
|
||||||
});
|
|
||||||
write.await?;
|
write.await?;
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
@@ -412,9 +417,8 @@ impl NostrRegistry {
|
|||||||
let keys = decrypt_task.await?;
|
let keys = decrypt_task.await?;
|
||||||
let public_key = keys.public_key();
|
let public_key = keys.public_key();
|
||||||
|
|
||||||
let write = cx.update(|cx| {
|
let write =
|
||||||
cx.write_credentials(USER_KEYRING, &public_key.to_hex(), stored.as_bytes())
|
cx.update(|cx| credentials::write(cx, &public_key.to_hex(), stored.as_bytes()));
|
||||||
});
|
|
||||||
write.await?;
|
write.await?;
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
@@ -440,7 +444,7 @@ impl NostrRegistry {
|
|||||||
|
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
let credential = with_master_key(&uri_string, &keys);
|
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| {
|
cx.spawn(async move |this, cx| {
|
||||||
let signer = NostrConnect::builder(connect_uri, keys)
|
let signer = NostrConnect::builder(connect_uri, keys)
|
||||||
@@ -464,12 +468,56 @@ impl NostrRegistry {
|
|||||||
self.current_user = None;
|
self.current_user = None;
|
||||||
self.passphrase_required = false;
|
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::SignerChanged);
|
||||||
cx.emit(StateEvent::NoSigner);
|
cx.emit(StateEvent::NoSigner);
|
||||||
cx.notify();
|
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
|
/// Start the browser proxy
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn connect_proxy(&mut self, cx: &mut Context<Self>) {
|
pub fn connect_proxy(&mut self, cx: &mut Context<Self>) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use gpui_kit::component::input::{Input, InputEvent, InputState};
|
|||||||
use gpui_kit::component::{ActiveTheme, Disableable, WindowExt, v_flex};
|
use gpui_kit::component::{ActiveTheme, Disableable, WindowExt, v_flex};
|
||||||
use instant::Duration;
|
use instant::Duration;
|
||||||
use nostr_connect::prelude::*;
|
use nostr_connect::prelude::*;
|
||||||
use state::NostrRegistry;
|
use state::{NostrRegistry, StateEvent};
|
||||||
|
|
||||||
pub fn open(window: &mut Window, cx: &mut App) {
|
pub fn open(window: &mut Window, cx: &mut App) {
|
||||||
let import = cx.new(|cx| ImportIdentity::new(window, cx));
|
let import = cx.new(|cx| ImportIdentity::new(window, cx));
|
||||||
@@ -73,12 +73,13 @@ impl ImportIdentity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(mode: Mode, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn build(mode: Mode, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
let key_input = cx.new(|cx| InputState::new(window, cx).placeholder("secret or bunker://"));
|
let key_input = cx.new(|cx| InputState::new(window, cx).placeholder("secret or bunker://"));
|
||||||
let pass_input = cx.new(|cx| InputState::new(window, cx).masked(true));
|
let pass_input = cx.new(|cx| InputState::new(window, cx).masked(true));
|
||||||
let repass_input = cx.new(|cx| InputState::new(window, cx).masked(true));
|
let repass_input = cx.new(|cx| InputState::new(window, cx).masked(true));
|
||||||
let error = cx.new(|_| None);
|
let error = cx.new(|_| None);
|
||||||
|
|
||||||
let subscriptions = vec![
|
let mut subscriptions = vec![
|
||||||
cx.subscribe_in(&key_input, window, |this, _input, event, _window, cx| {
|
cx.subscribe_in(&key_input, window, |this, _input, event, _window, cx| {
|
||||||
if let InputEvent::PressEnter { .. } = event {
|
if let InputEvent::PressEnter { .. } = event {
|
||||||
this.login(cx);
|
this.login(cx);
|
||||||
@@ -96,6 +97,14 @@ impl ImportIdentity {
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
subscriptions.push(
|
||||||
|
cx.subscribe_in(&nostr, window, |this, _nostr, event, _window, cx| {
|
||||||
|
if let StateEvent::Error(error) = event {
|
||||||
|
this.set_error(error.clone(), cx);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
mode,
|
mode,
|
||||||
key_input,
|
key_input,
|
||||||
@@ -188,6 +197,14 @@ impl ImportIdentity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn extension(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
nostr.update(cx, |this, cx| {
|
||||||
|
this.connect_extension(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||||
self.loading = status;
|
self.loading = status;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -329,6 +346,19 @@ impl Render for ImportIdentity {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let content = content.when(!is_unlock, |this| {
|
||||||
|
this.child(
|
||||||
|
Button::new("extension")
|
||||||
|
.label("Continue with Browser Extension (NIP-07)")
|
||||||
|
.ghost()
|
||||||
|
.disabled(self.loading)
|
||||||
|
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
||||||
|
this.extension(cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
content
|
content
|
||||||
})
|
})
|
||||||
.when_some(self.error.read(cx).as_ref(), |this, error| {
|
.when_some(self.error.read(cx).as_ref(), |this, error| {
|
||||||
|
|||||||
Reference in New Issue
Block a user