wip
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m25s

This commit is contained in:
2026-02-15 16:52:35 +07:00
parent a1aaa30a48
commit 452253bece
13 changed files with 610 additions and 569 deletions

View File

@@ -4,11 +4,25 @@ use nostr_sdk::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum DeviceState {
#[default]
Initial,
Idle,
Requesting,
Set,
}
impl DeviceState {
pub fn idle(&self) -> bool {
matches!(self, DeviceState::Idle)
}
pub fn requesting(&self) -> bool {
matches!(self, DeviceState::Requesting)
}
pub fn set(&self) -> bool {
matches!(self, DeviceState::Set)
}
}
/// Announcement
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Announcement {
@@ -24,7 +38,7 @@ impl From<&Event> for Announcement {
let public_key = val
.tags
.iter()
.find(|tag| tag.kind().as_str() == "n" || tag.kind().as_str() == "P")
.find(|tag| tag.kind().as_str() == "n")
.and_then(|tag| tag.content())
.and_then(|c| PublicKey::parse(c).ok())
.unwrap_or(val.pubkey);

View File

@@ -96,13 +96,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,6 +118,10 @@ 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(
@@ -134,6 +134,15 @@ impl NostrRegistry {
}),
);
subscriptions.push(
// Observe the NIP-17 state
cx.observe(&nip17, |this, state, cx| {
if state.read(cx) == &RelayState::Configured {
this.get_messages(cx);
};
}),
);
cx.defer(|cx| {
let nostr = NostrRegistry::global(cx);
@@ -244,30 +253,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,7 +277,7 @@ 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() {
@@ -350,82 +335,6 @@ impl NostrRegistry {
}));
}
/*
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();
@@ -578,6 +487,49 @@ impl NostrRegistry {
}));
}
/// Continuously get gift wrap events for the current user in their messaging relays
fn get_messages(&mut self, cx: &mut Context<Self>) {
let client = self.client();
let signer = self.signer();
let messaging_relays = self.messaging_relays(cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let urls = messaging_relays.await;
let public_key = signer.get_public_key().await?;
// Get messages with dekey
if let Some(signer) = signer.get_encryption_signer().await.as_ref() {
let device_pkey = signer.get_public_key().await?;
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(device_pkey);
let id = SubscriptionId::new(DEVICE_GIFTWRAP);
// Construct target for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).with_id(id).await?;
}
// Get messages with user key
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
let id = SubscriptionId::new(USER_GIFTWRAP);
// Construct target for subscription
let target = urls
.iter()
.map(|relay| (relay, vec![filter.clone()]))
.collect::<HashMap<_, _>>();
client.subscribe(target).with_id(id).await?;
Ok(())
});
task.detach_and_log_err(cx);
}
/// Get profile and contact list for current user
fn get_profile(&mut self, cx: &mut Context<Self>) {
let client = self.client();
@@ -688,6 +640,7 @@ impl NostrRegistry {
.send_event(&event)
.broadcast()
.ok_timeout(Duration::from_secs(TIMEOUT))
.ack_policy(AckPolicy::none())
.await?;
// Construct the default contact list
@@ -699,6 +652,7 @@ impl NostrRegistry {
.send_event(&event)
.broadcast()
.ok_timeout(Duration::from_secs(TIMEOUT))
.ack_policy(AckPolicy::none())
.await?;
// Construct the default messaging relay list
@@ -710,6 +664,7 @@ impl NostrRegistry {
.send_event(&event)
.to_nip65()
.ok_timeout(Duration::from_secs(TIMEOUT))
.ack_policy(AckPolicy::none())
.await?;
// Write user's credentials to the system keyring
@@ -980,6 +935,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![
(

View File

@@ -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 {