use tokio::sync::broadcast::{Receiver, Sender}; use crate::{ config::Config, models::{CodeChallengeMethod, CodeVerifier}, oauth, }; #[derive(Debug, PartialEq)] pub enum State { Init, AppStart { config: Config, }, Authenticate { open_browser: bool, code_verifier: CodeVerifier, code_challenge_method: CodeChallengeMethod, }, Connect { token: String, }, WaitForSim, Running, } #[derive(Debug, Clone, PartialEq)] pub enum Event { Ready { config: Config, }, StartAuthenticate { open_browser: bool, code_verifier: CodeVerifier, code_challenge_method: CodeChallengeMethod, }, // should not be string TokenReceived { token: String, }, // AppStart and Authenticate can fire off TokenReceived to transition into Connect Connected, // Once connected to the socket, and properly authenticated, fire off Connected to transition to WaitForSim Disconnected, // If for whatever reason we're disconnected from the backend, we need to transition back to Connect SimConnected, // SimConnect is connected, we're in the world and ready to send data, transition to Running SimDisconnected, // SimConnect is disconnected, we've finished the flight and exited back to the menu, transition back to WaitForSim Quit, } impl State { pub async fn next(self, event: Event) -> State { match (self, event) { // (Current State, SomeEvent) => NextState (_, Event::Ready { config }) => State::AppStart { config }, ( State::AppStart { .. }, Event::StartAuthenticate { open_browser, code_verifier, code_challenge_method, }, ) => Self::Authenticate { open_browser, code_verifier, code_challenge_method, }, // Goto Authenticate (State::AppStart { .. }, Event::TokenReceived { token }) => State::Connect { token }, (State::Authenticate { .. }, Event::TokenReceived { token }) => { State::Connect { token } } (State::Connect { .. }, Event::Connected) => todo!(), // Goto WaitForSim (State::WaitForSim, Event::SimConnected) => todo!(), // Goto Running (State::Running, Event::Disconnected) => todo!(), // Goto Connect (State::Running, Event::SimDisconnected) => todo!(), // Goto WaitForSim (_, Event::Quit) => todo!(), // All events can go into quit, to shutdown the application _ => panic!("Invalid state transition"), } } pub async fn run(&self, signal: Sender) -> Result<(), anyhow::Error> { match self { State::Init => Ok(()), State::AppStart { config } => { if let Some(token) = config.token() { signal.send(Event::TokenReceived { token: token.to_string(), })?; } else { let open_browser = config.open_browser(); let code_verifier = CodeVerifier::new(); let code_challenge_method = CodeChallengeMethod::Sha256; config.set_code_verifier(Some(code_verifier.clone()))?; config.set_code_challenge_method(Some(code_challenge_method.clone()))?; signal.send(Event::StartAuthenticate { open_browser, code_verifier, code_challenge_method, })?; } Ok(()) } State::Authenticate { open_browser, code_verifier, code_challenge_method, } => { if *open_browser { oauth::open_browser(code_verifier.clone(), code_challenge_method.clone())?; } Ok(()) } State::Connect { token } => { println!("Holyshit we've got a token: {}", token); Ok(()) } State::WaitForSim => Ok(()), State::Running => Ok(()), } } } pub async fn start( config: Config, event_sender: Sender, mut event_receiver: Receiver, ) -> Result<(), anyhow::Error> { let mut state = State::Init; state.run(event_sender.clone()).await?; loop { if let Ok(event) = event_receiver.recv().await { if event == Event::Quit { tracing::info!("Shutting down State Machine"); break; } state = state.next(event).await; // before run if let State::Connect { token } = &state { // before run Connect, save the given token in config config.set_token(Some(token.clone()))?; } state.run(event_sender.clone()).await?; } } tracing::info!("State Machine Shutdown"); Ok(()) }