Redesign for the v1 stable release (#3)
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m26s
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m26s
Only half done. Will continue in another PR. Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
@@ -16,7 +16,7 @@ use gpui::{
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use state::{tracker, NostrRegistry, GIFTWRAP_SUBSCRIPTION};
|
||||
use state::{tracker, NostrRegistry, RelayState, DEVICE_GIFTWRAP, USER_GIFTWRAP};
|
||||
|
||||
mod message;
|
||||
mod room;
|
||||
@@ -63,14 +63,17 @@ pub struct ChatRegistry {
|
||||
/// Loading status of the registry
|
||||
loading: bool,
|
||||
|
||||
/// Tracking the status of unwrapping gift wrap events.
|
||||
tracking_flag: Arc<AtomicBool>,
|
||||
|
||||
/// Channel's sender for communication between nostr and gpui
|
||||
sender: Sender<NostrEvent>,
|
||||
|
||||
/// Tracking the status of unwrapping gift wrap events.
|
||||
tracking_flag: Arc<AtomicBool>,
|
||||
|
||||
/// Handle tracking asynchronous task
|
||||
tracking: Option<Task<Result<(), Error>>>,
|
||||
|
||||
/// Handle notifications asynchronous task
|
||||
notifications: Option<Task<Result<(), Error>>>,
|
||||
notifications: Option<Task<()>>,
|
||||
|
||||
/// Tasks for asynchronous operations
|
||||
tasks: Vec<Task<()>>,
|
||||
@@ -101,7 +104,7 @@ impl ChatRegistry {
|
||||
let device_signer = device.read(cx).device_signer.clone();
|
||||
|
||||
// A flag to indicate if the registry is loading
|
||||
let tracking_flag = Arc::new(AtomicBool::new(true));
|
||||
let tracking_flag = Arc::new(AtomicBool::new(false));
|
||||
|
||||
// Channel for communication between nostr and gpui
|
||||
let (tx, rx) = flume::bounded::<NostrEvent>(2048);
|
||||
@@ -112,12 +115,14 @@ impl ChatRegistry {
|
||||
subscriptions.push(
|
||||
// Observe the identity
|
||||
cx.observe(&identity, |this, state, cx| {
|
||||
if state.read(cx).has_public_key() {
|
||||
if state.read(cx).messaging_relays_state() == RelayState::Set {
|
||||
// Handle nostr notifications
|
||||
this.handle_notifications(cx);
|
||||
// Track unwrapping progress
|
||||
this.tracking(cx);
|
||||
}
|
||||
// Get chat rooms from the database on every identity change
|
||||
this.get_rooms(cx);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -161,9 +166,10 @@ impl ChatRegistry {
|
||||
|
||||
Self {
|
||||
rooms: vec![],
|
||||
loading: true,
|
||||
tracking_flag,
|
||||
loading: false,
|
||||
sender: tx.clone(),
|
||||
tracking_flag,
|
||||
tracking: None,
|
||||
notifications: None,
|
||||
tasks,
|
||||
_subscriptions: subscriptions,
|
||||
@@ -181,9 +187,10 @@ impl ChatRegistry {
|
||||
let status = self.tracking_flag.clone();
|
||||
let tx = self.sender.clone();
|
||||
|
||||
self.tasks.push(cx.background_spawn(async move {
|
||||
self.notifications = Some(cx.background_spawn(async move {
|
||||
let initialized_at = Timestamp::now();
|
||||
let subscription_id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
||||
let sub_id1 = SubscriptionId::new(DEVICE_GIFTWRAP);
|
||||
let sub_id2 = SubscriptionId::new(USER_GIFTWRAP);
|
||||
|
||||
let mut notifications = client.notifications();
|
||||
let mut processed_events = HashSet::new();
|
||||
@@ -229,12 +236,12 @@ impl ChatRegistry {
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to unwrap: {e}");
|
||||
log::warn!("Failed to unwrap the gift wrap event: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
RelayMessage::EndOfStoredEvents(id) => {
|
||||
if id.as_ref() == &subscription_id {
|
||||
if id.as_ref() == &sub_id1 || id.as_ref() == &sub_id2 {
|
||||
tx.send_async(NostrEvent::Eose).await.ok();
|
||||
}
|
||||
}
|
||||
@@ -246,44 +253,18 @@ impl ChatRegistry {
|
||||
|
||||
/// Tracking the status of unwrapping gift wrap events.
|
||||
fn tracking(&mut self, cx: &mut Context<Self>) {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
let status = self.tracking_flag.clone();
|
||||
let tx = self.sender.clone();
|
||||
|
||||
self.notifications = Some(cx.background_spawn(async move {
|
||||
self.tracking = Some(cx.background_spawn(async move {
|
||||
let loop_duration = Duration::from_secs(12);
|
||||
|
||||
let mut is_start_processing = false;
|
||||
let mut total_loops = 0;
|
||||
|
||||
loop {
|
||||
if client.has_signer().await {
|
||||
total_loops += 1;
|
||||
|
||||
if status.load(Ordering::Acquire) {
|
||||
is_start_processing = true;
|
||||
// Reset gift wrap processing flag
|
||||
_ = status.compare_exchange(
|
||||
true,
|
||||
false,
|
||||
Ordering::Release,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
|
||||
tx.send_async(NostrEvent::Unwrapping(true)).await.ok();
|
||||
} else {
|
||||
// Only run further if we are already processing
|
||||
// Wait until after 2 loops to prevent exiting early while events are still being processed
|
||||
if is_start_processing && total_loops >= 2 {
|
||||
tx.send_async(NostrEvent::Unwrapping(false)).await.ok();
|
||||
|
||||
// Reset the counter
|
||||
is_start_processing = false;
|
||||
total_loops = 0;
|
||||
}
|
||||
}
|
||||
if status.load(Ordering::Acquire) {
|
||||
_ = status.compare_exchange(true, false, Ordering::Release, Ordering::Relaxed);
|
||||
tx.send_async(NostrEvent::Unwrapping(true)).await.ok();
|
||||
} else {
|
||||
tx.send_async(NostrEvent::Unwrapping(false)).await.ok();
|
||||
}
|
||||
smol::Timer::after(loop_duration).await;
|
||||
}
|
||||
@@ -309,22 +290,21 @@ impl ChatRegistry {
|
||||
.map(|this| this.downgrade())
|
||||
}
|
||||
|
||||
/// Get all ongoing rooms.
|
||||
pub fn ongoing_rooms(&self, cx: &App) -> Vec<Entity<Room>> {
|
||||
/// Get all rooms based on the filter.
|
||||
pub fn rooms(&self, filter: &RoomKind, cx: &App) -> Vec<Entity<Room>> {
|
||||
self.rooms
|
||||
.iter()
|
||||
.filter(|room| room.read(cx).kind == RoomKind::Ongoing)
|
||||
.filter(|room| &room.read(cx).kind == filter)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get all request rooms.
|
||||
pub fn request_rooms(&self, cx: &App) -> Vec<Entity<Room>> {
|
||||
/// Count the number of rooms based on the filter.
|
||||
pub fn count(&self, filter: &RoomKind, cx: &App) -> usize {
|
||||
self.rooms
|
||||
.iter()
|
||||
.filter(|room| room.read(cx).kind != RoomKind::Ongoing)
|
||||
.cloned()
|
||||
.collect()
|
||||
.filter(|room| &room.read(cx).kind == filter)
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Add a new room to the start of list.
|
||||
@@ -337,6 +317,7 @@ impl ChatRegistry {
|
||||
}
|
||||
|
||||
/// Emit an open room event.
|
||||
///
|
||||
/// If the room is new, add it to the registry.
|
||||
pub fn emit_room(&mut self, room: WeakEntity<Room>, cx: &mut Context<Self>) {
|
||||
if let Some(room) = room.upgrade() {
|
||||
@@ -365,28 +346,27 @@ impl ChatRegistry {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Search rooms by their name.
|
||||
pub fn search(&self, query: &str, cx: &App) -> Vec<Entity<Room>> {
|
||||
/// Finding rooms based on a query.
|
||||
pub fn find(&self, query: &str, cx: &App) -> Vec<Entity<Room>> {
|
||||
let matcher = SkimMatcherV2::default();
|
||||
|
||||
self.rooms
|
||||
.iter()
|
||||
.filter(|room| {
|
||||
matcher
|
||||
.fuzzy_match(room.read(cx).display_name(cx).as_ref(), query)
|
||||
.is_some()
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Search rooms by public keys.
|
||||
pub fn search_by_public_key(&self, public_key: PublicKey, cx: &App) -> Vec<Entity<Room>> {
|
||||
self.rooms
|
||||
.iter()
|
||||
.filter(|room| room.read(cx).members.contains(&public_key))
|
||||
.cloned()
|
||||
.collect()
|
||||
if let Ok(public_key) = PublicKey::parse(query) {
|
||||
self.rooms
|
||||
.iter()
|
||||
.filter(|room| room.read(cx).members.contains(&public_key))
|
||||
.cloned()
|
||||
.collect()
|
||||
} else {
|
||||
self.rooms
|
||||
.iter()
|
||||
.filter(|room| {
|
||||
matcher
|
||||
.fuzzy_match(room.read(cx).display_name(cx).as_ref(), query)
|
||||
.is_some()
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the registry.
|
||||
@@ -532,7 +512,7 @@ impl ChatRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a Nostr event into a Coop Message and push it to the belonging room
|
||||
/// Parse a nostr event into a message and push it to the belonging room
|
||||
///
|
||||
/// If the room doesn't exist, it will be created.
|
||||
/// Updates room ordering based on the most recent messages.
|
||||
@@ -579,7 +559,7 @@ impl ChatRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
// Unwraps a gift-wrapped event and processes its contents.
|
||||
/// Unwraps a gift-wrapped event and processes its contents.
|
||||
async fn extract_rumor(
|
||||
client: &Client,
|
||||
device_signer: &Option<Arc<dyn NostrSigner>>,
|
||||
@@ -603,35 +583,50 @@ impl ChatRegistry {
|
||||
Ok(rumor_unsigned)
|
||||
}
|
||||
|
||||
// Helper method to try unwrapping with different signers
|
||||
/// Helper method to try unwrapping with different signers
|
||||
async fn try_unwrap(
|
||||
client: &Client,
|
||||
device_signer: &Option<Arc<dyn NostrSigner>>,
|
||||
gift_wrap: &Event,
|
||||
) -> Result<UnwrappedGift, Error> {
|
||||
if let Some(signer) = device_signer.as_ref() {
|
||||
let seal = signer
|
||||
.nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
|
||||
.await?;
|
||||
// Try with the device signer first
|
||||
if let Some(signer) = device_signer {
|
||||
if let Ok(unwrapped) = Self::try_unwrap_with(gift_wrap, signer).await {
|
||||
return Ok(unwrapped);
|
||||
};
|
||||
};
|
||||
|
||||
let seal: Event = Event::from_json(seal)?;
|
||||
seal.verify_with_ctx(&SECP256K1)?;
|
||||
|
||||
let rumor = signer.nip44_decrypt(&seal.pubkey, &seal.content).await?;
|
||||
let rumor = UnsignedEvent::from_json(rumor)?;
|
||||
|
||||
return Ok(UnwrappedGift {
|
||||
sender: seal.pubkey,
|
||||
rumor,
|
||||
});
|
||||
}
|
||||
|
||||
let signer = client.signer().await?;
|
||||
let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;
|
||||
// Try with the user's signer
|
||||
let user_signer = client.signer().await?;
|
||||
let unwrapped = UnwrappedGift::from_gift_wrap(&user_signer, gift_wrap).await?;
|
||||
|
||||
Ok(unwrapped)
|
||||
}
|
||||
|
||||
/// Attempts to unwrap a gift wrap event with a given signer.
|
||||
async fn try_unwrap_with(
|
||||
gift_wrap: &Event,
|
||||
signer: &Arc<dyn NostrSigner>,
|
||||
) -> Result<UnwrappedGift, Error> {
|
||||
// Get the sealed event
|
||||
let seal = signer
|
||||
.nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
|
||||
.await?;
|
||||
|
||||
// Verify the sealed event
|
||||
let seal: Event = Event::from_json(seal)?;
|
||||
seal.verify_with_ctx(&SECP256K1)?;
|
||||
|
||||
// Get the rumor event
|
||||
let rumor = signer.nip44_decrypt(&seal.pubkey, &seal.content).await?;
|
||||
let rumor = UnsignedEvent::from_json(rumor)?;
|
||||
|
||||
Ok(UnwrappedGift {
|
||||
sender: seal.pubkey,
|
||||
rumor,
|
||||
})
|
||||
}
|
||||
|
||||
/// Stores an unwrapped event in local database with reference to original
|
||||
async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Result<(), Error> {
|
||||
let rumor_id = rumor.id.context("Rumor is missing an event id")?;
|
||||
|
||||
@@ -167,22 +167,11 @@ impl From<&UnsignedEvent> for Room {
|
||||
|
||||
impl Room {
|
||||
/// Constructs a new room with the given receiver and tags.
|
||||
pub fn new(subject: Option<String>, author: PublicKey, receivers: Vec<PublicKey>) -> Self {
|
||||
// Convert receiver's public keys into tags
|
||||
let mut tags: Tags = Tags::from_list(
|
||||
receivers
|
||||
.iter()
|
||||
.map(|pubkey| Tag::public_key(pubkey.to_owned()))
|
||||
.collect(),
|
||||
);
|
||||
|
||||
// Add subject if it is present
|
||||
if let Some(subject) = subject {
|
||||
tags.push(Tag::from_standardized_without_cell(TagStandard::Subject(
|
||||
subject,
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn new<T>(author: PublicKey, receivers: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = PublicKey>,
|
||||
{
|
||||
let tags = Tags::from_list(receivers.into_iter().map(Tag::public_key).collect());
|
||||
let mut event = EventBuilder::new(Kind::PrivateDirectMessage, "")
|
||||
.tags(tags)
|
||||
.build(author);
|
||||
|
||||
Reference in New Issue
Block a user