wip
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m25s
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m25s
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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![
|
||||
(
|
||||
|
||||
@@ -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