add nostr web proxy

This commit is contained in:
2026-08-02 17:00:45 +07:00
parent dbfee32d55
commit 60fc81fef5
7 changed files with 131 additions and 21 deletions

View File

@@ -12,6 +12,7 @@ nostr-sdk.workspace = true
nostr-gossip-memory.workspace = true
nostr-blossom.workspace = true
nostr-connect.workspace = true
nostr-browser-signer-proxy.workspace = true
gpui.workspace = true
instant.workspace = true

View File

@@ -3,7 +3,9 @@ use std::collections::HashMap;
use anyhow::{Error, anyhow};
use common::config_dir;
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, Window};
use gpui_tokio::Tokio;
use instant::Duration;
use nostr_browser_signer_proxy::prelude::*;
use nostr_connect::prelude::*;
use nostr_gossip_memory::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
@@ -307,6 +309,50 @@ impl NostrRegistry {
})
}
/// Start the browser proxy
pub fn connect_proxy(&mut self, cx: &mut Context<Self>) {
let proxy = BrowserSignerProxy::new(BrowserSignerProxyOptions::default());
let (tx, rx) = flume::bounded::<String>(1);
self.tasks.push(Tokio::spawn_result(cx, {
let proxy = proxy.clone();
async move {
// Start the proxy and get the web url
proxy.start().await?;
// Notify GPUI
let url = proxy.url();
tx.send(url).ok();
Ok(())
}
}));
self.tasks.push(Tokio::spawn_result(cx, {
let proxy = proxy.clone();
async move {
loop {
if proxy.is_session_active() {
break;
}
smol::Timer::after(Duration::from_secs(1)).await;
}
Ok(())
}
}));
self.tasks.push(cx.spawn({
let proxy = proxy.clone();
async move |this, cx| {
while let Ok(url) = rx.recv_async().await {
this.update(cx, |this, cx| {
cx.open_url(&url);
this.set_signer(proxy.clone(), cx);
})?;
}
Ok(())
}
}));
}
/// Get the public key of a NIP-05 address
pub fn query_address(&self, addr: Nip05Address, cx: &App) -> Task<Result<PublicKey, Error>> {
let client = self.client();

View File

@@ -14,7 +14,7 @@ pub fn v_flex() -> Div {
/// Returns a `Div` as divider.
pub fn divider(cx: &App) -> Div {
div().my_2().w_full().h_px().bg(cx.theme().border_variant)
div().my_1().w_full().h_px().bg(cx.theme().border_variant)
}
macro_rules! font_weight {

View File

@@ -19,6 +19,7 @@ gpui.workspace = true
nostr-sdk.workspace = true
instant.workspace = true
nostr-connect.workspace = true
nostr-browser-signer-proxy.workspace = true
anyhow.workspace = true
serde.workspace = true

View File

@@ -10,7 +10,7 @@ use state::{CoopAuthUrlHandler, NostrRegistry, USER_KEYRING};
use theme::ActiveTheme;
use ui::button::{Button, ButtonVariants};
use ui::input::{Input, InputEvent, InputState};
use ui::{Disableable, StyledExt, WindowExtension, v_flex};
use ui::{Disableable, StyledExt, WindowExtension, divider, v_flex};
#[derive(Debug)]
pub struct ImportIdentity {
@@ -164,6 +164,13 @@ impl ImportIdentity {
}));
}
fn proxy(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
nostr.update(cx, |this, cx| {
this.connect_proxy(cx);
});
}
fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
self.loading = status;
cx.notify();
@@ -199,10 +206,12 @@ impl ImportIdentity {
impl Render for ImportIdentity {
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
const MSG: &str = "Coop won't store your identity key on the local device. You need to re-login again in the next session. You can use Nostr Connect for persistent login.";
const BUNKER_WARN: &str = "Nostr Connect will usually take more time to get all your messages. Please keep your session open until you see all your messages.";
const KEY_WARN: &str = "Coop won't store your identity key on the local device. You need to re-login again in the next session. You can use Nostr Connect for persistent login.";
let require_password = self.key_input.read(cx).value().starts_with("ncryptsec1");
let key_warning = self.key_input.read(cx).value().starts_with("nsec1") || require_password;
let bunker_warning = self.key_input.read(cx).value().starts_with("bunker://");
v_flex()
.size_full()
@@ -227,13 +236,20 @@ impl Render for ImportIdentity {
.child(Input::new(&self.pass_input)),
)
})
.when(bunker_warning, |this| {
this.child(
div()
.text_xs()
.text_color(cx.theme().text_warning)
.child(div().child(BUNKER_WARN)),
)
})
.when(key_warning, |this| {
this.child(
div()
.text_xs()
.text_color(cx.theme().text_warning)
.child(div().font_semibold().child("Warning"))
.child(div().child(MSG)),
.child(div().child(KEY_WARN)),
)
}),
)
@@ -248,6 +264,17 @@ impl Render for ImportIdentity {
this.login(window, cx);
})),
)
.child(divider(cx))
.child(
Button::new("proxy")
.label("Connect via Web Extension (Experimental)")
.ghost_alt()
.loading(self.loading)
.disabled(self.loading)
.on_click(cx.listener(move |this, _ev, _window, cx| {
this.proxy(cx);
})),
)
.when_some(self.error.read(cx).as_ref(), |this, error| {
this.child(
div()