.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1056,6 +1056,7 @@ dependencies = [
|
|||||||
"common",
|
"common",
|
||||||
"device",
|
"device",
|
||||||
"flume 0.11.1",
|
"flume 0.11.1",
|
||||||
|
"futures",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ smallvec.workspace = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
flume.workspace = true
|
flume.workspace = true
|
||||||
|
|
||||||
|
futures.workspace = true
|
||||||
fuzzy-matcher = "0.3.7"
|
fuzzy-matcher = "0.3.7"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
|||||||
@@ -82,15 +82,15 @@ pub struct ChatRegistry {
|
|||||||
rooms: Vec<Entity<Room>>,
|
rooms: Vec<Entity<Room>>,
|
||||||
|
|
||||||
/// Events that failed to unwrap for any reason
|
/// Events that failed to unwrap for any reason
|
||||||
trashes: Entity<BTreeSet<FailedMessage>>,
|
trash: Entity<BTreeSet<FailedMessage>>,
|
||||||
|
|
||||||
/// Tracking events seen on which relays in the current session
|
/// Tracking events seen on which relays in the current session
|
||||||
seens: Arc<RwLock<HashMap<EventId, HashSet<RelayUrl>>>>,
|
seen: Arc<RwLock<HashMap<EventId, HashSet<RelayUrl>>>>,
|
||||||
|
|
||||||
/// Mapping of unwrapped event ids to their gift wrap event ids
|
/// Mapping of unwrapped event ids to their gift wrap event ids
|
||||||
event_map: Arc<RwLock<HashMap<EventId, EventId>>>,
|
event_map: Arc<RwLock<HashMap<EventId, EventId>>>,
|
||||||
|
|
||||||
/// Tracking the status of unwrapping gift wrap events.
|
/// True while the initial event backlog is still loading
|
||||||
tracking: Arc<AtomicBool>,
|
tracking: Arc<AtomicBool>,
|
||||||
|
|
||||||
/// Channel for sending signals to the UI.
|
/// Channel for sending signals to the UI.
|
||||||
@@ -108,10 +108,31 @@ pub struct ChatRegistry {
|
|||||||
/// Signal consumer task (cancelled on signer change)
|
/// Signal consumer task (cancelled on signer change)
|
||||||
signal_consumer: Option<Task<Result<(), Error>>>,
|
signal_consumer: Option<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
|
/// Fuzzy matcher for room search (cached; intentionally excluded from Debug)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
matcher: CachedMatcher,
|
||||||
|
|
||||||
/// Subscriptions
|
/// Subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 2]>,
|
_subscriptions: SmallVec<[Subscription; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper to provide Debug for SkimMatcherV2
|
||||||
|
struct CachedMatcher(SkimMatcherV2);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for CachedMatcher {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("CachedMatcher { .. }")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for CachedMatcher {
|
||||||
|
type Target = SkimMatcherV2;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EventEmitter<ChatEvent> for ChatRegistry {}
|
impl EventEmitter<ChatEvent> for ChatRegistry {}
|
||||||
|
|
||||||
impl ChatRegistry {
|
impl ChatRegistry {
|
||||||
@@ -145,16 +166,16 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
// Run at the end of the current cycle
|
// Run at the end of the current cycle
|
||||||
cx.defer_in(window, |this, _window, cx| {
|
cx.defer_in(window, |this, _window, cx| {
|
||||||
this.tracking(cx);
|
|
||||||
this.get_rooms(cx);
|
this.get_rooms(cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
rooms: vec![],
|
rooms: vec![],
|
||||||
trashes: cx.new(|_| BTreeSet::default()),
|
trash: cx.new(|_| BTreeSet::default()),
|
||||||
seens: Arc::new(RwLock::new(HashMap::default())),
|
seen: Arc::new(RwLock::new(HashMap::default())),
|
||||||
event_map: Arc::new(RwLock::new(HashMap::default())),
|
event_map: Arc::new(RwLock::new(HashMap::default())),
|
||||||
tracking: Arc::new(AtomicBool::new(false)),
|
tracking: Arc::new(AtomicBool::new(true)),
|
||||||
|
matcher: CachedMatcher(SkimMatcherV2::default()),
|
||||||
signal_rx: rx,
|
signal_rx: rx,
|
||||||
signal_tx: tx,
|
signal_tx: tx,
|
||||||
tasks: smallvec![],
|
tasks: smallvec![],
|
||||||
@@ -174,12 +195,10 @@ impl ChatRegistry {
|
|||||||
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 tracking = self.tracking.clone();
|
let seen = self.seen.clone();
|
||||||
let seens = self.seens.clone();
|
|
||||||
let event_map = self.event_map.clone();
|
let event_map = self.event_map.clone();
|
||||||
let trashes = self.trashes.downgrade();
|
let trash = self.trash.downgrade();
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -190,16 +209,19 @@ impl ChatRegistry {
|
|||||||
self.notification_listener = Some(cx.background_spawn(async move {
|
self.notification_listener = Some(cx.background_spawn(async move {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events = HashSet::new();
|
let mut processed_events = HashSet::new();
|
||||||
|
const MAX_PROCESSED: usize = 10_000;
|
||||||
|
|
||||||
while let Some(notification) = notifications.next().await {
|
while let Some(notification) = notifications.next().await {
|
||||||
let ClientNotification::Message { message, relay_url } = notification else {
|
let ClientNotification::Message { message, relay_url } = notification else {
|
||||||
// Skip non-message notifications
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
match *message {
|
match *message {
|
||||||
RelayMessage::Event { event, .. } => {
|
RelayMessage::Event { event, .. } => {
|
||||||
// De-duplicate events by their ID
|
// Prune the dedup set before it grows unbounded
|
||||||
|
if processed_events.len() >= MAX_PROCESSED {
|
||||||
|
processed_events.clear();
|
||||||
|
}
|
||||||
if !processed_events.insert(event.id) {
|
if !processed_events.insert(event.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -207,7 +229,6 @@ impl ChatRegistry {
|
|||||||
// Handle msg relays event to determine when the app is ready to subscribe
|
// Handle msg relays event to determine when the app is ready to subscribe
|
||||||
if event.kind == Kind::InboxRelays {
|
if event.kind == Kind::InboxRelays {
|
||||||
let current_user = signer.get_public_key_async().await?;
|
let current_user = signer.get_public_key_async().await?;
|
||||||
// Emit the inbox ready signal
|
|
||||||
if event.pubkey == current_user {
|
if event.pubkey == current_user {
|
||||||
tx.send_async(Signal::InboxReady).await?;
|
tx.send_async(Signal::InboxReady).await?;
|
||||||
}
|
}
|
||||||
@@ -220,14 +241,13 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
// Keep track of which relays have seen this event
|
// Keep track of which relays have seen this event
|
||||||
{
|
{
|
||||||
let mut seens = seens.write().unwrap();
|
let mut seen = seen.write().unwrap();
|
||||||
seens.entry(event.id).or_default().insert(relay_url);
|
seen.entry(event.id).or_default().insert(relay_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the rumor from the gift wrap event
|
// Extract the rumor from the gift wrap event
|
||||||
match extract_rumor(&client, &signer, event.as_ref()).await {
|
match extract_rumor(&client, &signer, event.as_ref()).await {
|
||||||
Ok(rumor) => {
|
Ok(rumor) => {
|
||||||
// Map the rumor id to the gift wrap event id for later lookup
|
|
||||||
let Some(rumor_id) = rumor.id else {
|
let Some(rumor_id) = rumor.id else {
|
||||||
log::error!("Rumor missing id after ensure_id");
|
log::error!("Rumor missing id after ensure_id");
|
||||||
continue;
|
continue;
|
||||||
@@ -237,20 +257,14 @@ impl ChatRegistry {
|
|||||||
event_map.insert(rumor_id, event.id);
|
event_map.insert(rumor_id, event.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the rumor has a recipient
|
|
||||||
if rumor.tags.is_empty() {
|
if rumor.tags.is_empty() {
|
||||||
let signal = Signal::error(&event, "Recipient is missing");
|
let signal = Signal::error(&event, "Recipient is missing");
|
||||||
tx.send_async(signal).await?;
|
tx.send_async(signal).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the rumor was created after the chat was initialized (for detecting new messages)
|
// Emit message for both new and backlog events
|
||||||
if rumor.created_at >= initialized_at {
|
|
||||||
let signal = Signal::message(event.id, rumor);
|
let signal = Signal::message(event.id, rumor);
|
||||||
tx.send_async(signal).await?;
|
tx.send_async(signal).await?;
|
||||||
} else {
|
|
||||||
// Mark the chat still processing new messages
|
|
||||||
tracking.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let reason = format!("Failed to extract rumor: {e}");
|
let reason = format!("Failed to extract rumor: {e}");
|
||||||
@@ -286,12 +300,13 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
Signal::Eose => {
|
Signal::Eose => {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.get_rooms(cx);
|
this.tracking.store(false, Ordering::Release);
|
||||||
|
this.sort(cx);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Signal::Error(trash) => {
|
Signal::Error(failed) => {
|
||||||
trashes.update(cx, |this, cx| {
|
trash.update(cx, |this, cx| {
|
||||||
this.insert(trash);
|
this.insert(failed);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@@ -302,27 +317,6 @@ impl ChatRegistry {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check periodically whether old gift-wrap events have finished processing,
|
|
||||||
/// and refresh rooms once the backlog is caught up.
|
|
||||||
fn tracking(&mut self, cx: &mut Context<Self>) {
|
|
||||||
let status = self.tracking.clone();
|
|
||||||
let tx = self.signal_tx.clone();
|
|
||||||
let check_interval = Duration::from_secs(15);
|
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |_, cx| {
|
|
||||||
loop {
|
|
||||||
cx.background_executor().timer(check_interval).await;
|
|
||||||
|
|
||||||
// Only trigger a room refresh if old events were being tracked
|
|
||||||
// (i.e. the notification handler set the flag while catching up).
|
|
||||||
// `swap` atomically reads and clears the flag.
|
|
||||||
if status.swap(false, Ordering::AcqRel) {
|
|
||||||
_ = tx.send_async(Signal::Eose).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all necessary metadata from relays for current user
|
/// Get all necessary metadata from relays for current user
|
||||||
pub fn get_metadata(&mut self, cx: &mut Context<Self>) {
|
pub fn get_metadata(&mut self, cx: &mut Context<Self>) {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
@@ -332,12 +326,9 @@ impl ChatRegistry {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let subscribe = cx.background_spawn({
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
let client = client.clone();
|
// Subscribe to metadata from relays
|
||||||
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
async move {
|
|
||||||
let opts =
|
|
||||||
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
|
||||||
|
|
||||||
let msg_relays = Filter::new()
|
let msg_relays = Filter::new()
|
||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
@@ -349,20 +340,15 @@ impl ChatRegistry {
|
|||||||
.author(public_key)
|
.author(public_key)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
client
|
_ = client
|
||||||
.subscribe(vec![msg_relays, contact_list])
|
.subscribe(vec![msg_relays, contact_list])
|
||||||
.close_on(opts)
|
.close_on(opts)
|
||||||
.await
|
.await;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
|
||||||
// Wait for subscription to complete (or fail silently)
|
|
||||||
_ = subscribe.await;
|
|
||||||
|
|
||||||
// Give relays time to respond
|
// Give relays time to respond
|
||||||
cx.background_executor().timer(Duration::from_secs(5)).await;
|
cx.background_executor().timer(Duration::from_secs(5)).await;
|
||||||
|
|
||||||
|
// Verify inbox relays were received
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
@@ -436,8 +422,8 @@ impl ChatRegistry {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refresh the chat registry, fetching messages and contact list from relays.
|
/// Reload the chat registry, fetching messages and contact list from relays.
|
||||||
pub fn refresh(&mut self, cx: &mut Context<Self>) {
|
pub fn reload(&mut self, cx: &mut Context<Self>) {
|
||||||
self.reset(cx);
|
self.reset(cx);
|
||||||
self.get_metadata(cx);
|
self.get_metadata(cx);
|
||||||
self.get_rooms(cx);
|
self.get_rooms(cx);
|
||||||
@@ -475,22 +461,22 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Count the number of messages seen by a given relay.
|
/// Count the number of messages seen by a given relay.
|
||||||
pub fn count_messages(&self, relay_url: &RelayUrl) -> usize {
|
pub fn count_messages(&self, relay_url: &RelayUrl) -> usize {
|
||||||
self.seens
|
self.seen
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.values()
|
.values()
|
||||||
.filter(|seen| seen.contains(relay_url))
|
.filter(|s| s.contains(relay_url))
|
||||||
.count()
|
.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count the number of trash messages.
|
/// Count the number of trash messages.
|
||||||
pub fn count_trash_messages(&self, cx: &App) -> usize {
|
pub fn count_trash_messages(&self, cx: &App) -> usize {
|
||||||
self.trashes.read(cx).len()
|
self.trash.read(cx).len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the trash messages entity.
|
/// Get the trash messages entity.
|
||||||
pub fn trashes(&self) -> Entity<BTreeSet<FailedMessage>> {
|
pub fn trash(&self) -> Entity<BTreeSet<FailedMessage>> {
|
||||||
self.trashes.clone()
|
self.trash.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the relays that have seen a given rumor id.
|
/// Get the relays that have seen a given rumor id.
|
||||||
@@ -504,7 +490,7 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Get the relays that have seen a given gift wrap id.
|
/// Get the relays that have seen a given gift wrap id.
|
||||||
pub fn seen_on(&self, id: &EventId) -> HashSet<RelayUrl> {
|
pub fn seen_on(&self, id: &EventId) -> HashSet<RelayUrl> {
|
||||||
self.seens
|
self.seen
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get(id)
|
.get(id)
|
||||||
@@ -560,8 +546,6 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Finding rooms based on a query.
|
/// Finding rooms based on a query.
|
||||||
pub fn find(&self, query: &str, cx: &App) -> Vec<Entity<Room>> {
|
pub fn find(&self, query: &str, cx: &App) -> Vec<Entity<Room>> {
|
||||||
let matcher = SkimMatcherV2::default();
|
|
||||||
|
|
||||||
if let Ok(public_key) = PublicKey::parse(query) {
|
if let Ok(public_key) = PublicKey::parse(query) {
|
||||||
self.rooms
|
self.rooms
|
||||||
.iter()
|
.iter()
|
||||||
@@ -572,7 +556,7 @@ impl ChatRegistry {
|
|||||||
self.rooms
|
self.rooms
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|room| {
|
.filter(|room| {
|
||||||
matcher
|
self.matcher
|
||||||
.fuzzy_match(room.read(cx).display_name(cx).as_ref(), query)
|
.fuzzy_match(room.read(cx).display_name(cx).as_ref(), query)
|
||||||
.is_some()
|
.is_some()
|
||||||
})
|
})
|
||||||
@@ -584,7 +568,7 @@ impl ChatRegistry {
|
|||||||
/// Reset the registry.
|
/// Reset the registry.
|
||||||
pub fn reset(&mut self, cx: &mut Context<Self>) {
|
pub fn reset(&mut self, cx: &mut Context<Self>) {
|
||||||
self.rooms.clear();
|
self.rooms.clear();
|
||||||
self.trashes.update(cx, |this, cx| {
|
self.trash.update(cx, |this, cx| {
|
||||||
this.clear();
|
this.clear();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -363,7 +363,11 @@ impl Room {
|
|||||||
.exit_policy(ReqExitPolicy::ExitOnEOSE)
|
.exit_policy(ReqExitPolicy::ExitOnEOSE)
|
||||||
.timeout(Some(Duration::from_secs(TIMEOUT)));
|
.timeout(Some(Duration::from_secs(TIMEOUT)));
|
||||||
|
|
||||||
for public_key in members.into_iter() {
|
let tasks: Vec<_> = members
|
||||||
|
.into_iter()
|
||||||
|
.map(|public_key| {
|
||||||
|
let client = client.clone();
|
||||||
|
async move {
|
||||||
let inbox = Filter::new()
|
let inbox = Filter::new()
|
||||||
.author(public_key)
|
.author(public_key)
|
||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
@@ -374,11 +378,16 @@ impl Room {
|
|||||||
.kind(Kind::Custom(10044))
|
.kind(Kind::Custom(10044))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
// Subscribe to the target
|
|
||||||
client
|
client
|
||||||
.subscribe(vec![inbox, announcement])
|
.subscribe(vec![inbox, announcement])
|
||||||
.close_on(opts)
|
.close_on(opts)
|
||||||
.await?;
|
.await
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for result in futures::future::join_all(tasks).await {
|
||||||
|
result?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -429,14 +438,6 @@ impl Room {
|
|||||||
// Get current user's public key
|
// Get current user's public key
|
||||||
let sender = nostr.read(cx).current_user()?;
|
let sender = nostr.read(cx).current_user()?;
|
||||||
|
|
||||||
// Get all members, excluding the sender
|
|
||||||
let members: Vec<Person> = self
|
|
||||||
.members
|
|
||||||
.iter()
|
|
||||||
.filter(|public_key| public_key != &&sender)
|
|
||||||
.map(|member| persons.read(cx).get(member, cx))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Construct event's tags
|
// Construct event's tags
|
||||||
let mut tags = vec![];
|
let mut tags = vec![];
|
||||||
|
|
||||||
@@ -450,8 +451,9 @@ impl Room {
|
|||||||
tags.push(Tag::event(id))
|
tags.push(Tag::event(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all receiver tags
|
// Add all receiver tags (no intermediate allocation)
|
||||||
for member in members.into_iter() {
|
for public_key in self.members.iter().filter(|pk| *pk != &sender) {
|
||||||
|
let member = persons.read(cx).get(public_key, cx);
|
||||||
tags.push(
|
tags.push(
|
||||||
Nip01Tag::PublicKey {
|
Nip01Tag::PublicKey {
|
||||||
public_key: member.public_key(),
|
public_key: member.public_key(),
|
||||||
@@ -473,6 +475,30 @@ impl Room {
|
|||||||
Some(event)
|
Some(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Select the appropriate signer based on signer kind and available keys.
|
||||||
|
fn select_signer(
|
||||||
|
signer_kind: &SignerKind,
|
||||||
|
has_announcement: bool,
|
||||||
|
encryption_signer: &Option<UniversalSigner>,
|
||||||
|
user_signer: &UniversalSigner,
|
||||||
|
) -> UniversalSigner {
|
||||||
|
match signer_kind {
|
||||||
|
SignerKind::Auto => {
|
||||||
|
if has_announcement {
|
||||||
|
encryption_signer
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| user_signer.clone())
|
||||||
|
} else {
|
||||||
|
user_signer.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SignerKind::Encryption => encryption_signer
|
||||||
|
.clone()
|
||||||
|
.expect("encryption signer must be set"),
|
||||||
|
SignerKind::User => user_signer.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Send rumor event to all members's messaging relays
|
/// Send rumor event to all members's messaging relays
|
||||||
pub fn send(&self, rumor: UnsignedEvent, cx: &App) -> Option<Task<Vec<SendReport>>> {
|
pub fn send(&self, rumor: UnsignedEvent, cx: &App) -> Option<Task<Vec<SendReport>>> {
|
||||||
let config = self.config.clone();
|
let config = self.config.clone();
|
||||||
@@ -525,23 +551,12 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the signer to use
|
// Determine the signer to use
|
||||||
let signer = match signer_kind {
|
let signer = Self::select_signer(
|
||||||
SignerKind::Auto => {
|
signer_kind,
|
||||||
if announcement.is_some()
|
announcement.is_some(),
|
||||||
&& let Some(encryption_signer) = encryption_signer.clone()
|
&encryption_signer,
|
||||||
{
|
&user_signer,
|
||||||
// Safe to unwrap due to earlier checks
|
);
|
||||||
encryption_signer
|
|
||||||
} else {
|
|
||||||
user_signer.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SignerKind::Encryption => {
|
|
||||||
// Safe to unwrap due to earlier checks
|
|
||||||
encryption_signer.as_ref().unwrap().clone()
|
|
||||||
}
|
|
||||||
SignerKind::User => user_signer.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the gift wrap event and collect the report
|
// Send the gift wrap event and collect the report
|
||||||
match send_gift_wrap(&client, &signer, &member, &rumor, signer_kind).await {
|
match send_gift_wrap(&client, &signer, &member, &rumor, signer_kind).await {
|
||||||
@@ -561,23 +576,12 @@ impl Room {
|
|||||||
let public_key = sender.public_key();
|
let public_key = sender.public_key();
|
||||||
|
|
||||||
// Determine the signer to use
|
// Determine the signer to use
|
||||||
let signer = match signer_kind {
|
let signer = Self::select_signer(
|
||||||
SignerKind::Auto => {
|
signer_kind,
|
||||||
if sender.announcement().is_some()
|
sender.announcement().is_some(),
|
||||||
&& let Some(encryption_signer) = encryption_signer.clone()
|
&encryption_signer,
|
||||||
{
|
&user_signer,
|
||||||
// Safe to unwrap due to earlier checks
|
);
|
||||||
encryption_signer
|
|
||||||
} else {
|
|
||||||
user_signer.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SignerKind::Encryption => {
|
|
||||||
// Safe to unwrap due to earlier checks
|
|
||||||
encryption_signer.as_ref().unwrap().clone()
|
|
||||||
}
|
|
||||||
SignerKind::User => user_signer.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match send_gift_wrap(&client, &signer, &sender, &rumor, signer_kind).await {
|
match send_gift_wrap(&client, &signer, &sender, &rumor, signer_kind).await {
|
||||||
Ok(report) => reports.push(report),
|
Ok(report) => reports.push(report),
|
||||||
|
|||||||
@@ -351,13 +351,20 @@ impl ChatPanel {
|
|||||||
let content = value.to_string();
|
let content = value.to_string();
|
||||||
let sent_ids = self.sent_ids.clone();
|
let sent_ids = self.sent_ids.clone();
|
||||||
|
|
||||||
// Upgrade room and create the rumor synchronously
|
// Upgrade room and create rumor + send task in a single read lock
|
||||||
let Some(room_entity) = room.upgrade() else {
|
let Some(room_entity) = room.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(rumor) = room_entity.read(cx).rumor(content.clone(), replies, cx) else {
|
let (rumor, send_task) = match room_entity.read_with(cx, |room, cx| {
|
||||||
|
let rumor = room.rumor(content.clone(), replies, cx)?;
|
||||||
|
let send_task = room.send(rumor.clone(), cx)?;
|
||||||
|
Some((rumor, send_task))
|
||||||
|
}) {
|
||||||
|
Some(pair) => pair,
|
||||||
|
None => {
|
||||||
window.push_notification("Failed to create message", cx);
|
window.push_notification("Failed to create message", cx);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = rumor.id.expect("rumor must have an id");
|
let id = rumor.id.expect("rumor must have an id");
|
||||||
@@ -367,12 +374,6 @@ impl ChatPanel {
|
|||||||
self.insert_reports(id, vec![], cx);
|
self.insert_reports(id, vec![], cx);
|
||||||
self.clear(window, cx);
|
self.clear(window, cx);
|
||||||
|
|
||||||
// Get the send task
|
|
||||||
let Some(send_task) = room_entity.read(cx).send(rumor, cx) else {
|
|
||||||
window.push_notification("Failed to send message", cx);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Spawn a single task to await the send and update reports
|
// Spawn a single task to await the send and update reports
|
||||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
let outputs = send_task.await;
|
let outputs = send_task.await;
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ impl DeviceRegistry {
|
|||||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let settings = AppSettings::global(cx);
|
let settings = AppSettings::global(cx);
|
||||||
let nip4e_enabled = settings.read(cx).is_nip4e_enabled(cx);
|
|
||||||
|
|
||||||
let signer = cx.new(|_| None);
|
let signer = cx.new(|_| None);
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
@@ -109,7 +108,7 @@ impl DeviceRegistry {
|
|||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Observe the user signer
|
// Observe the user signer
|
||||||
cx.subscribe(&nostr, move |this, _nostr, event, cx| {
|
cx.subscribe(&nostr, move |this, _nostr, event, cx| {
|
||||||
if event.signer_changed() && nip4e_enabled {
|
if event.signer_changed() && settings.read(cx).is_nip4e_enabled(cx) {
|
||||||
this.get_announcement(cx);
|
this.get_announcement(cx);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub struct PersonRegistry {
|
|||||||
persons: HashMap<PublicKey, Entity<Person>>,
|
persons: HashMap<PublicKey, Entity<Person>>,
|
||||||
|
|
||||||
/// Set of public keys that have been seen
|
/// Set of public keys that have been seen
|
||||||
seens: RefCell<HashSet<PublicKey>>,
|
seen: RefCell<HashSet<PublicKey>>,
|
||||||
|
|
||||||
/// Sender for requesting metadata
|
/// Sender for requesting metadata
|
||||||
sender: flume::Sender<PublicKey>,
|
sender: flume::Sender<PublicKey>,
|
||||||
@@ -62,22 +62,18 @@ impl PersonRegistry {
|
|||||||
|
|
||||||
// Channel for communication between nostr and gpui
|
// Channel for communication between nostr and gpui
|
||||||
let (tx, rx) = flume::bounded::<Dispatch>(100);
|
let (tx, rx) = flume::bounded::<Dispatch>(100);
|
||||||
let (mta_tx, mta_rx) = flume::unbounded::<PublicKey>();
|
let (metadata_tx, metadata_rx) = flume::unbounded::<PublicKey>();
|
||||||
|
|
||||||
let mut tasks = smallvec![];
|
let mut tasks = smallvec![];
|
||||||
|
|
||||||
tasks.push(cx.background_spawn({
|
let client2 = client.clone();
|
||||||
let client = client.clone();
|
tasks.push(cx.background_spawn(async move {
|
||||||
async move {
|
Self::handle_notifications(&client2, &tx).await;
|
||||||
Self::handle_notifications(&client, &tx).await;
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
tasks.push(cx.background_spawn({
|
let client3 = client.clone();
|
||||||
let client = client.clone();
|
tasks.push(cx.background_spawn(async move {
|
||||||
async move {
|
Self::handle_requests(&client3, &metadata_rx).await;
|
||||||
Self::handle_requests(&client, &mta_rx).await;
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
tasks.push(cx.spawn(async move |this, cx| {
|
tasks.push(cx.spawn(async move |this, cx| {
|
||||||
@@ -106,8 +102,8 @@ impl PersonRegistry {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
persons: HashMap::new(),
|
persons: HashMap::new(),
|
||||||
seens: RefCell::new(HashSet::new()),
|
seen: RefCell::new(HashSet::new()),
|
||||||
sender: mta_tx,
|
sender: metadata_tx,
|
||||||
tasks,
|
tasks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +206,7 @@ impl PersonRegistry {
|
|||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||||
if let Ok(persons) = task.await {
|
if let Ok(persons) = task.await {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.bulk_inserts(persons, cx);
|
this.bulk_insert(persons, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -249,7 +245,7 @@ impl PersonRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Insert batch of persons
|
/// Insert batch of persons
|
||||||
fn bulk_inserts(&mut self, persons: Vec<Person>, cx: &mut Context<Self>) {
|
fn bulk_insert(&mut self, persons: Vec<Person>, cx: &mut Context<Self>) {
|
||||||
for person in persons.into_iter() {
|
for person in persons.into_iter() {
|
||||||
let public_key = person.public_key();
|
let public_key = person.public_key();
|
||||||
self.persons
|
self.persons
|
||||||
@@ -284,7 +280,7 @@ impl PersonRegistry {
|
|||||||
|
|
||||||
let public_key = *public_key;
|
let public_key = *public_key;
|
||||||
|
|
||||||
if self.seens.borrow_mut().insert(public_key) {
|
if self.seen.borrow_mut().insert(public_key) {
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
|
||||||
// Spawn background task to request metadata
|
// Spawn background task to request metadata
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ impl Workspace {
|
|||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
// Trigger a refresh of the chat registry
|
// Trigger a refresh of the chat registry
|
||||||
chat.update(cx, |this, cx| {
|
chat.update(cx, |this, cx| {
|
||||||
this.refresh(cx);
|
this.reload(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Command::ShowRelayList => {
|
Command::ShowRelayList => {
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ impl TrashPanel {
|
|||||||
|
|
||||||
fn copy(&self, ix: usize, cx: &App) {
|
fn copy(&self, ix: usize, cx: &App) {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let trashes = chat.read(cx).trashes();
|
let trash_events = chat.read(cx).trash();
|
||||||
|
|
||||||
if let Some(message) = trashes.read(cx).iter().nth(ix) {
|
if let Some(message) = trash_events.read(cx).iter().nth(ix) {
|
||||||
let item = ClipboardItem::new_string(message.raw_event.to_string());
|
let item = ClipboardItem::new_string(message.raw_event.to_string());
|
||||||
cx.write_to_clipboard(item);
|
cx.write_to_clipboard(item);
|
||||||
}
|
}
|
||||||
@@ -52,9 +52,9 @@ impl TrashPanel {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let trashes = chat.read(cx).trashes();
|
let trash_events = chat.read(cx).trash();
|
||||||
|
|
||||||
if let Some(message) = trashes.read(cx).iter().nth(ix) {
|
if let Some(message) = trash_events.read(cx).iter().nth(ix) {
|
||||||
v_flex()
|
v_flex()
|
||||||
.id(ix)
|
.id(ix)
|
||||||
.p_2()
|
.p_2()
|
||||||
|
|||||||
Reference in New Issue
Block a user