This commit is contained in:
2026-03-09 13:58:49 +07:00
parent aec32e450a
commit 39c04cabad
16 changed files with 475 additions and 554 deletions

View File

@@ -10,6 +10,7 @@ common = { path = "../common" }
nostr.workspace = true
nostr-sdk.workspace = true
nostr-lmdb.workspace = true
nostr-memory.workspace = true
nostr-gossip-sqlite.workspace = true
nostr-connect.workspace = true
nostr-blossom.workspace = true

View File

@@ -4,10 +4,11 @@ use std::time::Duration;
use anyhow::{Context as AnyhowContext, Error, anyhow};
use common::config_dir;
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, Window};
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, SharedString, Task, Window};
use nostr_connect::prelude::*;
use nostr_gossip_sqlite::prelude::*;
use nostr_lmdb::prelude::*;
use nostr_memory::prelude::*;
use nostr_sdk::prelude::*;
mod blossom;
@@ -42,6 +43,21 @@ struct GlobalNostrRegistry(Entity<NostrRegistry>);
impl Global for GlobalNostrRegistry {}
/// Signer event.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum StateEvent {
/// Connecting to the bootstrapping relay
Connecting,
/// Connected to the bootstrapping relay
Connected,
/// User has not set up NIP-65 relays
RelayNotConfigured,
/// A new signer has been set
SignerSet,
/// An error occurred
Error(SharedString),
}
/// Nostr Registry
#[derive(Debug)]
pub struct NostrRegistry {
@@ -57,15 +73,14 @@ pub struct NostrRegistry {
/// App keys
///
/// Used for Nostr Connect and NIP-4e operations
pub app_keys: Keys,
/// Relay list state
pub relay_list_state: RelayState,
app_keys: Keys,
/// Tasks for asynchronous operations
tasks: Vec<Task<Result<(), Error>>>,
}
impl EventEmitter<StateEvent> for NostrRegistry {}
impl NostrRegistry {
/// Retrieve the global nostr state
pub fn global(cx: &App) -> Entity<Self> {
@@ -93,25 +108,32 @@ impl NostrRegistry {
.expect("Failed to initialize gossip instance")
});
// Construct the nostr lmdb instance
let lmdb = cx.foreground_executor().block_on(async move {
NostrLmdb::open(config_dir().join("nostr"))
.await
.expect("Failed to initialize database")
});
// Construct the nostr client
let client = ClientBuilder::default()
// Construct the nostr client builder
let mut builder = ClientBuilder::default()
.signer(signer.clone())
.gossip(gossip)
.database(lmdb)
.automatic_authentication(false)
.verify_subscriptions(false)
.connect_timeout(Duration::from_secs(TIMEOUT))
.sleep_when_idle(SleepWhenIdle::Enabled {
timeout: Duration::from_secs(600),
})
.build();
});
// Add database if not in debug mode
if !cfg!(debug_assertions) {
// Construct the nostr lmdb instance
let lmdb = cx.foreground_executor().block_on(async move {
NostrLmdb::open(config_dir().join("nostr"))
.await
.expect("Failed to initialize database")
});
builder = builder.database(lmdb);
} else {
builder = builder.database(MemoryDatabase::unbounded())
}
// Build the nostr client
let client = builder.build();
// Run at the end of current cycle
cx.defer_in(window, |this, _window, cx| {
@@ -123,7 +145,6 @@ impl NostrRegistry {
signer,
npubs,
app_keys,
relay_list_state: RelayState::Idle,
tasks: vec![],
}
}
@@ -143,10 +164,18 @@ impl NostrRegistry {
self.npubs.clone()
}
/// Get the app keys
pub fn keys(&self) -> Keys {
self.app_keys.clone()
}
/// Connect to the bootstrapping relays
fn connect(&mut self, cx: &mut Context<Self>) {
let client = self.client();
// Emit connecting event
cx.emit(StateEvent::Connecting);
self.tasks.push(cx.spawn(async move |this, cx| {
cx.background_executor()
.await_on_background(async move {
@@ -167,6 +196,7 @@ impl NostrRegistry {
// Update the state
this.update(cx, |this, cx| {
cx.emit(StateEvent::Connected);
this.get_npubs(cx);
})?;
@@ -177,6 +207,7 @@ impl NostrRegistry {
/// Get all used npubs
fn get_npubs(&mut self, cx: &mut Context<Self>) {
let npubs = self.npubs.downgrade();
let task: Task<Result<Vec<PublicKey>, Error>> = cx.background_spawn(async move {
let dir = config_dir().join("keys");
// Ensure keys directory exists
@@ -227,9 +258,8 @@ impl NostrRegistry {
}
},
Err(e) => {
log::error!("Failed to get npubs: {e}");
this.update(cx, |this, cx| {
this.create_identity(cx);
this.update(cx, |_this, cx| {
cx.emit(StateEvent::Error(SharedString::from(e.to_string())));
})?;
}
}
@@ -401,14 +431,13 @@ impl NostrRegistry {
cx.notify();
}
});
// Emit signer changed event
cx.emit(SignerEvent::Set);
cx.emit(StateEvent::SignerSet);
})?;
}
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
cx.emit(StateEvent::Error(SharedString::from(e.to_string())));
})?;
}
}
@@ -456,7 +485,7 @@ impl NostrRegistry {
}
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
cx.emit(StateEvent::Error(SharedString::from(e.to_string())));
})?;
}
}
@@ -495,14 +524,14 @@ impl NostrRegistry {
}
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
cx.emit(StateEvent::Error(SharedString::from(e.to_string())));
})?;
}
}
}
Err(e) => {
this.update(cx, |_this, cx| {
cx.emit(SignerEvent::Error(e.to_string()));
cx.emit(StateEvent::Error(SharedString::from(e.to_string())));
})?;
}
}
@@ -666,8 +695,6 @@ impl NostrRegistry {
}
}
impl EventEmitter<SignerEvent> for NostrRegistry {}
/// Get or create a new app keys
fn get_or_init_app_keys() -> Result<Keys, Error> {
let dir = config_dir().join(".app_keys");
@@ -726,43 +753,6 @@ fn default_messaging_relays() -> Vec<RelayUrl> {
]
}
/// Signer event.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum SignerEvent {
/// A new signer has been set
Set,
/// An error occurred
Error(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum RelayState {
#[default]
Idle,
Checking,
NotConfigured,
Configured,
}
impl RelayState {
pub fn idle(&self) -> bool {
matches!(self, RelayState::Idle)
}
pub fn checking(&self) -> bool {
matches!(self, RelayState::Checking)
}
pub fn not_configured(&self) -> bool {
matches!(self, RelayState::NotConfigured)
}
pub fn configured(&self) -> bool {
matches!(self, RelayState::Configured)
}
}
#[derive(Debug, Clone)]
pub struct CoopAuthUrlHandler;