feat: revamp the chat panel ui (#7)
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m40s
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m40s
Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
@@ -39,6 +39,8 @@ pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
|
||||
/// Default search relays
|
||||
pub const SEARCH_RELAYS: [&str; 1] = ["wss://antiprimal.net"];
|
||||
|
||||
pub const INDEXER_RELAYS: [&str; 1] = ["wss://indexer.coracle.social"];
|
||||
|
||||
/// Default bootstrap relays
|
||||
pub const BOOTSTRAP_RELAYS: [&str; 3] = [
|
||||
"wss://relay.damus.io",
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
use gpui::SharedString;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub enum DeviceState {
|
||||
#[default]
|
||||
Initial,
|
||||
Requesting,
|
||||
Set,
|
||||
}
|
||||
|
||||
/// Announcement
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Announcement {
|
||||
/// The public key of the device that created this announcement.
|
||||
public_key: PublicKey,
|
||||
|
||||
/// The name of the device that created this announcement.
|
||||
client_name: Option<String>,
|
||||
}
|
||||
|
||||
impl From<&Event> for Announcement {
|
||||
fn from(val: &Event) -> Self {
|
||||
let public_key = val
|
||||
.tags
|
||||
.iter()
|
||||
.find(|tag| tag.kind().as_str() == "n" || tag.kind().as_str() == "P")
|
||||
.and_then(|tag| tag.content())
|
||||
.and_then(|c| PublicKey::parse(c).ok())
|
||||
.unwrap_or(val.pubkey);
|
||||
|
||||
let client_name = val
|
||||
.tags
|
||||
.find(TagKind::Client)
|
||||
.and_then(|tag| tag.content())
|
||||
.map(|c| c.to_string());
|
||||
|
||||
Self::new(public_key, client_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Announcement {
|
||||
pub fn new(public_key: PublicKey, client_name: Option<String>) -> Self {
|
||||
Self {
|
||||
public_key,
|
||||
client_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the public key of the device that created this announcement.
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
self.public_key
|
||||
}
|
||||
|
||||
/// Returns the client name of the device that created this announcement.
|
||||
pub fn client_name(&self) -> SharedString {
|
||||
self.client_name
|
||||
.as_ref()
|
||||
.map(SharedString::from)
|
||||
.unwrap_or(SharedString::from("Unknown"))
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
use nostr_sdk::prelude::*;
|
||||
use smol::lock::RwLock;
|
||||
|
||||
static TRACKER: OnceLock<Arc<RwLock<EventTracker>>> = OnceLock::new();
|
||||
|
||||
pub fn tracker() -> &'static Arc<RwLock<EventTracker>> {
|
||||
TRACKER.get_or_init(|| Arc::new(RwLock::new(EventTracker::default())))
|
||||
}
|
||||
|
||||
/// Event tracker
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EventTracker {
|
||||
/// Tracking events sent by Coop in the current session
|
||||
sent_ids: HashSet<EventId>,
|
||||
|
||||
/// Events that need to be resent later
|
||||
pending_resend: HashSet<(EventId, RelayUrl)>,
|
||||
}
|
||||
|
||||
impl EventTracker {
|
||||
/// Check if an event was sent by Coop in the current session.
|
||||
pub fn is_sent_by_coop(&self, id: &EventId) -> bool {
|
||||
self.sent_ids.contains(id)
|
||||
}
|
||||
|
||||
/// Mark an event as sent by Coop.
|
||||
pub fn sent(&mut self, id: EventId) {
|
||||
self.sent_ids.insert(id);
|
||||
}
|
||||
|
||||
/// Get all events that need to be resent later for a specific relay.
|
||||
pub fn pending_resend(&mut self, relay: &RelayUrl) -> Vec<EventId> {
|
||||
self.pending_resend
|
||||
.extract_if(|(_id, url)| url == relay)
|
||||
.map(|(id, _url)| id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Add an event (id and relay url) to the pending resend set.
|
||||
pub fn add_to_pending(&mut self, id: EventId, url: RelayUrl) {
|
||||
self.pending_resend.insert((id, url));
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,21 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context as AnyhowContext, Error};
|
||||
use common::config_dir;
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
|
||||
use nostr_connect::prelude::*;
|
||||
use nostr_gossip_memory::prelude::*;
|
||||
use nostr_lmdb::NostrLmdb;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
mod constants;
|
||||
mod event;
|
||||
mod nip05;
|
||||
mod signer;
|
||||
|
||||
pub use constants::*;
|
||||
pub use event::*;
|
||||
pub use nip05::*;
|
||||
pub use signer::*;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
pub fn init(window: &mut Window, cx: &mut App) {
|
||||
// rustls uses the `aws_lc_rs` provider by default
|
||||
// This only errors if the default provider has already
|
||||
// been installed. We can ignore this `Result`.
|
||||
@@ -32,10 +30,7 @@ pub fn init(cx: &mut App) {
|
||||
// Initialize the tokio runtime
|
||||
gpui_tokio::init(cx);
|
||||
|
||||
// Initialize the event tracker
|
||||
let _tracker = tracker();
|
||||
|
||||
NostrRegistry::set_global(cx.new(NostrRegistry::new), cx);
|
||||
NostrRegistry::set_global(cx.new(|cx| NostrRegistry::new(window, cx)), cx);
|
||||
}
|
||||
|
||||
struct GlobalNostrRegistry(Entity<NostrRegistry>);
|
||||
@@ -87,7 +82,7 @@ impl NostrRegistry {
|
||||
}
|
||||
|
||||
/// Create a new nostr instance
|
||||
fn new(cx: &mut Context<Self>) -> Self {
|
||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
// Construct the nostr lmdb instance
|
||||
let lmdb = cx.foreground_executor().block_on(async move {
|
||||
NostrLmdb::open(config_dir().join("nostr"))
|
||||
@@ -96,13 +91,9 @@ impl NostrRegistry {
|
||||
});
|
||||
|
||||
// Construct the nostr signer
|
||||
let app_keys = Self::create_or_init_app_keys().unwrap_or(Keys::generate());
|
||||
let app_keys = get_or_init_app_keys().unwrap_or(Keys::generate());
|
||||
let signer = Arc::new(CoopSigner::new(app_keys.clone()));
|
||||
|
||||
// Construct the relay states entity
|
||||
let nip65 = cx.new(|_| RelayState::default());
|
||||
let nip17 = cx.new(|_| RelayState::default());
|
||||
|
||||
// Construct the nostr client
|
||||
let client = ClientBuilder::default()
|
||||
.signer(signer.clone())
|
||||
@@ -122,25 +113,33 @@ impl NostrRegistry {
|
||||
})
|
||||
.build();
|
||||
|
||||
// Construct the relay states entity
|
||||
let nip65 = cx.new(|_| RelayState::default());
|
||||
let nip17 = cx.new(|_| RelayState::default());
|
||||
|
||||
let mut subscriptions = vec![];
|
||||
|
||||
subscriptions.push(
|
||||
// Observe the NIP-65 state
|
||||
cx.observe(&nip65, |this, state, cx| {
|
||||
if state.read(cx).configured() {
|
||||
if state.read(cx).configured().is_some() {
|
||||
this.get_profile(cx);
|
||||
this.get_messaging_relays(cx);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
cx.defer(|cx| {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
subscriptions.push(
|
||||
// Observe the NIP-17 state
|
||||
cx.observe(&nip17, |this, nip17, cx| {
|
||||
if let Some(event) = nip17.read(cx).configured().cloned() {
|
||||
this.subscribe_to_giftwrap_events(&event, cx);
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
// Connect to the bootstrapping relays
|
||||
nostr.update(cx, |this, cx| {
|
||||
this.connect(cx);
|
||||
});
|
||||
cx.defer_in(window, |this, _window, cx| {
|
||||
this.connect(cx);
|
||||
});
|
||||
|
||||
Self {
|
||||
@@ -160,36 +159,35 @@ impl NostrRegistry {
|
||||
fn connect(&mut self, cx: &mut Context<Self>) {
|
||||
let client = self.client();
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
// Add search relay to the relay pool
|
||||
for url in SEARCH_RELAYS.into_iter() {
|
||||
client.add_relay(url).and_connect().await?;
|
||||
}
|
||||
|
||||
// Add bootstrap relay to the relay pool
|
||||
for url in BOOTSTRAP_RELAYS.into_iter() {
|
||||
client.add_relay(url).and_connect().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
// Wait for the task to complete
|
||||
task.await?;
|
||||
|
||||
// Update the state
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_connected(cx);
|
||||
})?;
|
||||
|
||||
// Small delay
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(200))
|
||||
.await_on_background(async move {
|
||||
// Add search relay to the relay pool
|
||||
for url in INDEXER_RELAYS.into_iter() {
|
||||
client
|
||||
.add_relay(url)
|
||||
.capabilities(RelayCapabilities::DISCOVERY)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Add search relay to the relay pool
|
||||
for url in SEARCH_RELAYS.into_iter() {
|
||||
client.add_relay(url).await.ok();
|
||||
}
|
||||
|
||||
// Add bootstrap relay to the relay pool
|
||||
for url in BOOTSTRAP_RELAYS.into_iter() {
|
||||
client.add_relay(url).await.ok();
|
||||
}
|
||||
|
||||
client.connect().await;
|
||||
})
|
||||
.await;
|
||||
|
||||
// Update the state
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_connected(cx);
|
||||
this.get_signer(cx);
|
||||
})?;
|
||||
|
||||
@@ -244,30 +242,6 @@ impl NostrRegistry {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Get a relay hint (messaging relay) for a given public key
|
||||
///
|
||||
/// Used for building chat messages
|
||||
pub fn relay_hint(&self, public_key: &PublicKey, cx: &App) -> Task<Option<RelayUrl>> {
|
||||
let client = self.client();
|
||||
let public_key = public_key.to_owned();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let filter = Filter::new()
|
||||
.author(public_key)
|
||||
.kind(Kind::InboxRelays)
|
||||
.limit(1);
|
||||
|
||||
if let Ok(events) = client.database().query(filter).await {
|
||||
if let Some(event) = events.first_owned() {
|
||||
let relays: Vec<RelayUrl> = nip17::extract_owned_relay_list(event).collect();
|
||||
return relays.first().cloned();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a list of messaging relays with current signer's public key
|
||||
pub fn messaging_relays(&self, cx: &App) -> Task<Vec<RelayUrl>> {
|
||||
let client = self.client();
|
||||
@@ -292,12 +266,11 @@ impl NostrRegistry {
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|events| events.first_owned())
|
||||
.map(|event| nip17::extract_owned_relay_list(event).collect())
|
||||
.map(|event| nip17::extract_owned_relay_list(event).take(3).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
for relay in relays.iter() {
|
||||
client.add_relay(relay).await.ok();
|
||||
client.connect_relay(relay).await.ok();
|
||||
client.add_relay(relay).and_connect().await.ok();
|
||||
}
|
||||
|
||||
relays
|
||||
@@ -305,15 +278,36 @@ impl NostrRegistry {
|
||||
}
|
||||
|
||||
/// Reset all relay states
|
||||
pub fn reset_relay_states(&mut self, cx: &mut Context<Self>) {
|
||||
pub fn reset_relays(&mut self, cx: &mut Context<Self>) {
|
||||
let client = self.client();
|
||||
|
||||
self.nip65.update(cx, |this, cx| {
|
||||
*this = RelayState::default();
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
self.nip17.update(cx, |this, cx| {
|
||||
*this = RelayState::default();
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
self.tasks.push(cx.background_spawn(async move {
|
||||
let relays = client.relays().await;
|
||||
|
||||
for (relay_url, relay) in relays.iter() {
|
||||
let url = relay_url.as_str();
|
||||
let default_relay = BOOTSTRAP_RELAYS.contains(&url)
|
||||
|| SEARCH_RELAYS.contains(&url)
|
||||
|| INDEXER_RELAYS.contains(&url);
|
||||
|
||||
if !default_relay {
|
||||
relay.unsubscribe_all().await?;
|
||||
relay.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/// Set the signer for the nostr client and verify the public key
|
||||
@@ -329,6 +323,9 @@ impl NostrRegistry {
|
||||
// Update signer
|
||||
signer.switch(new, owned).await;
|
||||
|
||||
// Unsubscribe from all subscriptions
|
||||
client.unsubscribe_all().await?;
|
||||
|
||||
// Verify signer
|
||||
let signer = client.signer().context("Signer not found")?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
@@ -343,89 +340,14 @@ impl NostrRegistry {
|
||||
|
||||
// Update states
|
||||
this.update(cx, |this, cx| {
|
||||
this.reset_relay_states(cx);
|
||||
this.reset_relays(cx);
|
||||
this.get_relay_list(cx);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
async fn get_adv_events_by(client: &Client, event: &Event) -> Result<(), Error> {
|
||||
// Subscription options
|
||||
let opts = SubscribeAutoCloseOptions::default()
|
||||
.timeout(Some(Duration::from_secs(TIMEOUT)))
|
||||
.exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
// Extract write relays from event
|
||||
let write_relays: Vec<&RelayUrl> = nip65::extract_relay_list(event)
|
||||
.filter_map(|(url, metadata)| {
|
||||
if metadata.is_none() || metadata == &Some(RelayMetadata::Write) {
|
||||
Some(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Ensure relay connections
|
||||
for relay in write_relays.iter() {
|
||||
client.add_relay(*relay).await?;
|
||||
client.connect_relay(*relay).await?;
|
||||
}
|
||||
|
||||
// Construct filter for inbox relays
|
||||
let inbox = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(event.pubkey)
|
||||
.limit(1);
|
||||
|
||||
// Construct filter for encryption announcement
|
||||
let announcement = Filter::new()
|
||||
.kind(Kind::Custom(10044))
|
||||
.author(event.pubkey)
|
||||
.limit(1);
|
||||
|
||||
// Construct target for subscription
|
||||
let target = write_relays
|
||||
.into_iter()
|
||||
.map(|relay| (relay, vec![inbox.clone(), announcement.clone()]))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
client.subscribe(target).close_on(opts).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
/// Get or create a new app keys
|
||||
fn create_or_init_app_keys() -> Result<Keys, Error> {
|
||||
let dir = config_dir().join(".app_keys");
|
||||
let content = match std::fs::read(&dir) {
|
||||
Ok(content) => content,
|
||||
Err(_) => {
|
||||
// Generate new keys if file doesn't exist
|
||||
let keys = Keys::generate();
|
||||
let secret_key = keys.secret_key();
|
||||
|
||||
// Create directory and write secret key
|
||||
std::fs::create_dir_all(dir.parent().unwrap())?;
|
||||
std::fs::write(&dir, secret_key.to_secret_bytes())?;
|
||||
|
||||
// Set permissions to readonly
|
||||
let mut perms = std::fs::metadata(&dir)?.permissions();
|
||||
perms.set_mode(0o400);
|
||||
std::fs::set_permissions(&dir, perms)?;
|
||||
|
||||
return Ok(keys);
|
||||
}
|
||||
};
|
||||
let secret_key = SecretKey::from_slice(&content)?;
|
||||
let keys = Keys::new(secret_key);
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
// Get relay list for current user
|
||||
fn get_relay_list(&mut self, cx: &mut Context<Self>) {
|
||||
let client = self.client();
|
||||
@@ -454,14 +376,13 @@ impl NostrRegistry {
|
||||
|
||||
let mut stream = client
|
||||
.stream_events(target)
|
||||
.policy(ReqExitPolicy::WaitForEvents(1))
|
||||
.timeout(Duration::from_secs(TIMEOUT))
|
||||
.await?;
|
||||
|
||||
while let Some((_url, res)) = stream.next().await {
|
||||
match res {
|
||||
Ok(event) => {
|
||||
log::info!("Received relay list event: {event:?}");
|
||||
|
||||
// Construct a filter to continuously receive relay list events
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::RelayList)
|
||||
@@ -477,7 +398,7 @@ impl NostrRegistry {
|
||||
// Subscribe to the relay list events
|
||||
client.subscribe(target).await?;
|
||||
|
||||
return Ok(RelayState::Configured);
|
||||
return Ok(RelayState::Configured(Box::new(event)));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to receive relay list event: {e}");
|
||||
@@ -531,14 +452,13 @@ impl NostrRegistry {
|
||||
// Stream events from the write relays
|
||||
let mut stream = client
|
||||
.stream_events(filter)
|
||||
.policy(ReqExitPolicy::WaitForEvents(1))
|
||||
.timeout(Duration::from_secs(TIMEOUT))
|
||||
.await?;
|
||||
|
||||
while let Some((_url, res)) = stream.next().await {
|
||||
match res {
|
||||
Ok(event) => {
|
||||
log::info!("Received messaging relays event: {event:?}");
|
||||
|
||||
// Construct a filter to continuously receive relay list events
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
@@ -548,7 +468,7 @@ impl NostrRegistry {
|
||||
// Subscribe to the relay list events
|
||||
client.subscribe(filter).await?;
|
||||
|
||||
return Ok(RelayState::Configured);
|
||||
return Ok(RelayState::Configured(Box::new(event)));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to get messaging relays: {e}");
|
||||
@@ -578,6 +498,41 @@ impl NostrRegistry {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Continuously get gift wrap events for the current user in their messaging relays
|
||||
fn subscribe_to_giftwrap_events(&mut self, relay_list: &Event, cx: &mut Context<Self>) {
|
||||
let client = self.client();
|
||||
let signer = self.signer();
|
||||
let relay_urls: Vec<RelayUrl> = nip17::extract_relay_list(relay_list).cloned().collect();
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
for url in relay_urls.iter() {
|
||||
client.add_relay(url).and_connect().await?;
|
||||
}
|
||||
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
let id = SubscriptionId::new(USER_GIFTWRAP);
|
||||
|
||||
// Construct target for subscription
|
||||
let target: HashMap<&RelayUrl, Filter> = relay_urls
|
||||
.iter()
|
||||
.map(|relay| (relay, filter.clone()))
|
||||
.collect();
|
||||
|
||||
let output = client.subscribe(target).with_id(id).await?;
|
||||
|
||||
log::info!(
|
||||
"Successfully subscribed to user gift-wrap messages on: {:?}",
|
||||
output.success
|
||||
);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
task.detach();
|
||||
}
|
||||
|
||||
/// Get profile and contact list for current user
|
||||
fn get_profile(&mut self, cx: &mut Context<Self>) {
|
||||
let client = self.client();
|
||||
@@ -648,6 +603,7 @@ impl NostrRegistry {
|
||||
fn set_default_signer(&mut self, cx: &mut Context<Self>) {
|
||||
let client = self.client();
|
||||
let keys = Keys::generate();
|
||||
let async_keys = keys.clone();
|
||||
|
||||
// Create a write credential task
|
||||
let write_credential = cx.write_credentials(
|
||||
@@ -656,21 +612,18 @@ impl NostrRegistry {
|
||||
&keys.secret_key().to_secret_bytes(),
|
||||
);
|
||||
|
||||
// Update the signer
|
||||
self.set_signer(keys, false, cx);
|
||||
|
||||
// Set the creating signer status
|
||||
self.set_creating_signer(true, cx);
|
||||
|
||||
// Run async tasks in background
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let signer = client.signer().context("Signer not found")?;
|
||||
let signer = async_keys.into_nostr_signer();
|
||||
|
||||
// Get default relay list
|
||||
let relay_list = default_relay_list();
|
||||
|
||||
// Publish relay list event
|
||||
let event = EventBuilder::relay_list(relay_list).sign(signer).await?;
|
||||
let event = EventBuilder::relay_list(relay_list).sign(&signer).await?;
|
||||
client
|
||||
.send_event(&event)
|
||||
.broadcast()
|
||||
@@ -683,33 +636,36 @@ impl NostrRegistry {
|
||||
let metadata = Metadata::new().display_name(&name).picture(avatar);
|
||||
|
||||
// Publish metadata event
|
||||
let event = EventBuilder::metadata(&metadata).sign(signer).await?;
|
||||
let event = EventBuilder::metadata(&metadata).sign(&signer).await?;
|
||||
client
|
||||
.send_event(&event)
|
||||
.broadcast()
|
||||
.ok_timeout(Duration::from_secs(TIMEOUT))
|
||||
.ack_policy(AckPolicy::none())
|
||||
.await?;
|
||||
|
||||
// Construct the default contact list
|
||||
let contacts = vec![Contact::new(PublicKey::parse(COOP_PUBKEY).unwrap())];
|
||||
|
||||
// Publish contact list event
|
||||
let event = EventBuilder::contact_list(contacts).sign(signer).await?;
|
||||
let event = EventBuilder::contact_list(contacts).sign(&signer).await?;
|
||||
client
|
||||
.send_event(&event)
|
||||
.broadcast()
|
||||
.ok_timeout(Duration::from_secs(TIMEOUT))
|
||||
.ack_policy(AckPolicy::none())
|
||||
.await?;
|
||||
|
||||
// Construct the default messaging relay list
|
||||
let relays = default_messaging_relays();
|
||||
|
||||
// Publish messaging relay list event
|
||||
let event = EventBuilder::nip17_relay_list(relays).sign(signer).await?;
|
||||
let event = EventBuilder::nip17_relay_list(relays).sign(&signer).await?;
|
||||
client
|
||||
.send_event(&event)
|
||||
.to_nip65()
|
||||
.ok_timeout(Duration::from_secs(TIMEOUT))
|
||||
.ack_policy(AckPolicy::none())
|
||||
.await?;
|
||||
|
||||
// Write user's credentials to the system keyring
|
||||
@@ -724,7 +680,7 @@ impl NostrRegistry {
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_creating_signer(false, cx);
|
||||
this.get_relay_list(cx);
|
||||
this.set_signer(keys, false, cx);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
@@ -743,7 +699,6 @@ impl NostrRegistry {
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_signer(keys, false, cx);
|
||||
this.get_relay_list(cx);
|
||||
})?;
|
||||
}
|
||||
_ => {
|
||||
@@ -786,7 +741,6 @@ impl NostrRegistry {
|
||||
Ok(signer) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_signer(signer, true, cx);
|
||||
this.get_relay_list(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -882,9 +836,30 @@ impl NostrRegistry {
|
||||
let client = self.client();
|
||||
let query = query.to_string();
|
||||
|
||||
// Get the address task if the query is a valid NIP-05 address
|
||||
let address_task = if let Ok(addr) = Nip05Address::parse(&query) {
|
||||
Some(self.get_address(addr, cx))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let mut results: Vec<PublicKey> = Vec::with_capacity(FIND_LIMIT);
|
||||
|
||||
// Return early if the query is a valid NIP-05 address
|
||||
if let Some(task) = address_task {
|
||||
if let Ok(public_key) = task.await {
|
||||
results.push(public_key);
|
||||
return Ok(results);
|
||||
}
|
||||
}
|
||||
|
||||
// Return early if the query is a valid public key
|
||||
if let Ok(public_key) = PublicKey::parse(&query) {
|
||||
results.push(public_key);
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
// Construct the filter for the search query
|
||||
let filter = Filter::new()
|
||||
.search(query.to_lowercase())
|
||||
@@ -980,6 +955,36 @@ impl NostrRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get or create a new app keys
|
||||
fn get_or_init_app_keys() -> Result<Keys, Error> {
|
||||
let dir = config_dir().join(".app_keys");
|
||||
|
||||
let content = match std::fs::read(&dir) {
|
||||
Ok(content) => content,
|
||||
Err(_) => {
|
||||
// Generate new keys if file doesn't exist
|
||||
let keys = Keys::generate();
|
||||
let secret_key = keys.secret_key();
|
||||
|
||||
// Create directory and write secret key
|
||||
std::fs::create_dir_all(dir.parent().unwrap())?;
|
||||
std::fs::write(&dir, secret_key.to_secret_bytes())?;
|
||||
|
||||
// Set permissions to readonly
|
||||
let mut perms = std::fs::metadata(&dir)?.permissions();
|
||||
perms.set_mode(0o400);
|
||||
std::fs::set_permissions(&dir, perms)?;
|
||||
|
||||
return Ok(keys);
|
||||
}
|
||||
};
|
||||
|
||||
let secret_key = SecretKey::from_slice(&content)?;
|
||||
let keys = Keys::new(secret_key);
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
fn default_relay_list() -> Vec<(RelayUrl, Option<RelayMetadata>)> {
|
||||
vec![
|
||||
(
|
||||
@@ -991,7 +996,7 @@ fn default_relay_list() -> Vec<(RelayUrl, Option<RelayMetadata>)> {
|
||||
Some(RelayMetadata::Write),
|
||||
),
|
||||
(
|
||||
RelayUrl::parse("wss://relay.primal.net/").unwrap(),
|
||||
RelayUrl::parse("wss://relay.damus.io/").unwrap(),
|
||||
Some(RelayMetadata::Read),
|
||||
),
|
||||
(
|
||||
@@ -1003,18 +1008,18 @@ fn default_relay_list() -> Vec<(RelayUrl, Option<RelayMetadata>)> {
|
||||
|
||||
fn default_messaging_relays() -> Vec<RelayUrl> {
|
||||
vec![
|
||||
RelayUrl::parse("wss://auth.nostr1.com/").unwrap(),
|
||||
//RelayUrl::parse("wss://auth.nostr1.com/").unwrap(),
|
||||
RelayUrl::parse("wss://nip17.com/").unwrap(),
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub enum RelayState {
|
||||
#[default]
|
||||
Idle,
|
||||
Checking,
|
||||
NotConfigured,
|
||||
Configured,
|
||||
Configured(Box<Event>),
|
||||
}
|
||||
|
||||
impl RelayState {
|
||||
@@ -1030,8 +1035,11 @@ impl RelayState {
|
||||
matches!(self, RelayState::NotConfigured)
|
||||
}
|
||||
|
||||
pub fn configured(&self) -> bool {
|
||||
matches!(self, RelayState::Configured)
|
||||
pub fn configured(&self) -> Option<&Event> {
|
||||
match self {
|
||||
RelayState::Configured(event) => Some(event),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,15 @@ use smol::lock::RwLock;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CoopSigner {
|
||||
/// User's signer
|
||||
signer: RwLock<Arc<dyn NostrSigner>>,
|
||||
|
||||
/// Signer's public key
|
||||
/// User's signer public key
|
||||
signer_pkey: RwLock<Option<PublicKey>>,
|
||||
|
||||
/// Specific signer for encryption purposes
|
||||
encryption_signer: RwLock<Option<Arc<dyn NostrSigner>>>,
|
||||
|
||||
/// Whether coop is creating a new identity
|
||||
creating: AtomicBool,
|
||||
|
||||
@@ -30,6 +34,7 @@ impl CoopSigner {
|
||||
Self {
|
||||
signer: RwLock::new(signer.into_nostr_signer()),
|
||||
signer_pkey: RwLock::new(None),
|
||||
encryption_signer: RwLock::new(None),
|
||||
creating: AtomicBool::new(false),
|
||||
owned: AtomicBool::new(false),
|
||||
}
|
||||
@@ -40,6 +45,11 @@ impl CoopSigner {
|
||||
self.signer.read().await.clone()
|
||||
}
|
||||
|
||||
/// Get the encryption signer.
|
||||
pub async fn get_encryption_signer(&self) -> Option<Arc<dyn NostrSigner>> {
|
||||
self.encryption_signer.read().await.clone()
|
||||
}
|
||||
|
||||
/// Get public key
|
||||
pub fn public_key(&self) -> Option<PublicKey> {
|
||||
self.signer_pkey.read_blocking().to_owned()
|
||||
@@ -64,6 +74,7 @@ impl CoopSigner {
|
||||
let public_key = new_signer.get_public_key().await.ok();
|
||||
let mut signer = self.signer.write().await;
|
||||
let mut signer_pkey = self.signer_pkey.write().await;
|
||||
let mut encryption_signer = self.encryption_signer.write().await;
|
||||
|
||||
// Switch to the new signer
|
||||
*signer = new_signer;
|
||||
@@ -71,9 +82,21 @@ impl CoopSigner {
|
||||
// Update the public key
|
||||
*signer_pkey = public_key;
|
||||
|
||||
// Reset the encryption signer
|
||||
*encryption_signer = None;
|
||||
|
||||
// Update the owned flag
|
||||
self.owned.store(owned, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Set the encryption signer.
|
||||
pub async fn set_encryption_signer<T>(&self, new: T)
|
||||
where
|
||||
T: IntoNostrSigner,
|
||||
{
|
||||
let mut encryption_signer = self.encryption_signer.write().await;
|
||||
*encryption_signer = Some(new.into_nostr_signer());
|
||||
}
|
||||
}
|
||||
|
||||
impl NostrSigner for CoopSigner {
|
||||
|
||||
Reference in New Issue
Block a user