use std::{cell::RefCell, rc::Rc}; use egui_virtual_list::VirtualList; use enostr::{Pubkey, RelayPool}; use nostrdb::{Filter, Ndb, NoteKey, Transaction}; use notedeck::{create_nip51_set, filter::default_limit, Nip51SetCache, UnknownIds}; use uuid::Uuid; use crate::subscriptions::Subscriptions; #[derive(Debug)] enum OnboardingState { AwaitingTrustedPksList(Vec), HaveFollowPacks(Nip51SetCache), } /// Manages the onboarding process. Responsible for retriving the kind 30000 list of trusted pubkeys /// and then retrieving all follow packs from the trusted pks updating when new ones arrive #[derive(Default)] pub struct Onboarding { state: Option>, pub list: Rc>, } impl Onboarding { pub fn get_follow_packs(&self) -> Option<&Nip51SetCache> { let Some(Ok(OnboardingState::HaveFollowPacks(packs))) = &self.state else { return None; }; Some(packs) } pub fn get_follow_packs_mut(&mut self) -> Option<&mut Nip51SetCache> { let Some(Ok(OnboardingState::HaveFollowPacks(packs))) = &mut self.state else { return None; }; Some(packs) } pub fn process( &mut self, pool: &mut RelayPool, ndb: &Ndb, subs: &mut Subscriptions, unknown_ids: &mut UnknownIds, ) { match &self.state { Some(res) => { let Ok(OnboardingState::AwaitingTrustedPksList(filter)) = res else { return; }; let txn = Transaction::new(ndb).expect("txns"); let Ok(res) = ndb.query(&txn, filter, 1) else { return; }; if res.is_empty() { return; } let key = res.first().expect("checked empty").note_key; let new_state = get_trusted_authors(ndb, &txn, key).and_then(|trusted_pks| { let pks: Vec<&[u8; 32]> = trusted_pks.iter().map(|f| f.bytes()).collect(); Nip51SetCache::new(pool, ndb, &txn, unknown_ids, vec![follow_packs_filter(pks)]) .map(OnboardingState::HaveFollowPacks) .ok_or(OnboardingError::InvalidNip51Set) }); self.state = Some(new_state); } None => { let filter = vec![trusted_pks_list_filter()]; let subid = Uuid::new_v4().to_string(); pool.subscribe(subid.clone(), filter.clone()); subs.subs .insert(subid, crate::subscriptions::SubKind::OneShot); let new_state = Some(Ok(OnboardingState::AwaitingTrustedPksList(filter))); self.state = new_state; } } } // Unsubscribe and clear state pub fn end_onboarding(&mut self, pool: &mut RelayPool, ndb: &mut Ndb) { let Some(Ok(OnboardingState::HaveFollowPacks(state))) = &mut self.state else { self.state = None; return; }; let unified = &state.sub; pool.unsubscribe(unified.remote.clone()); let _ = ndb.unsubscribe(unified.local); self.state = None; } } #[derive(Debug)] pub enum OnboardingError { InvalidNip51Set, InvalidTrustedPksListKind, NdbCouldNotFindNote, } // author providing the list of trusted follow pack authors const FOLLOW_PACK_AUTHOR: [u8; 32] = [ 0x89, 0x5c, 0x2a, 0x90, 0xa8, 0x60, 0xac, 0x18, 0x43, 0x4a, 0xa6, 0x9e, 0x7b, 0x0d, 0xa8, 0x46, 0x57, 0x21, 0x21, 0x6f, 0xa3, 0x6e, 0x42, 0xc0, 0x22, 0xe3, 0x93, 0x57, 0x9c, 0x48, 0x6c, 0xba, ]; fn trusted_pks_list_filter() -> Filter { Filter::new() .kinds([30000]) .limit(1) .authors(&[FOLLOW_PACK_AUTHOR]) .tags(["trusted-follow-pack-authors"], 'd') .build() } pub fn follow_packs_filter(pks: Vec<&[u8; 32]>) -> Filter { Filter::new() .kinds([39089]) .limit(default_limit()) .authors(pks) .build() } /// gets the pubkeys from a kind 30000 follow set fn get_trusted_authors( ndb: &Ndb, txn: &Transaction, key: NoteKey, ) -> Result, OnboardingError> { let Ok(note) = ndb.get_note_by_key(txn, key) else { return Result::Err(OnboardingError::NdbCouldNotFindNote); }; if note.kind() != 30000 { return Result::Err(OnboardingError::InvalidTrustedPksListKind); } let Some(nip51set) = create_nip51_set(note) else { return Result::Err(OnboardingError::InvalidNip51Set); }; Ok(nip51set.pks) }