Continue redesign for the v1 stable release (#5)
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m32s

Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
2026-02-12 08:32:17 +00:00
parent 32201554ec
commit ecd7f6aa9b
43 changed files with 2794 additions and 3409 deletions

View File

@@ -9,6 +9,20 @@ pub enum DeviceState {
Set,
}
impl DeviceState {
pub fn initial(&self) -> bool {
matches!(self, DeviceState::Initial)
}
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 {

View File

@@ -1,17 +1,17 @@
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::Duration;
use anyhow::{anyhow, Context as AnyhowContext, Error};
use common::app_name;
pub use device::*;
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
use nostr_sdk::prelude::*;
use smallvec::{smallvec, SmallVec};
use state::{NostrRegistry, RelayState, DEVICE_GIFTWRAP, TIMEOUT, USER_GIFTWRAP};
use state::{app_name, NostrRegistry, RelayState, DEVICE_GIFTWRAP, TIMEOUT, USER_GIFTWRAP};
mod device;
pub use device::*;
const IDENTIFIER: &str = "coop:device";
pub fn init(cx: &mut App) {
@@ -30,17 +30,17 @@ pub struct DeviceRegistry {
/// Device signer
pub device_signer: Entity<Option<Arc<dyn NostrSigner>>>,
/// Device state
pub state: DeviceState,
/// Device requests
requests: Entity<HashSet<Event>>,
/// Device state
state: DeviceState,
/// Async tasks
tasks: Vec<Task<Result<(), Error>>>,
/// Subscriptions
_subscriptions: SmallVec<[Subscription; 1]>,
_subscriptions: SmallVec<[Subscription; 2]>,
}
impl DeviceRegistry {
@@ -58,7 +58,8 @@ impl DeviceRegistry {
fn new(cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let identity = nostr.read(cx).identity();
let nip65_state = nostr.read(cx).nip65_state();
let nip17_state = nostr.read(cx).nip17_state();
let device_signer = cx.new(|_| None);
let requests = cx.new(|_| HashSet::default());
@@ -70,21 +71,26 @@ impl DeviceRegistry {
let mut tasks = vec![];
subscriptions.push(
// Observe the identity entity
cx.observe(&identity, |this, state, cx| {
match state.read(cx).relay_list_state() {
RelayState::Initial => {
// Observe the NIP-65 state
cx.observe(&nip65_state, |this, state, cx| {
match state.read(cx) {
RelayState::Idle => {
this.reset(cx);
}
RelayState::Set => {
RelayState::Configured => {
this.get_announcement(cx);
if state.read(cx).messaging_relays_state() == RelayState::Set {
this.get_messages(cx);
}
}
_ => {}
}
};
}),
);
subscriptions.push(
// Observe the NIP-17 state
cx.observe(&nip17_state, |this, state, cx| {
if state.read(cx) == &RelayState::Configured {
this.get_messages(cx);
};
}),
);
@@ -130,8 +136,8 @@ impl DeviceRegistry {
let mut notifications = client.notifications();
let mut processed_events = HashSet::new();
while let Ok(notification) = notifications.recv().await {
if let RelayPoolNotification::Message {
while let Some(notification) = notifications.next().await {
if let ClientNotification::Message {
message: RelayMessage::Event { event, .. },
..
} = notification
@@ -162,7 +168,7 @@ impl DeviceRegistry {
/// Verify the author of an event
async fn verify_author(client: &Client, event: &Event) -> bool {
if let Ok(signer) = client.signer().await {
if let Some(signer) = client.signer() {
if let Ok(public_key) = signer.get_public_key().await {
return public_key == event.pubkey;
}
@@ -172,7 +178,7 @@ impl DeviceRegistry {
/// Encrypt and store device keys in the local database.
async fn set_keys(client: &Client, secret: &str) -> Result<(), Error> {
let signer = client.signer().await?;
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
// Encrypt the value
@@ -193,7 +199,7 @@ impl DeviceRegistry {
/// Get device keys from the local database.
async fn get_keys(client: &Client) -> Result<Keys, Error> {
let signer = client.signer().await?;
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
let filter = Filter::new()
@@ -244,6 +250,8 @@ impl DeviceRegistry {
*this = Some(Arc::new(signer));
cx.notify();
});
log::info!("Device Signer set");
}
/// Set the device state
@@ -265,40 +273,44 @@ impl DeviceRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let device_signer = self.device_signer.read(cx).clone();
let messaging_relays = nostr.read(cx).messaging_relays(cx);
let public_key = nostr.read(cx).identity().read(cx).public_key();
let messaging_relays = nostr.read(cx).messaging_relays(&public_key, cx);
cx.background_spawn(async move {
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let urls = messaging_relays.await;
let user_signer = client.signer().context("Signer not found")?;
let public_key = user_signer.get_public_key().await?;
// Get messages with dekey
if let Some(signer) = device_signer.as_ref() {
if let Ok(pkey) = signer.get_public_key().await {
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(pkey);
let id = SubscriptionId::new(DEVICE_GIFTWRAP);
let device_pkey = signer.get_public_key().await?;
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(device_pkey);
let id = SubscriptionId::new(DEVICE_GIFTWRAP);
if let Err(e) = client
.subscribe_with_id_to(&urls, id, vec![filter], None)
.await
{
log::error!("Failed to subscribe to gift wrap events: {e}");
}
}
// 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);
if let Err(e) = client
.subscribe_with_id_to(urls, id, vec![filter], None)
.await
{
log::error!("Failed to subscribe to gift wrap events: {e}");
}
})
.detach();
// 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 device announcement for current user
@@ -306,11 +318,9 @@ impl DeviceRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let task: Task<Result<Event, Error>> = cx.background_spawn(async move {
let urls = write_relays.await;
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
// Construct the filter for the device announcement event
let filter = Filter::new()
@@ -319,7 +329,8 @@ impl DeviceRegistry {
.limit(1);
let mut stream = client
.stream_events_from(&urls, vec![filter], Duration::from_secs(TIMEOUT))
.stream_events(filter)
.timeout(Duration::from_secs(TIMEOUT))
.await?;
while let Some((_url, res)) = stream.next().await {
@@ -360,28 +371,21 @@ impl DeviceRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
// Generate a new device keys
let keys = Keys::generate();
let secret = keys.secret_key().to_secret_hex();
let n = keys.public_key();
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let signer = client.signer().await?;
let urls = write_relays.await;
// Construct an announcement event
let event = EventBuilder::new(Kind::Custom(10044), "")
.tags(vec![
Tag::custom(TagKind::custom("n"), vec![n]),
Tag::client(app_name()),
])
.sign(&signer)
.await?;
let builder = EventBuilder::new(Kind::Custom(10044), "").tags(vec![
Tag::custom(TagKind::custom("n"), vec![n]),
Tag::client(app_name()),
]);
let event = client.sign_event_builder(builder).await?;
// Publish announcement
client.send_event_to(&urls, &event).await?;
client.send_event(&event).to_nip65().await?;
// Save device keys to the database
Self::set_keys(&client, &secret).await?;
@@ -449,11 +453,9 @@ impl DeviceRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let urls = write_relays.await;
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
// Construct a filter for device key requests
let filter = Filter::new()
@@ -462,7 +464,7 @@ impl DeviceRegistry {
.since(Timestamp::now());
// Subscribe to the device key requests on user's write relays
client.subscribe_to(&urls, vec![filter], None).await?;
client.subscribe(filter).await?;
Ok(())
});
@@ -475,11 +477,9 @@ impl DeviceRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let urls = write_relays.await;
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
// Construct a filter for device key requests
let filter = Filter::new()
@@ -488,7 +488,7 @@ impl DeviceRegistry {
.since(Timestamp::now());
// Subscribe to the device key requests on user's write relays
client.subscribe_to(&urls, vec![filter], None).await?;
client.subscribe(filter).await?;
Ok(())
});
@@ -501,14 +501,11 @@ impl DeviceRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let app_keys = nostr.read(cx).app_keys().clone();
let app_pubkey = app_keys.public_key();
let task: Task<Result<Option<Keys>, Error>> = cx.background_spawn(async move {
let signer = client.signer().await?;
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
let filter = Filter::new()
@@ -535,19 +532,15 @@ impl DeviceRegistry {
Ok(Some(keys))
}
None => {
let urls = write_relays.await;
// Construct an event for device key request
let event = EventBuilder::new(Kind::Custom(4454), "")
.tags(vec![
Tag::client(app_name()),
Tag::custom(TagKind::custom("P"), vec![app_pubkey]),
])
.sign(&signer)
.await?;
let builder = EventBuilder::new(Kind::Custom(4454), "").tags(vec![
Tag::client(app_name()),
Tag::custom(TagKind::custom("P"), vec![app_pubkey]),
]);
let event = client.sign_event_builder(builder).await?;
// Send the event to write relays
client.send_event_to(&urls, &event).await?;
client.send_event(&event).to_nip65().await?;
Ok(None)
}
@@ -620,12 +613,8 @@ impl DeviceRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let public_key = nostr.read(cx).identity().read(cx).public_key();
let write_relays = nostr.read(cx).write_relays(&public_key, cx);
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let urls = write_relays.await;
let signer = client.signer().await?;
let signer = client.signer().context("Signer not found")?;
// Get device keys
let keys = Self::get_keys(&client).await?;
@@ -646,16 +635,14 @@ impl DeviceRegistry {
//
// P tag: the current device's public key
// p tag: the requester's public key
let event = EventBuilder::new(Kind::Custom(4455), payload)
.tags(vec![
Tag::custom(TagKind::custom("P"), vec![keys.public_key()]),
Tag::public_key(target),
])
.sign(&signer)
.await?;
let builder = EventBuilder::new(Kind::Custom(4455), payload).tags(vec![
Tag::custom(TagKind::custom("P"), vec![keys.public_key()]),
Tag::public_key(target),
]);
let event = client.sign_event_builder(builder).await?;
// Send the response event to the user's relay list
client.send_event_to(&urls, &event).await?;
client.send_event(&event).to_nip65().await?;
Ok(())
});