feat: rewrite the nip-4e implementation #1
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4467,8 +4467,8 @@ version = "0.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"common",
|
||||
"flume",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"nostr-sdk",
|
||||
"smallvec",
|
||||
|
||||
@@ -278,7 +278,7 @@ impl Room {
|
||||
.or_else(|| self.members.first())
|
||||
.expect("Room should have at least one member");
|
||||
|
||||
persons.read(cx).get_person(target_member, cx)
|
||||
persons.read(cx).get(target_member, cx)
|
||||
}
|
||||
|
||||
/// Merge the names of the first two members of the room.
|
||||
@@ -289,7 +289,7 @@ impl Room {
|
||||
let profiles: Vec<Profile> = self
|
||||
.members
|
||||
.iter()
|
||||
.map(|public_key| persons.read(cx).get_person(public_key, cx))
|
||||
.map(|public_key| persons.read(cx).get(public_key, cx))
|
||||
.collect();
|
||||
|
||||
let mut name = profiles
|
||||
|
||||
@@ -508,7 +508,7 @@ impl ChatPanel {
|
||||
|
||||
fn profile(&self, public_key: &PublicKey, cx: &Context<Self>) -> Profile {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
persons.read(cx).get_person(public_key, cx)
|
||||
persons.read(cx).get(public_key, cx)
|
||||
}
|
||||
|
||||
fn render_announcement(&self, ix: usize, cx: &Context<Self>) -> AnyElement {
|
||||
@@ -795,7 +795,7 @@ impl ChatPanel {
|
||||
|
||||
fn render_report(report: &SendReport, cx: &App) -> impl IntoElement {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get_person(&report.receiver, cx);
|
||||
let profile = persons.read(cx).get(&report.receiver, cx);
|
||||
let name = profile.display_name();
|
||||
let avatar = profile.avatar(true);
|
||||
|
||||
@@ -1057,7 +1057,7 @@ impl ChatPanel {
|
||||
fn render_reply(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
|
||||
if let Some(text) = self.message(id) {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get_person(&text.author, cx);
|
||||
let profile = persons.read(cx).get(&text.author, cx);
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
|
||||
@@ -254,7 +254,7 @@ fn render_pubkey(
|
||||
cx: &App,
|
||||
) {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get_person(&public_key, cx);
|
||||
let profile = persons.read(cx).get(&public_key, cx);
|
||||
let display_name = format!("@{}", profile.display_name());
|
||||
|
||||
text.replace_range(range.clone(), &display_name);
|
||||
|
||||
@@ -259,7 +259,7 @@ impl ChatSpace {
|
||||
match result {
|
||||
Ok(profile) => {
|
||||
persons.update(cx, |this, cx| {
|
||||
this.insert_or_update_person(profile, cx);
|
||||
this.insert(profile, cx);
|
||||
// Close the edit profile modal
|
||||
window.close_all_modals(cx);
|
||||
});
|
||||
@@ -545,7 +545,7 @@ impl ChatSpace {
|
||||
})
|
||||
.when_some(identity.read(cx).option_public_key(), |this, public_key| {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get_person(&public_key, cx);
|
||||
let profile = persons.read(cx).get(&public_key, cx);
|
||||
|
||||
let keystore = KeyStore::global(cx);
|
||||
let is_using_file_keystore = keystore.read(cx).is_using_file_keystore();
|
||||
|
||||
@@ -44,7 +44,7 @@ impl ProfileViewer {
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get_person(&target, cx);
|
||||
let profile = persons.read(cx).get(&target, cx);
|
||||
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ impl Compose {
|
||||
};
|
||||
|
||||
let public_key = contact.public_key;
|
||||
let profile = persons.read(cx).get_person(&public_key, cx);
|
||||
let profile = persons.read(cx).get(&public_key, cx);
|
||||
|
||||
items.push(
|
||||
h_flex()
|
||||
|
||||
@@ -37,7 +37,7 @@ impl Screening {
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get_person(&public_key, cx);
|
||||
let profile = persons.read(cx).get(&public_key, cx);
|
||||
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
|
||||
@@ -205,9 +205,7 @@ impl Render for Startup {
|
||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let bunker = self.credential.secret().starts_with("bunker://");
|
||||
let profile = persons
|
||||
.read(cx)
|
||||
.get_person(&self.credential.public_key(), cx);
|
||||
let profile = persons.read(cx).get(&self.credential.public_key(), cx);
|
||||
|
||||
v_flex()
|
||||
.image_cache(self.image_cache.clone())
|
||||
|
||||
@@ -11,7 +11,7 @@ state = { path = "../state" }
|
||||
gpui.workspace = true
|
||||
nostr-sdk.workspace = true
|
||||
anyhow.workspace = true
|
||||
itertools.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
flume.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -17,64 +17,50 @@ impl Global for GlobalPersonRegistry {}
|
||||
#[derive(Debug)]
|
||||
pub struct PersonRegistry {
|
||||
/// Collection of all persons (user profiles)
|
||||
pub persons: HashMap<PublicKey, Entity<Profile>>,
|
||||
persons: HashMap<PublicKey, Entity<Profile>>,
|
||||
|
||||
/// Tasks for asynchronous operations
|
||||
_tasks: SmallVec<[Task<()>; 2]>,
|
||||
_tasks: SmallVec<[Task<()>; 3]>,
|
||||
}
|
||||
|
||||
impl PersonRegistry {
|
||||
/// Retrieve the global person registry state
|
||||
/// Retrieve the global person registry
|
||||
pub fn global(cx: &App) -> Entity<Self> {
|
||||
cx.global::<GlobalPersonRegistry>().0.clone()
|
||||
}
|
||||
|
||||
/// Set the global person registry instance
|
||||
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
|
||||
fn set_global(state: Entity<Self>, cx: &mut App) {
|
||||
cx.set_global(GlobalPersonRegistry(state));
|
||||
}
|
||||
|
||||
/// Create a new person registry instance
|
||||
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
|
||||
fn new(cx: &mut Context<Self>) -> Self {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
// Channel for communication between nostr and gpui
|
||||
let (tx, rx) = flume::bounded::<Profile>(100);
|
||||
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
tasks.push(
|
||||
// Handle notifications
|
||||
cx.spawn({
|
||||
let client = nostr.read(cx).client();
|
||||
// Handle nostr notifications
|
||||
cx.background_spawn({
|
||||
let client = client.clone();
|
||||
|
||||
async move |this, cx| {
|
||||
let mut notifications = client.notifications();
|
||||
let mut processed_events = HashSet::new();
|
||||
async move { Self::handle_notifications(&client, &tx).await }
|
||||
}),
|
||||
);
|
||||
|
||||
while let Ok(notification) = notifications.recv().await {
|
||||
let RelayPoolNotification::Message { message, .. } = notification else {
|
||||
// Skip if the notification is not a message
|
||||
continue;
|
||||
};
|
||||
|
||||
if let RelayMessage::Event { event, .. } = message {
|
||||
if !processed_events.insert(event.id) {
|
||||
// Skip if the event has already been processed
|
||||
continue;
|
||||
}
|
||||
|
||||
if event.kind != Kind::Metadata {
|
||||
// Skip if the event is not a metadata event
|
||||
continue;
|
||||
};
|
||||
|
||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||
let profile = Profile::new(event.pubkey, metadata);
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.insert_or_update_person(profile, cx);
|
||||
})
|
||||
.expect("Entity has been released")
|
||||
}
|
||||
}
|
||||
tasks.push(
|
||||
// Update GPUI state
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Ok(profile) = rx.recv_async().await {
|
||||
this.update(cx, |this, cx| {
|
||||
this.insert(profile, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -83,18 +69,19 @@ impl PersonRegistry {
|
||||
// Load all user profiles from the database
|
||||
cx.spawn(async move |this, cx| {
|
||||
let result = cx
|
||||
.background_spawn(async move { Self::load_persons(&client).await })
|
||||
.background_executor()
|
||||
.await_on_background(async move { Self::load_persons(&client).await })
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(profiles) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.bulk_insert_persons(profiles, cx);
|
||||
this.bulk_inserts(profiles, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to load persons: {e}");
|
||||
log::error!("Failed to load all persons from the database: {e}");
|
||||
}
|
||||
};
|
||||
}),
|
||||
@@ -106,6 +93,34 @@ impl PersonRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle nostr notifications
|
||||
async fn handle_notifications(client: &Client, tx: &flume::Sender<Profile>) {
|
||||
let mut notifications = client.notifications();
|
||||
let mut processed_events = HashSet::new();
|
||||
|
||||
while let Ok(notification) = notifications.recv().await {
|
||||
let RelayPoolNotification::Message { message, .. } = notification else {
|
||||
// Skip if the notification is not a message
|
||||
continue;
|
||||
};
|
||||
|
||||
if let RelayMessage::Event { event, .. } = message {
|
||||
if !processed_events.insert(event.id) {
|
||||
// Skip if the event has already been processed
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only process metadata events
|
||||
if event.kind == Kind::Metadata {
|
||||
let metadata = Metadata::from_json(&event.content).unwrap_or_default();
|
||||
let profile = Profile::new(event.pubkey, metadata);
|
||||
|
||||
tx.send_async(profile).await.ok();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all user profiles from the database
|
||||
async fn load_persons(client: &Client) -> Result<Vec<Profile>, Error> {
|
||||
let filter = Filter::new().kind(Kind::Metadata).limit(200);
|
||||
@@ -123,7 +138,7 @@ impl PersonRegistry {
|
||||
}
|
||||
|
||||
/// Insert batch of persons
|
||||
fn bulk_insert_persons(&mut self, profiles: Vec<Profile>, cx: &mut Context<Self>) {
|
||||
fn bulk_inserts(&mut self, profiles: Vec<Profile>, cx: &mut Context<Self>) {
|
||||
for profile in profiles.into_iter() {
|
||||
self.persons
|
||||
.insert(profile.public_key(), cx.new(|_| profile));
|
||||
@@ -132,7 +147,7 @@ impl PersonRegistry {
|
||||
}
|
||||
|
||||
/// Insert or update a person
|
||||
pub fn insert_or_update_person(&mut self, profile: Profile, cx: &mut App) {
|
||||
pub fn insert(&mut self, profile: Profile, cx: &mut App) {
|
||||
let public_key = profile.public_key();
|
||||
|
||||
match self.persons.get(&public_key) {
|
||||
@@ -148,24 +163,12 @@ impl PersonRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get single person
|
||||
pub fn get_person(&self, public_key: &PublicKey, cx: &App) -> Profile {
|
||||
/// Get single person by public key
|
||||
pub fn get(&self, public_key: &PublicKey, cx: &App) -> Profile {
|
||||
self.persons
|
||||
.get(public_key)
|
||||
.map(|e| e.read(cx))
|
||||
.cloned()
|
||||
.unwrap_or(Profile::new(public_key.to_owned(), Metadata::default()))
|
||||
}
|
||||
|
||||
/// Get group of persons
|
||||
pub fn get_group_person(&self, public_keys: &[PublicKey], cx: &App) -> Vec<Profile> {
|
||||
let mut profiles = vec![];
|
||||
|
||||
for public_key in public_keys.iter() {
|
||||
let profile = self.get_person(public_key, cx);
|
||||
profiles.push(profile);
|
||||
}
|
||||
|
||||
profiles
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,12 +313,13 @@ impl RelayAuth {
|
||||
move |_ev, window, cx| {
|
||||
// Set loading state to true
|
||||
loading.set(true);
|
||||
|
||||
// Process to approve the request
|
||||
entity
|
||||
.update(cx, |this, cx| {
|
||||
this.response(req.clone(), window, cx);
|
||||
})
|
||||
.expect("Entity has been released");
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -326,9 +327,7 @@ impl RelayAuth {
|
||||
// Push the notification to the current window
|
||||
window.push_notification(note, cx);
|
||||
|
||||
// Focus the window if it's not active
|
||||
if !window.is_window_hovered() {
|
||||
window.activate_window();
|
||||
}
|
||||
// Bring the window to the front
|
||||
cx.activate(true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user