feat: rewrite the nip-4e implementation #1

Merged
reya merged 17 commits from rewrite-nostr-backend into master 2026-01-13 16:00:09 +08:00
12 changed files with 77 additions and 77 deletions
Showing only changes of commit 2d9563b888 - Show all commits

2
Cargo.lock generated
View File

@@ -4467,8 +4467,8 @@ version = "0.3.0"
dependencies = [
"anyhow",
"common",
"flume",
"gpui",
"itertools 0.13.0",
"log",
"nostr-sdk",
"smallvec",

View File

@@ -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

View File

@@ -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()

View File

@@ -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);

View File

@@ -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();

View File

@@ -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![];

View File

@@ -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()

View File

@@ -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![];

View File

@@ -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())

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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);
}
}