wip
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m18s
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m18s
This commit is contained in:
@@ -7,8 +7,6 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{anyhow, Context as AnyhowContext, Error};
|
use anyhow::{anyhow, Context as AnyhowContext, Error};
|
||||||
use common::EventUtils;
|
use common::EventUtils;
|
||||||
use device::DeviceRegistry;
|
|
||||||
use flume::Sender;
|
|
||||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||||
use fuzzy_matcher::FuzzyMatcher;
|
use fuzzy_matcher::FuzzyMatcher;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -45,11 +43,9 @@ pub enum ChatEvent {
|
|||||||
|
|
||||||
/// Channel signal.
|
/// Channel signal.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
enum NostrEvent {
|
enum Signal {
|
||||||
/// Message received from relay pool
|
/// Message received from relay pool
|
||||||
Message(NewMessage),
|
Message(NewMessage),
|
||||||
/// Unwrapping status
|
|
||||||
Unwrapping(bool),
|
|
||||||
/// Eose received from relay pool
|
/// Eose received from relay pool
|
||||||
Eose,
|
Eose,
|
||||||
}
|
}
|
||||||
@@ -60,23 +56,14 @@ pub struct ChatRegistry {
|
|||||||
/// Collection of all chat rooms
|
/// Collection of all chat rooms
|
||||||
rooms: Vec<Entity<Room>>,
|
rooms: Vec<Entity<Room>>,
|
||||||
|
|
||||||
/// Loading status of the registry
|
|
||||||
loading: bool,
|
|
||||||
|
|
||||||
/// Channel's sender for communication between nostr and gpui
|
|
||||||
sender: Sender<NostrEvent>,
|
|
||||||
|
|
||||||
/// Tracking the status of unwrapping gift wrap events.
|
/// Tracking the status of unwrapping gift wrap events.
|
||||||
tracking_flag: Arc<AtomicBool>,
|
tracking_flag: Arc<AtomicBool>,
|
||||||
|
|
||||||
/// Handle tracking asynchronous task
|
/// Handle tracking asynchronous task
|
||||||
tracking: Option<Task<Result<(), Error>>>,
|
tracking_task: Option<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
/// Handle notifications asynchronous task
|
/// Handle notification asynchronous task
|
||||||
notifications: Option<Task<()>>,
|
notification_task: Option<Task<()>>,
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
|
||||||
tasks: Vec<Task<()>>,
|
|
||||||
|
|
||||||
/// Subscriptions
|
/// Subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
@@ -97,79 +84,30 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Create a new chat registry instance
|
/// Create a new chat registry instance
|
||||||
fn new(cx: &mut Context<Self>) -> Self {
|
fn new(cx: &mut Context<Self>) -> Self {
|
||||||
let device = DeviceRegistry::global(cx);
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let nip17_state = nostr.read(cx).nip17_state();
|
let nip17 = nostr.read(cx).nip17_state();
|
||||||
|
|
||||||
// A flag to indicate if the registry is loading
|
|
||||||
let tracking_flag = Arc::new(AtomicBool::new(false));
|
|
||||||
|
|
||||||
// Channel for communication between nostr and gpui
|
|
||||||
let (tx, rx) = flume::bounded::<NostrEvent>(2048);
|
|
||||||
|
|
||||||
let mut tasks = vec![];
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Observe the identity
|
// Observe the identity
|
||||||
cx.observe(&nip17_state, |this, state, cx| {
|
cx.observe(&nip17, |this, state, cx| {
|
||||||
if state.read(cx) == &RelayState::Configured {
|
if state.read(cx) == &RelayState::Configured {
|
||||||
// Handle nostr notifications
|
// Handle nostr notifications
|
||||||
this.handle_notifications(cx);
|
this.handle_notifications(cx);
|
||||||
// Track unwrapping progress
|
// Track unwrapping progress
|
||||||
this.tracking(cx);
|
this.tracking(cx);
|
||||||
}
|
}
|
||||||
// Get chat rooms from the database on every identity change
|
// Get chat rooms from the database on every state changes
|
||||||
this.get_rooms(cx);
|
this.get_rooms(cx);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
subscriptions.push(
|
|
||||||
// Observe the device signer state
|
|
||||||
cx.observe(&device, |this, state, cx| {
|
|
||||||
if state.read(cx).state().set() {
|
|
||||||
this.handle_notifications(cx);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
// Update GPUI states
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
while let Ok(message) = rx.recv_async().await {
|
|
||||||
match message {
|
|
||||||
NostrEvent::Message(message) => {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.new_message(message, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
NostrEvent::Unwrapping(status) => {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.set_loading(status, cx);
|
|
||||||
this.get_rooms(cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
NostrEvent::Eose => {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.get_rooms(cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
rooms: vec![],
|
rooms: vec![],
|
||||||
loading: false,
|
tracking_flag: Arc::new(AtomicBool::new(false)),
|
||||||
sender: tx.clone(),
|
tracking_task: None,
|
||||||
tracking_flag,
|
notification_task: None,
|
||||||
tracking: None,
|
|
||||||
notifications: None,
|
|
||||||
tasks,
|
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,16 +117,17 @@ impl ChatRegistry {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let signer = nostr.read(cx).signer();
|
let signer = nostr.read(cx).signer();
|
||||||
|
|
||||||
let status = self.tracking_flag.clone();
|
let status = self.tracking_flag.clone();
|
||||||
let tx = self.sender.clone();
|
|
||||||
|
|
||||||
self.notifications = Some(cx.background_spawn(async move {
|
|
||||||
let initialized_at = Timestamp::now();
|
let initialized_at = Timestamp::now();
|
||||||
let sub_id1 = SubscriptionId::new(DEVICE_GIFTWRAP);
|
let sub_id1 = SubscriptionId::new(DEVICE_GIFTWRAP);
|
||||||
let sub_id2 = SubscriptionId::new(USER_GIFTWRAP);
|
let sub_id2 = SubscriptionId::new(USER_GIFTWRAP);
|
||||||
let device_signer = signer.get_encryption_signer().await;
|
|
||||||
|
|
||||||
|
// Channel for communication between nostr and gpui
|
||||||
|
let (tx, rx) = flume::bounded::<Signal>(1024);
|
||||||
|
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let device_signer = signer.get_encryption_signer().await;
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
|
||||||
@@ -223,7 +162,7 @@ impl ChatRegistry {
|
|||||||
// the event is already emitted
|
// the event is already emitted
|
||||||
if !sent_by_coop {
|
if !sent_by_coop {
|
||||||
let new_message = NewMessage::new(event.id, rumor);
|
let new_message = NewMessage::new(event.id, rumor);
|
||||||
let signal = NostrEvent::Message(new_message);
|
let signal = Signal::Message(new_message);
|
||||||
|
|
||||||
tx.send_async(signal).await.ok();
|
tx.send_async(signal).await.ok();
|
||||||
}
|
}
|
||||||
@@ -239,29 +178,45 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
RelayMessage::EndOfStoredEvents(id) => {
|
RelayMessage::EndOfStoredEvents(id) => {
|
||||||
if id.as_ref() == &sub_id1 || id.as_ref() == &sub_id2 {
|
if id.as_ref() == &sub_id1 || id.as_ref() == &sub_id2 {
|
||||||
tx.send_async(NostrEvent::Eose).await.ok();
|
tx.send_async(Signal::Eose).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
self.notification_task = Some(cx.spawn(async move |this, cx| {
|
||||||
|
while let Ok(message) = rx.recv_async().await {
|
||||||
|
match message {
|
||||||
|
Signal::Message(message) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.new_message(message, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Signal::Eose => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.get_rooms(cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tracking the status of unwrapping gift wrap events.
|
/// Tracking the status of unwrapping gift wrap events.
|
||||||
fn tracking(&mut self, cx: &mut Context<Self>) {
|
fn tracking(&mut self, cx: &mut Context<Self>) {
|
||||||
let status = self.tracking_flag.clone();
|
let status = self.tracking_flag.clone();
|
||||||
let tx = self.sender.clone();
|
|
||||||
|
|
||||||
self.tracking = Some(cx.background_spawn(async move {
|
self.tracking_task = Some(cx.background_spawn(async move {
|
||||||
let loop_duration = Duration::from_secs(12);
|
let loop_duration = Duration::from_secs(10);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if status.load(Ordering::Acquire) {
|
if status.load(Ordering::Acquire) {
|
||||||
_ = status.compare_exchange(true, false, Ordering::Release, Ordering::Relaxed);
|
_ = 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;
|
smol::Timer::after(loop_duration).await;
|
||||||
}
|
}
|
||||||
@@ -270,13 +225,7 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Get the loading status of the chat registry
|
/// Get the loading status of the chat registry
|
||||||
pub fn loading(&self) -> bool {
|
pub fn loading(&self) -> bool {
|
||||||
self.loading
|
self.tracking_flag.load(Ordering::Acquire)
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the loading status of the chat registry
|
|
||||||
pub fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) {
|
|
||||||
self.loading = loading;
|
|
||||||
cx.notify();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a weak reference to a room by its ID.
|
/// Get a weak reference to a room by its ID.
|
||||||
@@ -312,19 +261,19 @@ impl ChatRegistry {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
if let Some(signer) = client.signer() {
|
let signer = client.signer()?;
|
||||||
if let Ok(public_key) = signer.get_public_key().await {
|
let public_key = signer.get_public_key().await.ok()?;
|
||||||
|
let room: Room = room.into().organize(&public_key);
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.rooms
|
this.rooms.insert(0, cx.new(|_| room));
|
||||||
.insert(0, cx.new(|_| room.into().organize(&public_key)));
|
|
||||||
cx.emit(ChatEvent::Ping);
|
cx.emit(ChatEvent::Ping);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok()
|
||||||
}
|
})
|
||||||
}
|
.detach();
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit an open room event.
|
/// Emit an open room event.
|
||||||
@@ -417,20 +366,16 @@ impl ChatRegistry {
|
|||||||
pub fn get_rooms(&mut self, cx: &mut Context<Self>) {
|
pub fn get_rooms(&mut self, cx: &mut Context<Self>) {
|
||||||
let task = self.get_rooms_from_database(cx);
|
let task = self.get_rooms_from_database(cx);
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
match task.await {
|
let rooms = task.await.ok()?;
|
||||||
Ok(rooms) => {
|
|
||||||
this.update(cx, move |this, cx| {
|
this.update(cx, move |this, cx| {
|
||||||
this.extend_rooms(rooms, cx);
|
this.extend_rooms(rooms, cx);
|
||||||
this.sort(cx);
|
this.sort(cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok()
|
||||||
}
|
})
|
||||||
Err(e) => {
|
.detach();
|
||||||
log::error!("Failed to load rooms: {e}")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a task to load rooms from the database
|
/// Create a task to load rooms from the database
|
||||||
|
|||||||
@@ -314,12 +314,21 @@ impl Room {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Construct a filter for gossip relays
|
// Construct a filter for messaging relays
|
||||||
let filter = Filter::new().kind(Kind::RelayList).author(member).limit(1);
|
let inbox = Filter::new()
|
||||||
|
.kind(Kind::InboxRelays)
|
||||||
|
.author(member)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
// Construct a filter for announcement
|
||||||
|
let announcement = Filter::new()
|
||||||
|
.kind(Kind::Custom(10044))
|
||||||
|
.author(member)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
// Subscribe to get member's gossip relays
|
// Subscribe to get member's gossip relays
|
||||||
client
|
client
|
||||||
.subscribe(filter)
|
.subscribe(vec![inbox, announcement])
|
||||||
.with_id(subscription_id.clone())
|
.with_id(subscription_id.clone())
|
||||||
.close_on(
|
.close_on(
|
||||||
SubscribeAutoCloseOptions::default()
|
SubscribeAutoCloseOptions::default()
|
||||||
@@ -357,62 +366,22 @@ impl Room {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct extra tags for a message
|
// Construct a rumor event for direct message
|
||||||
fn extra_tags(&self, sender: PublicKey, members: &[Person], replies: &[EventId]) -> Vec<Tag> {
|
pub fn rumor<S, I>(&self, content: S, replies: I, cx: &App) -> Option<UnsignedEvent>
|
||||||
let mut extra_tags = vec![];
|
|
||||||
|
|
||||||
// Add subject tag if present
|
|
||||||
if let Some(value) = self.subject.as_ref() {
|
|
||||||
extra_tags.push(Tag::from_standardized_without_cell(TagStandard::Subject(
|
|
||||||
value.to_string(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all reply tags
|
|
||||||
for id in replies {
|
|
||||||
extra_tags.push(Tag::event(*id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all receiver tags
|
|
||||||
for member in members.iter() {
|
|
||||||
// Skip current user
|
|
||||||
if member.public_key() == sender {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
extra_tags.push(Tag::from_standardized_without_cell(
|
|
||||||
TagStandard::PublicKey {
|
|
||||||
public_key: member.public_key(),
|
|
||||||
relay_url: member.messaging_relay_hint(),
|
|
||||||
alias: None,
|
|
||||||
uppercase: false,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
extra_tags
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send<S, I>(&self, content: S, replies: I, cx: &App) -> Option<Task<Vec<SendReport>>>
|
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
I: IntoIterator<Item = EventId>,
|
I: IntoIterator<Item = EventId>,
|
||||||
{
|
{
|
||||||
let persons = PersonRegistry::global(cx);
|
let kind = Kind::PrivateDirectMessage;
|
||||||
let nostr = NostrRegistry::global(cx);
|
|
||||||
|
|
||||||
let client = nostr.read(cx).client();
|
|
||||||
let signer = nostr.read(cx).signer();
|
|
||||||
|
|
||||||
let content: String = content.into();
|
let content: String = content.into();
|
||||||
let replies: Vec<EventId> = replies.into_iter().collect();
|
let replies: Vec<EventId> = replies.into_iter().collect();
|
||||||
|
|
||||||
|
let persons = PersonRegistry::global(cx);
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
|
||||||
// Get current user's public key
|
// Get current user's public key
|
||||||
let sender = nostr.read(cx).signer().public_key()?;
|
let sender = nostr.read(cx).signer().public_key()?;
|
||||||
|
|
||||||
// get room's config
|
|
||||||
let config = self.config.clone();
|
|
||||||
|
|
||||||
// Get all members
|
// Get all members
|
||||||
let members: Vec<Person> = self
|
let members: Vec<Person> = self
|
||||||
.members
|
.members
|
||||||
@@ -421,40 +390,107 @@ impl Room {
|
|||||||
.map(|member| persons.read(cx).get(member, cx))
|
.map(|member| persons.read(cx).get(member, cx))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Get extra tags
|
// Construct event's tags
|
||||||
let extra_tags = self.extra_tags(sender, &members, &replies);
|
let mut tags = vec![];
|
||||||
|
|
||||||
|
// Add subject tag if present
|
||||||
|
if let Some(value) = self.subject.as_ref() {
|
||||||
|
tags.push(Tag::from_standardized_without_cell(TagStandard::Subject(
|
||||||
|
value.to_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all reply tags
|
||||||
|
for id in replies.into_iter() {
|
||||||
|
tags.push(Tag::event(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all receiver tags
|
||||||
|
for member in members.into_iter() {
|
||||||
|
// Skip current user
|
||||||
|
if member.public_key() == sender {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.push(Tag::from_standardized_without_cell(
|
||||||
|
TagStandard::PublicKey {
|
||||||
|
public_key: member.public_key(),
|
||||||
|
relay_url: member.messaging_relay_hint(),
|
||||||
|
alias: None,
|
||||||
|
uppercase: false,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a direct message rumor event
|
||||||
|
// WARNING: never sign and send this event to relays
|
||||||
|
let mut event = EventBuilder::new(kind, content).tags(tags).build(sender);
|
||||||
|
|
||||||
|
// Ensure that the ID is set
|
||||||
|
event.ensure_id();
|
||||||
|
|
||||||
|
Some(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send rumor event to all members's messaging relays
|
||||||
|
pub fn send(&self, rumor: UnsignedEvent, cx: &App) -> Option<Task<Vec<SendReport>>> {
|
||||||
|
let persons = PersonRegistry::global(cx);
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let client = nostr.read(cx).client();
|
||||||
|
let signer = nostr.read(cx).signer();
|
||||||
|
|
||||||
|
// Get room's config
|
||||||
|
let config = self.config.clone();
|
||||||
|
|
||||||
|
// Get current user's public key
|
||||||
|
let sender = nostr.read(cx).signer().public_key()?;
|
||||||
|
|
||||||
|
// Get all members (excluding sender)
|
||||||
|
let members: Vec<Person> = self
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.filter(|public_key| public_key != &&sender)
|
||||||
|
.map(|member| persons.read(cx).get(member, cx))
|
||||||
|
.collect();
|
||||||
|
|
||||||
Some(cx.background_spawn(async move {
|
Some(cx.background_spawn(async move {
|
||||||
let signer_kind = config.signer_kind();
|
let signer_kind = config.signer_kind();
|
||||||
let backup = config.backup();
|
|
||||||
|
|
||||||
// Get all available signers
|
|
||||||
let user_signer = signer.get().await;
|
let user_signer = signer.get().await;
|
||||||
let encryption_signer = signer.get_encryption_signer().await;
|
let encryption_signer = signer.get_encryption_signer().await;
|
||||||
|
|
||||||
let mut reports: Vec<SendReport> = vec![];
|
let mut reports = Vec::new();
|
||||||
|
|
||||||
|
for member in members {
|
||||||
|
let relays = member.messaging_relays();
|
||||||
|
let announcement = member.announcement();
|
||||||
|
|
||||||
for member in members.into_iter() {
|
|
||||||
// Skip if member has no messaging relays
|
// Skip if member has no messaging relays
|
||||||
if member.messaging_relays().is_empty() {
|
if relays.is_empty() {
|
||||||
let report = SendReport::new(member.public_key()).error("No messaging relays");
|
reports.push(SendReport::new(member.public_key()).error("No messaging relays"));
|
||||||
reports.push(report);
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the room is forced to use an encryption signer,
|
// Ensure relay connections
|
||||||
// skip if the receiver has not set up an encryption signer.
|
for url in relays.iter() {
|
||||||
if signer_kind.encryption() && member.announcement().is_none() {
|
client
|
||||||
let report = SendReport::new(member.public_key()).error("Encryption not found");
|
.add_relay(url)
|
||||||
reports.push(report);
|
.and_connect()
|
||||||
|
.capabilities(RelayCapabilities::GOSSIP)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// When forced to use encryption signer, skip if receiver has no announcement
|
||||||
|
if signer_kind.encryption() && announcement.is_none() {
|
||||||
|
reports
|
||||||
|
.push(SendReport::new(member.public_key()).error("Encryption not found"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (receiver, signer) = match signer_kind {
|
// Determine receiver and signer based on signer kind
|
||||||
|
let (receiver, signer_to_use) = match signer_kind {
|
||||||
SignerKind::Auto => {
|
SignerKind::Auto => {
|
||||||
if let Some(announcement) = member.announcement() {
|
if let Some(announcement) = announcement {
|
||||||
if let Some(enc_signer) = encryption_signer.as_ref() {
|
if let Some(enc_signer) = encryption_signer.as_ref() {
|
||||||
(announcement.public_key(), enc_signer.clone())
|
(announcement.public_key(), enc_signer.clone())
|
||||||
} else {
|
} else {
|
||||||
@@ -466,21 +502,48 @@ impl Room {
|
|||||||
}
|
}
|
||||||
SignerKind::Encryption => {
|
SignerKind::Encryption => {
|
||||||
let Some(encryption_signer) = encryption_signer.as_ref() else {
|
let Some(encryption_signer) = encryption_signer.as_ref() else {
|
||||||
let report =
|
reports.push(
|
||||||
SendReport::new(member.public_key()).error("Encryption not found");
|
SendReport::new(member.public_key()).error("Encryption not found"),
|
||||||
reports.push(report);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(announcement) = member.announcement() else {
|
let Some(announcement) = announcement else {
|
||||||
let report = SendReport::new(member.public_key())
|
reports.push(
|
||||||
.error("Announcement not found");
|
SendReport::new(member.public_key())
|
||||||
reports.push(report);
|
.error("Announcement not found"),
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
(announcement.public_key(), encryption_signer.clone())
|
(announcement.public_key(), encryption_signer.clone())
|
||||||
}
|
}
|
||||||
SignerKind::User => (member.public_key(), user_signer.clone()),
|
SignerKind::User => (member.public_key(), user_signer.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create and send gift-wrapped event
|
||||||
|
match EventBuilder::gift_wrap(&signer_to_use, &receiver, rumor.clone(), vec![])
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(event) => {
|
||||||
|
match client
|
||||||
|
.send_event(&event)
|
||||||
|
.to(relays)
|
||||||
|
.ack_policy(AckPolicy::none())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(output) => {
|
||||||
|
reports.push(SendReport::new(member.public_key()).output(output));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
reports.push(
|
||||||
|
SendReport::new(member.public_key()).error(e.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
reports.push(SendReport::new(member.public_key()).error(e.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reports
|
reports
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use anyhow::Error;
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
use chat::{Message, RenderedMessage, Room, RoomEvent, RoomKind, SendReport};
|
use chat::{Message, RenderedMessage, Room, RoomEvent, SendReport};
|
||||||
use common::{nip96_upload, RenderedTimestamp};
|
use common::{nip96_upload, RenderedTimestamp};
|
||||||
use dock::panel::{Panel, PanelEvent};
|
use dock::panel::{Panel, PanelEvent};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
@@ -60,7 +60,7 @@ pub struct ChatPanel {
|
|||||||
/// Mapping message ids to their rendered texts
|
/// Mapping message ids to their rendered texts
|
||||||
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
|
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
|
||||||
|
|
||||||
/// Mapping message ids to their reports
|
/// Mapping message (rumor event) ids to their reports
|
||||||
reports_by_id: BTreeMap<EventId, Vec<SendReport>>,
|
reports_by_id: BTreeMap<EventId, Vec<SendReport>>,
|
||||||
|
|
||||||
/// Input state
|
/// Input state
|
||||||
@@ -124,6 +124,7 @@ impl ChatPanel {
|
|||||||
// Define all functions that will run after the current cycle
|
// Define all functions that will run after the current cycle
|
||||||
cx.defer_in(window, |this, window, cx| {
|
cx.defer_in(window, |this, window, cx| {
|
||||||
this.subscribe_room_events(window, cx);
|
this.subscribe_room_events(window, cx);
|
||||||
|
this.connect(window, cx);
|
||||||
this.get_messages(window, cx);
|
this.get_messages(window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,6 +165,15 @@ impl ChatPanel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all necessary data for each member
|
||||||
|
fn connect(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let Ok(connect) = self.room.read_with(cx, |this, cx| this.early_connect(cx)) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tasks.push(cx.background_spawn(connect));
|
||||||
|
}
|
||||||
|
|
||||||
/// Load all messages belonging to this room
|
/// Load all messages belonging to this room
|
||||||
fn get_messages(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
fn get_messages(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let Ok(get_messages) = self.room.read_with(cx, |this, cx| this.get_messages(cx)) else {
|
let Ok(get_messages) = self.room.read_with(cx, |this, cx| this.get_messages(cx)) else {
|
||||||
@@ -182,7 +192,7 @@ impl ChatPanel {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get user input content and merged all attachments
|
/// Get user input content and merged all attachments if available
|
||||||
fn get_input_value(&self, cx: &Context<Self>) -> String {
|
fn get_input_value(&self, cx: &Context<Self>) -> String {
|
||||||
// Get input's value
|
// Get input's value
|
||||||
let mut content = self.input.read(cx).value().trim().to_string();
|
let mut content = self.input.read(cx).value().trim().to_string();
|
||||||
@@ -222,7 +232,52 @@ impl ChatPanel {
|
|||||||
|
|
||||||
/// Send a message to all members of the chat
|
/// Send a message to all members of the chat
|
||||||
fn send_message(&mut self, value: &str, window: &mut Window, cx: &mut Context<Self>) {
|
fn send_message(&mut self, value: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// TODO
|
if value.trim().is_empty() {
|
||||||
|
window.push_notification("Cannot send an empty message", cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get room entity
|
||||||
|
let room = self.room.clone();
|
||||||
|
|
||||||
|
let replies: Vec<EventId> = self.replies_to.read(cx).iter().copied().collect();
|
||||||
|
let content = value.to_string();
|
||||||
|
|
||||||
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let room = room.upgrade().context("Room is not available")?;
|
||||||
|
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
match room.read(cx).rumor(content, replies, cx) {
|
||||||
|
Some(rumor) => {
|
||||||
|
this.insert_message(&rumor, true, cx);
|
||||||
|
this.send_and_wait(rumor, window, cx);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
window.push_notification("Failed to create message", cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_and_wait(&mut self, rumor: UnsignedEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let Some(room) = self.room.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(task) = room.read(cx).send(rumor, cx) else {
|
||||||
|
window.push_notification("Failed to send message", cx);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let outputs = task.await;
|
||||||
|
log::info!("Message sent successfully: {outputs:?}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_reports(&mut self, id: EventId, reports: Vec<SendReport>, cx: &mut Context<Self>) {
|
fn insert_reports(&mut self, id: EventId, reports: Vec<SendReport>, cx: &mut Context<Self>) {
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ fn main() {
|
|||||||
// Initialize theme registry
|
// Initialize theme registry
|
||||||
theme::init(cx);
|
theme::init(cx);
|
||||||
|
|
||||||
|
// Initialize settings
|
||||||
|
settings::init(cx);
|
||||||
|
|
||||||
// Initialize the nostr client
|
// Initialize the nostr client
|
||||||
state::init(cx);
|
state::init(cx);
|
||||||
|
|
||||||
@@ -86,9 +89,6 @@ fn main() {
|
|||||||
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||||
device::init(window, cx);
|
device::init(window, cx);
|
||||||
|
|
||||||
// Initialize settings
|
|
||||||
settings::init(cx);
|
|
||||||
|
|
||||||
// Initialize relay auth registry
|
// Initialize relay auth registry
|
||||||
relay_auth::init(window, cx);
|
relay_auth::init(window, cx);
|
||||||
|
|
||||||
|
|||||||
@@ -206,17 +206,6 @@ impl Sidebar {
|
|||||||
|
|
||||||
/// Search
|
/// Search
|
||||||
fn search(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn search(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// Return if a search is already in progress
|
|
||||||
if self.finding {
|
|
||||||
if self.find_task.is_none() {
|
|
||||||
window.push_notification("There is another search in progress", cx);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// Cancel the ongoing search request
|
|
||||||
self.find_task = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get query
|
// Get query
|
||||||
let query = self.find_input.read(cx).value();
|
let query = self.find_input.read(cx).value();
|
||||||
|
|
||||||
@@ -228,12 +217,14 @@ impl Sidebar {
|
|||||||
// Block the input until the search completes
|
// Block the input until the search completes
|
||||||
self.set_finding(true, window, cx);
|
self.set_finding(true, window, cx);
|
||||||
|
|
||||||
|
// Create the search task
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let find_users = nostr.read(cx).search(&query, cx);
|
let find_users = nostr.read(cx).search(&query, cx);
|
||||||
|
|
||||||
// Run task in the main thread
|
// Run task in the main thread
|
||||||
self.find_task = Some(cx.spawn_in(window, async move |this, cx| {
|
self.find_task = Some(cx.spawn_in(window, async move |this, cx| {
|
||||||
let rooms = find_users.await?;
|
let rooms = find_users.await?;
|
||||||
|
|
||||||
// Update the UI with the search results
|
// Update the UI with the search results
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.set_results(rooms, cx);
|
this.set_results(rooms, cx);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub const USER_GIFTWRAP: &str = "user-gift-wraps";
|
|||||||
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
|
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
|
||||||
|
|
||||||
/// Default search relays
|
/// Default search relays
|
||||||
pub const SEARCH_RELAYS: [&str; 1] = ["wss://antiprimal.net"];
|
pub const SEARCH_RELAYS: [&str; 2] = ["wss://antiprimal.net", "wss://search.nos.today"];
|
||||||
|
|
||||||
/// Default bootstrap relays
|
/// Default bootstrap relays
|
||||||
pub const BOOTSTRAP_RELAYS: [&str; 3] = [
|
pub const BOOTSTRAP_RELAYS: [&str; 3] = [
|
||||||
|
|||||||
@@ -837,9 +837,30 @@ impl NostrRegistry {
|
|||||||
let client = self.client();
|
let client = self.client();
|
||||||
let query = query.to_string();
|
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 {
|
cx.background_spawn(async move {
|
||||||
let mut results: Vec<PublicKey> = Vec::with_capacity(FIND_LIMIT);
|
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
|
// Construct the filter for the search query
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.search(query.to_lowercase())
|
.search(query.to_lowercase())
|
||||||
|
|||||||
Reference in New Issue
Block a user