refactor
This commit is contained in:
@@ -8,7 +8,7 @@ use instant::Duration;
|
||||
use itertools::Itertools;
|
||||
use nip4e::DeviceRegistry;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{Directory, Person, WebOfTrust};
|
||||
use person::{Directory, Person, PersonRegistry};
|
||||
use settings::{AppSettings, RoomConfig, SignerKind};
|
||||
use state::{NostrRegistry, TIMEOUT, UniversalSigner};
|
||||
|
||||
@@ -190,7 +190,7 @@ impl Room {
|
||||
return false;
|
||||
}
|
||||
|
||||
let wot = WebOfTrust::global(cx).read(cx);
|
||||
let wot = PersonRegistry::global(cx).read(cx).wot().read(cx);
|
||||
|
||||
Self::trusted_by(&self.members, me, |key| wot.is_in_network(key))
|
||||
}
|
||||
@@ -203,7 +203,7 @@ impl Room {
|
||||
return false;
|
||||
};
|
||||
|
||||
let wot = WebOfTrust::global(cx).read(cx);
|
||||
let wot = PersonRegistry::global(cx).read(cx).wot().read(cx);
|
||||
|
||||
Self::trusted_by(&self.members, me, |key| !wot.is_outside(key))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ pub use wot::*;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
PersonRegistry::set_global(cx.new(PersonRegistry::new), cx);
|
||||
WebOfTrust::set_global(cx.new(WebOfTrust::new), cx);
|
||||
}
|
||||
|
||||
struct GlobalPersonRegistry(Entity<PersonRegistry>);
|
||||
@@ -63,6 +62,8 @@ enum Dispatch {
|
||||
pub struct PersonRegistry {
|
||||
/// Public keys with a metadata request in flight
|
||||
in_flight: RwLock<HashSet<PublicKey>>,
|
||||
/// Web of trust graph of the signed-in user
|
||||
wot: Entity<WebOfTrust>,
|
||||
sender: flume::Sender<PublicKey>,
|
||||
tasks: SmallVec<[Task<()>; 4]>,
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
@@ -83,6 +84,7 @@ impl PersonRegistry {
|
||||
fn new(cx: &mut Context<Self>) -> Self {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
let wot = cx.new(WebOfTrust::new);
|
||||
|
||||
// Channel for communication between nostr and gpui
|
||||
let (tx, rx) = flume::bounded::<Dispatch>(100);
|
||||
@@ -125,17 +127,28 @@ impl PersonRegistry {
|
||||
let subscription = cx.subscribe(&nostr, |this, _nostr, event, cx| {
|
||||
if event.signer_changed() {
|
||||
this.get_emojis(cx);
|
||||
|
||||
this.wot.update(cx, |wot, cx| {
|
||||
wot.reset(cx);
|
||||
wot.hydrate(cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
in_flight: RwLock::new(HashSet::new()),
|
||||
wot,
|
||||
sender: metadata_tx,
|
||||
tasks,
|
||||
_subscriptions: smallvec![subscription],
|
||||
}
|
||||
}
|
||||
|
||||
/// The web of trust graph of the signed-in user
|
||||
pub fn wot(&self) -> Entity<WebOfTrust> {
|
||||
self.wot.clone()
|
||||
}
|
||||
|
||||
/// Handle nostr notifications
|
||||
async fn handle_notifications(client: &Client, tx: &flume::Sender<Dispatch>) {
|
||||
let mut notifications = client.notifications();
|
||||
|
||||
+14
-65
@@ -1,6 +1,6 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Subscription, Task};
|
||||
use gpui::{AppContext, Context, EventEmitter, Task};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
@@ -11,20 +11,11 @@ const MAX_FOLLOWS: usize = 2000;
|
||||
/// Cap a followed author's contact list to bound hostile publishers
|
||||
const MAX_TAGS_PER_LIST: usize = 5000;
|
||||
/// Time between reconciliations with the relays
|
||||
const SYNC_INTERVAL: u64 = 15 * 60;
|
||||
const INTERVAL: u64 = 15 * 60;
|
||||
|
||||
struct GlobalWebOfTrust(Entity<WebOfTrust>);
|
||||
|
||||
impl Global for GlobalWebOfTrust {}
|
||||
|
||||
/// A change to the web of trust
|
||||
/// The web of trust graph changed
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum WoTEvent {
|
||||
/// The graph changed
|
||||
Updated,
|
||||
/// The initial load finished
|
||||
Ready,
|
||||
}
|
||||
pub struct WoTEvent;
|
||||
|
||||
impl EventEmitter<WoTEvent> for WebOfTrust {}
|
||||
|
||||
@@ -48,38 +39,22 @@ pub struct WebOfTrust {
|
||||
disabled: bool,
|
||||
/// Whether the initial load has finished
|
||||
ready: bool,
|
||||
/// Whether the next refresh completes the initial load
|
||||
initial: bool,
|
||||
/// Task loading the graph, replaced by newer loads
|
||||
hydration: Option<Task<()>>,
|
||||
/// Tasks for asynchronous operations
|
||||
tasks: SmallVec<[Task<()>; 4]>,
|
||||
/// Event subscriptions
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
}
|
||||
|
||||
impl WebOfTrust {
|
||||
/// Retrieve the global web of trust
|
||||
pub fn global(cx: &App) -> Entity<Self> {
|
||||
cx.global::<GlobalWebOfTrust>().0.clone()
|
||||
}
|
||||
|
||||
/// Set the global web of trust instance
|
||||
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
|
||||
cx.set_global(GlobalWebOfTrust(state));
|
||||
}
|
||||
|
||||
/// Create a new web of trust instance
|
||||
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let entity = cx.entity().downgrade();
|
||||
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
tasks.push(cx.spawn(async move |this, cx| {
|
||||
loop {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_secs(SYNC_INTERVAL))
|
||||
.timer(Duration::from_secs(INTERVAL))
|
||||
.await;
|
||||
// Ignore errors, the next refresh will retry
|
||||
let _ = this.update(cx, |this, cx| {
|
||||
@@ -88,13 +63,6 @@ impl WebOfTrust {
|
||||
}
|
||||
}));
|
||||
|
||||
let subscription = cx.subscribe(&nostr, |this, _nostr, event, cx| {
|
||||
if event.signer_changed() {
|
||||
this.reset(cx);
|
||||
this.hydrate(cx);
|
||||
}
|
||||
});
|
||||
|
||||
cx.defer(move |cx| {
|
||||
let _ = entity.update(cx, |this, cx| {
|
||||
this.hydrate(cx);
|
||||
@@ -109,23 +77,21 @@ impl WebOfTrust {
|
||||
reverse: HashMap::new(),
|
||||
disabled: false,
|
||||
ready: false,
|
||||
initial: false,
|
||||
hydration: None,
|
||||
tasks,
|
||||
_subscriptions: smallvec![subscription],
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the graph for the current account
|
||||
fn hydrate(&mut self, cx: &mut Context<Self>) {
|
||||
pub(crate) fn hydrate(&mut self, cx: &mut Context<Self>) {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
let Some(self_key) = nostr.read(cx).current_user() else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.self_key = Some(self_key);
|
||||
self.initial = true;
|
||||
|
||||
// The locally stored contact list, applied before the relays respond
|
||||
let local: Task<Option<Event>> = cx.background_spawn(async move {
|
||||
@@ -155,7 +121,7 @@ impl WebOfTrust {
|
||||
// Credit the local graph before the relays respond
|
||||
this.update(cx, |this, cx| {
|
||||
if this.self_key == Some(self_key) && this.store_follows(at, snapshot.clone()) {
|
||||
cx.emit(WoTEvent::Updated);
|
||||
cx.emit(WoTEvent);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
@@ -206,16 +172,12 @@ impl WebOfTrust {
|
||||
let mut grew = false;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
let was_ready = this.ready;
|
||||
let changed = this.apply_latest(self_key, events, cx);
|
||||
this.ready = true;
|
||||
|
||||
if this.initial {
|
||||
this.initial = false;
|
||||
this.ready = true;
|
||||
cx.emit(WoTEvent::Ready);
|
||||
}
|
||||
|
||||
if changed {
|
||||
cx.emit(WoTEvent::Updated);
|
||||
if changed || !was_ready {
|
||||
cx.emit(WoTEvent);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -407,7 +369,7 @@ impl WebOfTrust {
|
||||
}
|
||||
|
||||
/// Clear the graph on sign out or account switch
|
||||
fn reset(&mut self, cx: &mut Context<Self>) {
|
||||
pub(crate) fn reset(&mut self, cx: &mut Context<Self>) {
|
||||
self.self_key = None;
|
||||
self.follows.clear();
|
||||
self.follows_at = None;
|
||||
@@ -415,10 +377,9 @@ impl WebOfTrust {
|
||||
self.reverse.clear();
|
||||
self.disabled = false;
|
||||
self.ready = false;
|
||||
self.initial = false;
|
||||
self.hydration = None;
|
||||
|
||||
cx.emit(WoTEvent::Updated);
|
||||
cx.emit(WoTEvent);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -450,20 +411,10 @@ impl WebOfTrust {
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the initial load has finished
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.ready
|
||||
}
|
||||
|
||||
/// Whether the graph is loaded and score queries are meaningful
|
||||
pub fn is_loaded(&self) -> bool {
|
||||
self.ready && !self.disabled
|
||||
}
|
||||
|
||||
/// Whether scoring is disabled due to an oversized follow set
|
||||
pub fn is_disabled(&self) -> bool {
|
||||
self.disabled
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -487,10 +438,8 @@ mod tests {
|
||||
reverse: HashMap::new(),
|
||||
disabled: false,
|
||||
ready: false,
|
||||
initial: false,
|
||||
hydration: None,
|
||||
tasks: smallvec![],
|
||||
_subscriptions: smallvec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::{
|
||||
};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{Directory, Person, PersonRegistry, WebOfTrust, shorten_pubkey};
|
||||
use person::{Directory, Person, PersonRegistry, shorten_pubkey};
|
||||
use settings::AppSettings;
|
||||
use state::{NostrRegistry, upload};
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
|
||||
@@ -460,7 +460,7 @@ impl Render for ProfileDialog {
|
||||
.map(SharedString::from);
|
||||
|
||||
let score = {
|
||||
let wot = WebOfTrust::global(cx).read(cx);
|
||||
let wot = PersonRegistry::global(cx).read(cx).wot().read(cx);
|
||||
wot.is_loaded().then(|| wot.score(&self.public_key))
|
||||
}
|
||||
.map(|score| {
|
||||
|
||||
@@ -9,7 +9,7 @@ use gpui::{
|
||||
};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::{Directory, Person, WebOfTrust, shorten_pubkey};
|
||||
use person::{Directory, Person, PersonRegistry, shorten_pubkey};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use state::{BOOTSTRAP_RELAYS, NostrAddress, NostrRegistry, TIMEOUT};
|
||||
use theme::{ActiveTheme, TextStyle, Typeset as _, button_radius};
|
||||
@@ -33,7 +33,7 @@ pub struct Screening {
|
||||
last_active: Option<Timestamp>,
|
||||
/// All mutual contacts of the person being screened.
|
||||
mutual_contacts: Vec<PublicKey>,
|
||||
/// Whether the database fallback for mutual contacts has run.
|
||||
/// Whether the fallback lookup for mutual contacts has run.
|
||||
mutuals_fallback: bool,
|
||||
/// Person directory for the person and their mutual contacts
|
||||
directory: Entity<Directory>,
|
||||
@@ -43,7 +43,7 @@ pub struct Screening {
|
||||
|
||||
impl Screening {
|
||||
pub fn new(public_key: PublicKey, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let wot = WebOfTrust::global(cx);
|
||||
let wot = PersonRegistry::global(cx).read(cx).wot();
|
||||
let directory = Directory::new(cx);
|
||||
|
||||
let mut subscriptions = smallvec![];
|
||||
@@ -53,8 +53,8 @@ impl Screening {
|
||||
window.close_all_dialogs(cx);
|
||||
}));
|
||||
|
||||
subscriptions.push(cx.subscribe(&wot, |this, _wot, _event, cx| {
|
||||
this.check_wot(cx);
|
||||
subscriptions.push(cx.subscribe(&wot, |_this, _wot, _event, cx| {
|
||||
cx.notify();
|
||||
}));
|
||||
|
||||
directory.update(cx, |directory, cx| {
|
||||
@@ -63,7 +63,7 @@ impl Screening {
|
||||
|
||||
cx.defer_in(window, |this, _window, cx| {
|
||||
this.check_contact(cx);
|
||||
this.check_wot(cx);
|
||||
this.fetch_fallback(cx);
|
||||
this.check_last_activity(cx);
|
||||
this.verify_identifier(cx);
|
||||
});
|
||||
@@ -121,27 +121,17 @@ impl Screening {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Check mutual contacts through the web of trust,
|
||||
/// falling back to the database while it loads
|
||||
fn check_wot(&mut self, cx: &mut Context<Self>) {
|
||||
let trusted = {
|
||||
let wot = WebOfTrust::global(cx).read(cx);
|
||||
wot.is_loaded().then(|| wot.mutuals(&self.public_key))
|
||||
};
|
||||
/// Fetch mutual contacts from the database while the web of trust loads
|
||||
fn fetch_fallback(&mut self, cx: &mut Context<Self>) {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
|
||||
if let Some(contacts) = trusted {
|
||||
self.mutual_contacts = contacts;
|
||||
cx.notify();
|
||||
return;
|
||||
}
|
||||
|
||||
if self.mutuals_fallback {
|
||||
if self.mutuals_fallback || persons.read(cx).wot().read(cx).is_loaded() {
|
||||
return;
|
||||
}
|
||||
self.mutuals_fallback = true;
|
||||
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
let public_key = self.public_key;
|
||||
|
||||
let Some(current_user) = nostr.read(cx).current_user() else {
|
||||
@@ -166,11 +156,9 @@ impl Screening {
|
||||
let contacts = task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
// The web of trust wins once its load has finished
|
||||
if WebOfTrust::global(cx).read(cx).is_loaded() {
|
||||
if persons.read(cx).wot().read(cx).is_loaded() {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mutual_contacts = contacts;
|
||||
cx.notify();
|
||||
})
|
||||
@@ -292,8 +280,12 @@ impl Screening {
|
||||
}));
|
||||
}
|
||||
|
||||
fn mutual_contacts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let contacts = self.mutual_contacts.clone();
|
||||
fn mutual_contacts(
|
||||
&mut self,
|
||||
contacts: Vec<PublicKey>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let directory = self.directory.clone();
|
||||
|
||||
window.open_dialog(cx, move |this, _window, _cx| {
|
||||
@@ -346,11 +338,23 @@ impl Render for Screening {
|
||||
const NIP05_NOT_MATCH: &str = "The address does not match the user's public key.";
|
||||
const NO_NIP05: &str = "This person has not set up their friendly address";
|
||||
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = self.profile(cx);
|
||||
let shorten_pubkey = shorten_pubkey(self.public_key, 8);
|
||||
|
||||
let last_active = self.last_active.map(|_| true);
|
||||
let mutuals = self.mutual_contacts.len();
|
||||
|
||||
// Pull from the web of trust once loaded, else use the database fallback
|
||||
let mutual_contacts = {
|
||||
let wot = persons.read(cx).wot().read(cx);
|
||||
if wot.is_loaded() {
|
||||
wot.mutuals(&self.public_key)
|
||||
} else {
|
||||
self.mutual_contacts.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let mutuals = mutual_contacts.len();
|
||||
let mutuals_str = format!("You have {} mutual contacts with this person.", mutuals);
|
||||
|
||||
v_flex()
|
||||
@@ -543,7 +547,11 @@ impl Render for Screening {
|
||||
.rounded()
|
||||
.disabled(mutuals == 0)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.mutual_contacts(window, cx);
|
||||
this.mutual_contacts(
|
||||
mutual_contacts.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user