From e237328ec611a5891586530c1d3cb26c16c1093b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Sun, 1 Oct 2023 00:00:00 +0100 Subject: feat(login) fetch user relays and metadata get user relay list and metadata events from relays when keys are used and last fetch attempt was more than an hour ago uses user's write relays if known, otherwise uses fallback relays to achieve this a method for intergration testing event fetching from relays was added --- test_utils/Cargo.toml | 1 + test_utils/src/lib.rs | 62 ++++++++++++++++++++++++ test_utils/src/relay.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 180 insertions(+), 9 deletions(-) (limited to 'test_utils') diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 2d3555b..6a3fff8 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -15,3 +15,4 @@ rand = "0.8" rexpect = { git = "https://github.com/phaer/rexpect.git", branch= "skip-ansi-escape-codes" } simple-websockets = "0.1.6" strip-ansi-escapes = "0.2.0" +tungstenite = "0.20.1" diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index a9d818c..2a06357 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -23,6 +23,39 @@ pub static TEST_KEY_1_ENCRYPTED_WEAK: &str = "ncryptsec1qy8ke0tjqnn8wt3w6lnc86c2 pub static TEST_KEY_1_KEYS: Lazy = Lazy::new(|| nostr::Keys::from_sk_str(TEST_KEY_1_NSEC).unwrap()); +pub fn generate_test_key_1_metadata_event(name: &str) -> nostr::Event { + nostr::event::EventBuilder::set_metadata(nostr::Metadata::new().name(name)) + .to_event(&TEST_KEY_1_KEYS) + .unwrap() +} +pub fn generate_test_key_1_metadata_event_old(name: &str) -> nostr::Event { + make_event_old_or_change_user( + generate_test_key_1_metadata_event(name), + &TEST_KEY_1_KEYS, + 10000, + ) +} + +pub fn generate_test_key_1_relay_list_event() -> nostr::Event { + nostr::event::EventBuilder::new( + nostr::Kind::RelayList, + "", + &[ + nostr::Tag::RelayMetadata( + "ws://localhost:8053".into(), + Some(nostr::RelayMetadata::Write), + ), + nostr::Tag::RelayMetadata( + "ws://localhost:8054".into(), + Some(nostr::RelayMetadata::Read), + ), + nostr::Tag::RelayMetadata("ws://localhost:8055".into(), None), + ], + ) + .to_event(&TEST_KEY_1_KEYS) + .unwrap() +} + pub static TEST_KEY_2_NSEC: &str = "nsec1ypglg6nj6ep0g2qmyfqcv2al502gje3jvpwye6mthmkvj93tqkesknv6qm"; pub static TEST_KEY_2_NPUB: &str = @@ -33,12 +66,41 @@ pub static TEST_KEY_2_ENCRYPTED: &str = "...2"; pub static TEST_KEY_2_KEYS: Lazy = Lazy::new(|| nostr::Keys::from_sk_str(TEST_KEY_2_NSEC).unwrap()); +pub fn generate_test_key_2_metadata_event(name: &str) -> nostr::Event { + nostr::event::EventBuilder::set_metadata(nostr::Metadata::new().name(name)) + .to_event(&TEST_KEY_2_KEYS) + .unwrap() +} + pub static TEST_INVALID_NSEC: &str = "nsec1ppsg5sm2aex"; pub static TEST_PASSWORD: &str = "769dfd£pwega8SHGv3!#Bsfd5t"; pub static TEST_INVALID_PASSWORD: &str = "INVALID769dfd£pwega8SHGv3!"; pub static TEST_WEAK_PASSWORD: &str = "fhaiuhfwe"; pub static TEST_RANDOM_TOKEN: &str = "lkjh2398HLKJ43hrweiJ6FaPfdssgtrg"; +pub fn make_event_old_or_change_user( + event: nostr::Event, + keys: &nostr::Keys, + how_old_in_secs: u64, +) -> nostr::Event { + let mut unsigned = nostr::event::EventBuilder::new(event.kind, event.content, &event.tags) + .to_unsigned_event(keys.public_key()); + + unsigned.created_at = nostr::types::Timestamp::try_from( + nostr::types::Timestamp::now().as_u64() - how_old_in_secs, + ) + .unwrap(); + unsigned.id = nostr::EventId::new( + &keys.public_key(), + unsigned.created_at, + &unsigned.kind, + &unsigned.tags, + &unsigned.content, + ); + + unsigned.sign(keys).unwrap() +} + /// wrapper for a cli testing tool - currently wraps rexpect and dialoguer /// /// 1. allow more accurate articulation of expected behaviour diff --git a/test_utils/src/relay.rs b/test_utils/src/relay.rs index 6de3618..ce618a3 100644 --- a/test_utils/src/relay.rs +++ b/test_utils/src/relay.rs @@ -5,26 +5,36 @@ use nostr::{ClientMessage, RelayMessage}; use crate::CliTester; -type ListenerFunc<'a> = &'a dyn Fn(&mut Relay, u64, nostr::Event) -> Result<()>; +type ListenerEventFunc<'a> = &'a dyn Fn(&mut Relay, u64, nostr::Event) -> Result<()>; +pub type ListenerReqFunc<'a> = + &'a dyn Fn(&mut Relay, u64, nostr::SubscriptionId, Vec) -> Result<()>; pub struct Relay<'a> { port: u16, event_hub: simple_websockets::EventHub, clients: HashMap, pub events: Vec, - event_listener: Option>, + pub reqs: Vec>, + event_listener: Option>, + req_listener: Option>, } impl<'a> Relay<'a> { - pub fn new(port: u16, event_listener: Option>) -> Self { + pub fn new( + port: u16, + event_listener: Option>, + req_listener: Option>, + ) -> Self { let event_hub = simple_websockets::launch(port) .unwrap_or_else(|_| panic!("failed to listen on port {port}")); Self { port, events: vec![], + reqs: vec![], event_hub, clients: HashMap::new(), event_listener, + req_listener, } } pub fn respond_ok( @@ -44,11 +54,54 @@ impl<'a> Relay<'a> { // bail!(format!("{}", &ok_json)); Ok(responder.send(simple_websockets::Message::Text(ok_json))) } + + pub fn respond_eose( + &self, + client_id: u64, + subscription_id: nostr::SubscriptionId, + ) -> Result { + let responder = self.clients.get(&client_id).unwrap(); + + Ok(responder.send(simple_websockets::Message::Text( + RelayMessage::EndOfStoredEvents(subscription_id).as_json(), + ))) + } + + /// send events and eose + pub fn respond_events( + &self, + client_id: u64, + subscription_id: &nostr::SubscriptionId, + events: &Vec, + ) -> Result { + let responder = self.clients.get(&client_id).unwrap(); + + for event in events { + let res = responder.send(simple_websockets::Message::Text( + RelayMessage::Event { + subscription_id: subscription_id.clone(), + event: Box::new(event.clone()), + } + .as_json(), + )); + if !res { + return Ok(false); + } + } + self.respond_eose(client_id, subscription_id.clone()) + } + + pub fn shutdown(&mut self) -> Result<()> { + let (mut socket, _) = tungstenite::connect(format!("ws://localhost:{}", self.port))?; + socket.write(tungstenite::Message::text("shut me down"))?; + socket.close(None)?; + Ok(()) + } /// listen, collect events and responds with event_listener to events or /// Ok(eventid) if event_listner is None pub async fn listen_until_close(&mut self) -> Result<()> { loop { - println!("polling"); + println!("{} polling", self.port); match self.event_hub.poll_async().await { simple_websockets::Event::Connect(client_id, responder) => { // add their Responder to our `clients` map: @@ -65,8 +118,13 @@ impl<'a> Relay<'a> { "Received a message from client #{}: {:?}", client_id, message ); - - if let Ok(event) = get_nevent(message) { + if let simple_websockets::Message::Text(s) = message.clone() { + if s.eq("shut me down") { + println!("{} recieved shut me down", self.port); + break; + } + } + if let Ok(event) = get_nevent(&message) { self.events.push(event.clone()); if let Some(listner) = self.event_listener { listner(self, client_id, event)?; @@ -74,16 +132,40 @@ impl<'a> Relay<'a> { self.respond_ok(client_id, event, None)?; } } + + if let Ok((subscription_id, filters)) = get_nreq(&message) { + self.reqs.push(filters.clone()); + if let Some(listner) = self.req_listener { + listner(self, client_id, subscription_id, filters)?; + } else { + self.respond_eose(client_id, subscription_id)?; + } + // respond with events + // respond with EOSE + } + if is_nclose(&message) { + println!("{} recieved nostr close", self.port); + break; + } } } } - println!("stop polling"); - println!("we may not be polling but the tcplistner is still listening"); + println!( + "{} stop polling. we may not be polling but the tcplistner is still listening", + self.port + ); Ok(()) } } -fn get_nevent(message: simple_websockets::Message) -> Result { +pub fn shutdown_relay(port: u64) -> Result<()> { + let (mut socket, _) = tungstenite::connect(format!("ws://localhost:{}", port))?; + socket.write(tungstenite::Message::text("shut me down"))?; + socket.close(None)?; + Ok(()) +} + +fn get_nevent(message: &simple_websockets::Message) -> Result { if let simple_websockets::Message::Text(s) = message.clone() { let cm_result = ClientMessage::from_json(s); if let Ok(ClientMessage::Event(event)) = cm_result { @@ -94,6 +176,32 @@ fn get_nevent(message: simple_websockets::Message) -> Result { bail!("not nostr event") } +fn get_nreq( + message: &simple_websockets::Message, +) -> Result<(nostr::SubscriptionId, Vec)> { + if let simple_websockets::Message::Text(s) = message.clone() { + let cm_result = ClientMessage::from_json(s); + if let Ok(ClientMessage::Req { + subscription_id, + filters, + }) = cm_result + { + return Ok((subscription_id, filters)); + } + } + bail!("not nostr event") +} + +fn is_nclose(message: &simple_websockets::Message) -> bool { + if let simple_websockets::Message::Text(s) = message.clone() { + let cm_result = ClientMessage::from_json(s); + if let Ok(ClientMessage::Close(_)) = cm_result { + return true; + } + } + false +} + pub enum Message { Event, // Request, -- cgit v1.2.3