This commit is contained in:
2026-06-10 10:17:40 +07:00
parent 04983be23f
commit 0230fcff23
26 changed files with 718 additions and 1534 deletions

View File

@@ -16,7 +16,7 @@ use gpui::{
use nostr_sdk::prelude::*;
use smallvec::{SmallVec, smallvec};
use smol::lock::RwLock;
use state::{CoopSigner, DEVICE_GIFTWRAP, NostrRegistry, StateEvent, USER_GIFTWRAP};
use state::{DEVICE_GIFTWRAP, NostrRegistry, USER_GIFTWRAP};
mod message;
mod room;
@@ -137,15 +137,17 @@ impl ChatRegistry {
/// Create a new chat registry instance
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let nostr = NostrRegistry::global(cx);
let user_signer = nostr.read(cx).signer.clone();
let (tx, rx) = flume::unbounded::<Signal>();
let mut subscriptions = smallvec![];
subscriptions.push(
// Subscribe to the signer event
cx.subscribe(&nostr, |this, _state, event, cx| {
if event == &StateEvent::SignerSet {
cx.observe(&user_signer, |this, signer, cx| {
if let Some(keys) = signer.read(cx).clone() {
this.reset(cx);
this.handle_notifications(keys, cx);
this.get_metadata(cx);
this.get_rooms(cx);
};
@@ -154,7 +156,6 @@ impl ChatRegistry {
// Run at the end of the current cycle
cx.defer_in(window, |this, _window, cx| {
this.handle_notifications(cx);
this.tracking(cx);
this.get_rooms(cx);
});
@@ -174,10 +175,9 @@ impl ChatRegistry {
}
/// Handle nostr notifications
fn handle_notifications(&mut self, cx: &mut Context<Self>) {
fn handle_notifications(&mut self, signer: Keys, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let tracking = self.tracking.clone();
let msg_relays_existed = self.msg_relays_existed.clone();
@@ -219,7 +219,7 @@ impl ChatRegistry {
// Handle msg relays event to determine when the app is ready to subscribe
if event.kind == Kind::InboxRelays {
let current_user = signer.get_public_key().await?;
let current_user = signer.get_public_key_async().await?;
if event.pubkey == current_user {
// Mark that the msg relays have been found
@@ -265,10 +265,10 @@ impl ChatRegistry {
}
}
}
RelayMessage::EndOfStoredEvents(id) => {
if id.as_ref() == &sub_id1 || id.as_ref() == &sub_id2 {
tx.send_async(Signal::eose()).await?;
}
RelayMessage::EndOfStoredEvents(id)
if (id.as_ref() == &sub_id1 || id.as_ref() == &sub_id2) =>
{
tx.send_async(Signal::eose()).await?;
}
_ => {}
}
@@ -337,9 +337,8 @@ impl ChatRegistry {
pub fn get_metadata(&mut self, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let Some(public_key) = signer.public_key() else {
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
return;
};
@@ -392,11 +391,14 @@ impl ChatRegistry {
fn get_messages(&mut self, msg_relays: &Event, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
let urls: Vec<RelayUrl> = nip17::extract_relay_list(msg_relays).cloned().collect();
let urls: Vec<RelayUrl> = nip17::extract_relay_list(msg_relays).collect();
let Some(signer) = nostr.read(cx).signer(cx) else {
return;
};
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
let public_key = signer.get_public_key().await?;
let public_key = signer.get_public_key_async().await?;
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
let id = SubscriptionId::new(format!("{}-msg", public_key.to_hex()));
@@ -510,11 +512,12 @@ impl ChatRegistry {
I: Into<Room> + 'static,
{
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
return;
};
cx.spawn(async move |this, cx| {
let signer = client.signer()?;
let public_key = signer.get_public_key().await.ok()?;
let room: Room = room.into().organize(&public_key);
this.update(cx, |this, cx| {
@@ -643,10 +646,11 @@ impl ChatRegistry {
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
cx.background_spawn(async move {
let signer = client.signer().context("Signer not found")?;
let public_key = signer.get_public_key().await?;
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
return Task::ready(Err(anyhow!("Signer is required")));
};
cx.background_spawn(async move {
// Get contacts
let contacts = client
.database()
@@ -723,15 +727,15 @@ impl ChatRegistry {
/// Updates room ordering based on the most recent messages.
pub fn new_message(&mut self, message: NewMessage, cx: &mut Context<Self>) {
let nostr = NostrRegistry::global(cx);
let signer = nostr.read(cx).signer();
let Some(public_key) = nostr.read(cx).signer_pubkey(cx) else {
return;
};
match self.rooms.iter().find(|e| e.read(cx).id == message.room) {
Some(room) => {
room.update(cx, |this, cx| {
if this.kind == RoomKind::Request
&& let Some(public_key) = signer.public_key()
&& message.rumor.pubkey == public_key
{
if this.kind == RoomKind::Request && message.rumor.pubkey == public_key {
this.set_ongoing(cx);
}
this.push_message(message, cx);
@@ -760,7 +764,7 @@ impl ChatRegistry {
/// Unwraps a gift-wrapped event and processes its contents.
async fn extract_rumor(
client: &Client,
signer: &Arc<CoopSigner>,
signer: &Keys,
gift_wrap: &Event,
) -> Result<UnsignedEvent, Error> {
// Try to get cached rumor first
@@ -784,8 +788,9 @@ async fn extract_rumor(
}
/// Helper method to try unwrapping with different signers
async fn try_unwrap(signer: &Arc<CoopSigner>, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
// Try with the device signer first
async fn try_unwrap(signer: &Keys, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
/*
* // Try with the device signer first
if let Some(signer) = signer.get_encryption_signer().await {
log::info!("trying with encryption key");
if let Ok(unwrapped) = try_unwrap_with(gift_wrap, &signer).await {
@@ -795,19 +800,17 @@ async fn try_unwrap(signer: &Arc<CoopSigner>, gift_wrap: &Event) -> Result<Unwra
// Fallback to the user's signer
let user_signer = signer.get().await;
let unwrapped = try_unwrap_with(gift_wrap, &user_signer).await?;
*/
let unwrapped = try_unwrap_with(gift_wrap, signer).await?;
Ok(unwrapped)
}
/// Attempts to unwrap a gift wrap event with a given signer.
async fn try_unwrap_with<T>(gift_wrap: &Event, signer: &T) -> Result<UnwrappedGift, Error>
where
T: NostrSigner + 'static,
{
async fn try_unwrap_with(gift_wrap: &Event, signer: &Keys) -> Result<UnwrappedGift, Error> {
// Get the sealed event
let seal = signer
.nip44_decrypt(&gift_wrap.pubkey, &gift_wrap.content)
.nip44_decrypt_async(&gift_wrap.pubkey, &gift_wrap.content)
.await?;
// Verify the sealed event
@@ -815,7 +818,10 @@ where
seal.verify_with_ctx(&SECP256K1)?;
// Get the rumor event
let rumor = signer.nip44_decrypt(&seal.pubkey, &seal.content).await?;
let rumor = signer
.nip44_decrypt_async(&seal.pubkey, &seal.content)
.await?;
let rumor = UnsignedEvent::from_json(rumor)?;
Ok(UnwrappedGift {
@@ -836,26 +842,17 @@ async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Resul
tags.push(Tag::identifier(id));
// Add a reference to the rumor's author
tags.push(Tag::custom(
TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::A)),
[author],
));
tags.push(Tag::custom("a", [author]));
// Add a conversation id
tags.push(Tag::custom(
TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::C)),
[conversation.to_string()],
));
tags.push(Tag::custom("c", [conversation.to_string()]));
// Add a reference to the rumor's id
tags.push(Tag::event(rumor_id));
// Add references to the rumor's participants
for receiver in rumor.tags.public_keys().copied() {
tags.push(Tag::custom(
TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::P)),
[receiver],
));
for receiver in rumor.tags.public_keys() {
tags.push(Tag::custom("P", [receiver]));
}
// Convert rumor to json
@@ -864,7 +861,7 @@ async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Resul
// Construct the event
let event = EventBuilder::new(Kind::ApplicationSpecificData, content)
.tags(tags)
.sign(&Keys::generate())
.finalize_async(&Keys::generate())
.await?;
// Save the event to the database
@@ -890,7 +887,7 @@ async fn get_rumor(client: &Client, gift_wrap: EventId) -> Result<UnsignedEvent,
/// Get the conversation ID for a given rumor (message).
fn conversation_id(rumor: &UnsignedEvent) -> u64 {
let mut hasher = DefaultHasher::new();
let mut pubkeys: Vec<PublicKey> = rumor.tags.public_keys().copied().collect();
let mut pubkeys: Vec<PublicKey> = rumor.tags.public_keys().collect();
pubkeys.push(rumor.pubkey);
pubkeys.sort();
pubkeys.dedup();

View File

@@ -242,13 +242,13 @@ fn extract_mentions(content: &str) -> Vec<Mention> {
fn extract_reply_ids(inner: &Tags) -> Vec<EventId> {
let mut replies_to = vec![];
for tag in inner.filter(TagKind::e()) {
for tag in inner.iter().filter(|tag| tag.kind() == "e") {
if let Some(id) = tag.content().and_then(|id| EventId::parse(id).ok()) {
replies_to.push(id);
}
}
for tag in inner.filter(TagKind::q()) {
for tag in inner.iter().filter(|tag| tag.kind() == "q") {
if let Some(id) = tag.content().and_then(|id| EventId::parse(id).ok()) {
replies_to.push(id);
}

View File

@@ -4,6 +4,7 @@ use std::time::Duration;
use anyhow::{Error, anyhow};
use common::EventExt;
use device::DeviceRegistry;
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
use itertools::Itertools;
use nostr_sdk::prelude::*;
@@ -21,7 +22,7 @@ pub struct SendReport {
pub receiver: PublicKey,
pub gift_wrap_id: Option<EventId>,
pub error: Option<SharedString>,
pub output: Option<Output<EventId>>,
pub output: Option<Output<EventId, EventSendStatus>>,
}
impl SendReport {
@@ -41,7 +42,7 @@ impl SendReport {
}
/// Set the output.
pub fn output(mut self, output: Output<EventId>) -> Self {
pub fn output(mut self, output: Output<EventId, EventSendStatus>) -> Self {
self.output = Some(output);
self
}
@@ -171,7 +172,8 @@ impl From<&UnsignedEvent> for Room {
let members = val.extract_public_keys();
let subject = val
.tags
.find(TagKind::Subject)
.iter()
.find(|tag| tag.kind() == "subject")
.and_then(|tag| tag.content().map(|s| s.to_owned().into()));
Room {
@@ -205,7 +207,7 @@ impl Room {
// WARNING: never sign this event
let mut event = EventBuilder::new(Kind::PrivateDirectMessage, "")
.tags(tags)
.build(author);
.finalize_unsigned(author);
// Ensure that the ID is set
event.ensure_id();
@@ -425,7 +427,7 @@ impl Room {
let nostr = NostrRegistry::global(cx);
// Get current user's public key
let sender = nostr.read(cx).signer().public_key()?;
let sender = nostr.read(cx).signer_pubkey(cx)?;
// Get all members, excluding the sender
let members: Vec<Person> = self
@@ -440,9 +442,7 @@ impl Room {
// 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(),
)));
tags.push(Tag::custom("subject", vec![value.to_string()]));
}
// Add all reply tags
@@ -452,19 +452,20 @@ impl Room {
// Add all receiver tags
for member in members.into_iter() {
tags.push(Tag::from_standardized_without_cell(
TagStandard::PublicKey {
tags.push(
Nip01Tag::PublicKey {
public_key: member.public_key(),
relay_url: member.messaging_relay_hint(),
alias: None,
uppercase: false,
},
));
relay_hint: member.messaging_relay_hint(),
}
.to_tag(),
);
}
// 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);
let mut event = EventBuilder::new(kind, content)
.tags(tags)
.finalize_unsigned(sender);
// Ensure that the ID is set
event.ensure_id();
@@ -475,13 +476,18 @@ impl Room {
/// Send rumor event to all members's messaging relays
pub fn send(&self, rumor: UnsignedEvent, cx: &App) -> Option<Task<Vec<SendReport>>> {
let config = self.config.clone();
let persons = PersonRegistry::global(cx);
let device = DeviceRegistry::global(cx);
let encryption_signer = device.read(cx).signer(cx);
let nostr = NostrRegistry::global(cx);
let client = nostr.read(cx).client();
let signer = nostr.read(cx).signer();
// Get current user's public key
let public_key = nostr.read(cx).signer().public_key()?;
let user_signer = nostr.read(cx).signer(cx)?;
let public_key = nostr.read(cx).signer_pubkey(cx)?;
let persons = PersonRegistry::global(cx);
let sender = persons.read(cx).get(&public_key, cx);
// Get all members (excluding sender)
@@ -496,9 +502,6 @@ impl Room {
let signer_kind = config.signer_kind();
let backup = config.backup();
let user_signer = signer.get().await;
let encryption_signer = signer.get_encryption_signer().await;
let mut sents = 0;
let mut reports = Vec::new();
@@ -592,17 +595,14 @@ impl Room {
}
// Helper function to send a gift-wrapped event
async fn send_gift_wrap<T>(
async fn send_gift_wrap(
client: &Client,
signer: &T,
signer: &Keys,
receiver: &Person,
rumor: &UnsignedEvent,
config: &SignerKind,
) -> Result<SendReport, Error>
where
T: NostrSigner + 'static,
{
let k_tag = Tag::custom(TagKind::k(), vec!["14"]);
) -> Result<SendReport, Error> {
let k_tag = Tag::custom("k", vec!["14"]);
let mut extra_tags = vec![k_tag];
// Determine the receiver public key based on the config
@@ -627,7 +627,10 @@ where
};
// Construct the gift wrap event
let event = EventBuilder::gift_wrap(signer, &receiver, rumor.clone(), extra_tags).await?;
let event = nip59::GiftWrapBuilder::new(receiver, rumor.clone())
.extra_tags(extra_tags)
.finalize_async(signer)
.await?;
// Send the gift wrap event and collect the report
let report = client