onboarding.rs (4674B)
1 use std::{cell::RefCell, rc::Rc}; 2 3 use egui_virtual_list::VirtualList; 4 use enostr::{Pubkey, RelayPool}; 5 use nostrdb::{Filter, Ndb, NoteKey, Transaction}; 6 use notedeck::{create_nip51_set, filter::default_limit, Nip51SetCache, UnknownIds}; 7 use uuid::Uuid; 8 9 use crate::subscriptions::Subscriptions; 10 11 #[derive(Debug)] 12 enum OnboardingState { 13 AwaitingTrustedPksList(Vec<Filter>), 14 HaveFollowPacks(Nip51SetCache), 15 } 16 17 /// Manages the onboarding process. Responsible for retriving the kind 30000 list of trusted pubkeys 18 /// and then retrieving all follow packs from the trusted pks updating when new ones arrive 19 #[derive(Default)] 20 pub struct Onboarding { 21 state: Option<Result<OnboardingState, OnboardingError>>, 22 pub list: Rc<RefCell<VirtualList>>, 23 } 24 25 impl Onboarding { 26 pub fn get_follow_packs(&self) -> Option<&Nip51SetCache> { 27 let Some(Ok(OnboardingState::HaveFollowPacks(packs))) = &self.state else { 28 return None; 29 }; 30 31 Some(packs) 32 } 33 34 pub fn get_follow_packs_mut(&mut self) -> Option<&mut Nip51SetCache> { 35 let Some(Ok(OnboardingState::HaveFollowPacks(packs))) = &mut self.state else { 36 return None; 37 }; 38 39 Some(packs) 40 } 41 42 pub fn process( 43 &mut self, 44 pool: &mut RelayPool, 45 ndb: &Ndb, 46 subs: &mut Subscriptions, 47 unknown_ids: &mut UnknownIds, 48 ) { 49 match &self.state { 50 Some(res) => { 51 let Ok(OnboardingState::AwaitingTrustedPksList(filter)) = res else { 52 return; 53 }; 54 55 let txn = Transaction::new(ndb).expect("txns"); 56 let Ok(res) = ndb.query(&txn, filter, 1) else { 57 return; 58 }; 59 60 if res.is_empty() { 61 return; 62 } 63 64 let key = res.first().expect("checked empty").note_key; 65 66 let new_state = get_trusted_authors(ndb, &txn, key).and_then(|trusted_pks| { 67 let pks: Vec<&[u8; 32]> = trusted_pks.iter().map(|f| f.bytes()).collect(); 68 Nip51SetCache::new(pool, ndb, &txn, unknown_ids, vec![follow_packs_filter(pks)]) 69 .map(OnboardingState::HaveFollowPacks) 70 .ok_or(OnboardingError::InvalidNip51Set) 71 }); 72 73 self.state = Some(new_state); 74 } 75 None => { 76 let filter = vec![trusted_pks_list_filter()]; 77 78 let subid = Uuid::new_v4().to_string(); 79 pool.subscribe(subid.clone(), filter.clone()); 80 subs.subs 81 .insert(subid, crate::subscriptions::SubKind::OneShot); 82 83 let new_state = Some(Ok(OnboardingState::AwaitingTrustedPksList(filter))); 84 self.state = new_state; 85 } 86 } 87 } 88 89 // Unsubscribe and clear state 90 pub fn end_onboarding(&mut self, pool: &mut RelayPool, ndb: &mut Ndb) { 91 let Some(Ok(OnboardingState::HaveFollowPacks(state))) = &mut self.state else { 92 self.state = None; 93 return; 94 }; 95 96 let unified = &state.sub; 97 98 pool.unsubscribe(unified.remote.clone()); 99 let _ = ndb.unsubscribe(unified.local); 100 101 self.state = None; 102 } 103 } 104 105 #[derive(Debug)] 106 pub enum OnboardingError { 107 InvalidNip51Set, 108 InvalidTrustedPksListKind, 109 NdbCouldNotFindNote, 110 } 111 112 // author providing the list of trusted follow pack authors 113 const FOLLOW_PACK_AUTHOR: [u8; 32] = [ 114 0x89, 0x5c, 0x2a, 0x90, 0xa8, 0x60, 0xac, 0x18, 0x43, 0x4a, 0xa6, 0x9e, 0x7b, 0x0d, 0xa8, 0x46, 115 0x57, 0x21, 0x21, 0x6f, 0xa3, 0x6e, 0x42, 0xc0, 0x22, 0xe3, 0x93, 0x57, 0x9c, 0x48, 0x6c, 0xba, 116 ]; 117 118 fn trusted_pks_list_filter() -> Filter { 119 Filter::new() 120 .kinds([30000]) 121 .limit(1) 122 .authors(&[FOLLOW_PACK_AUTHOR]) 123 .tags(["trusted-follow-pack-authors"], 'd') 124 .build() 125 } 126 127 pub fn follow_packs_filter(pks: Vec<&[u8; 32]>) -> Filter { 128 Filter::new() 129 .kinds([39089]) 130 .limit(default_limit()) 131 .authors(pks) 132 .build() 133 } 134 135 /// gets the pubkeys from a kind 30000 follow set 136 fn get_trusted_authors( 137 ndb: &Ndb, 138 txn: &Transaction, 139 key: NoteKey, 140 ) -> Result<Vec<Pubkey>, OnboardingError> { 141 let Ok(note) = ndb.get_note_by_key(txn, key) else { 142 return Result::Err(OnboardingError::NdbCouldNotFindNote); 143 }; 144 145 if note.kind() != 30000 { 146 return Result::Err(OnboardingError::InvalidTrustedPksListKind); 147 } 148 149 let Some(nip51set) = create_nip51_set(note) else { 150 return Result::Err(OnboardingError::InvalidNip51Set); 151 }; 152 153 Ok(nip51set.pks) 154 }