websocket protocol
This commit is contained in:
@@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
avam-protocol = { path = "../avam-protocol" }
|
||||
anyhow = { version = "1.0" }
|
||||
base64 = { version = "0.22.1", default-features = false }
|
||||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
|
@@ -1,90 +1,180 @@
|
||||
use std::{borrow::Cow, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use avam_protocol::{Packet, Packets, SimConnectPacket, SystemPacket};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use reqwest::StatusCode;
|
||||
use tokio::{
|
||||
sync::broadcast::{Receiver, Sender},
|
||||
time::sleep,
|
||||
sync::broadcast::{channel, Receiver, Sender},
|
||||
task::JoinSet,
|
||||
time::{sleep, timeout},
|
||||
};
|
||||
use tokio_tungstenite::{
|
||||
connect_async,
|
||||
tungstenite::{
|
||||
self,
|
||||
protocol::{frame::coding::CloseCode, CloseFrame},
|
||||
ClientRequestBuilder,
|
||||
ClientRequestBuilder, Message,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{state_machine::Event, BASE_URL};
|
||||
use crate::{config::Config, state_machine::Event, BASE_URL};
|
||||
|
||||
pub async fn start(
|
||||
config: Config,
|
||||
simconnect_sender: Sender<SimConnectPacket>,
|
||||
socket_receiver: Receiver<Packets>,
|
||||
event_sender: Sender<Event>,
|
||||
mut event_receiver: Receiver<Event>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let mut writer = None;
|
||||
|
||||
let uri: tungstenite::http::Uri = format!("{}/ws", BASE_URL.replace("https", "wss")).parse()?;
|
||||
|
||||
let mut futures = JoinSet::new();
|
||||
|
||||
let (ia_sender, ia_receiver) = channel(10);
|
||||
loop {
|
||||
if let Ok(event) = &event_receiver.try_recv() {
|
||||
match event {
|
||||
Event::TokenReceived { token } => {
|
||||
Event::TokenReceived => {
|
||||
let Some(token) = config.token() else {
|
||||
let _ = event_sender.send(Event::Logout);
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
let builder = ClientRequestBuilder::new(uri.clone())
|
||||
.with_header("Authorization", format!("Bearer {}", token));
|
||||
|
||||
let (socket, response) = connect_async(builder).await?;
|
||||
tracing::info!("Connecting");
|
||||
let Ok(Ok((socket, response))) =
|
||||
timeout(Duration::from_secs(10), connect_async(builder)).await
|
||||
else {
|
||||
tracing::error!("Unable to connect");
|
||||
let _ = event_sender.send(Event::Disconnected);
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
if response.status() != StatusCode::SWITCHING_PROTOCOLS {
|
||||
tracing::error!("{:#?}", response);
|
||||
tracing::error!("Unable to connect: {:#?}", response);
|
||||
let _ = event_sender.send(Event::Disconnected);
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
let (write, mut read) = socket.split();
|
||||
writer = Some(write);
|
||||
let (mut write, mut read) = socket.split();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let message = match read.next().await {
|
||||
Some(data) => match data {
|
||||
Ok(message) => message,
|
||||
Err(e) => {
|
||||
tracing::error!("{:?}", e);
|
||||
return;
|
||||
let mut ia_receiver: Receiver<Packets> = ia_receiver.resubscribe();
|
||||
futures.spawn(async move {
|
||||
loop {
|
||||
if let Ok(d) = ia_receiver.try_recv() {
|
||||
let message = match d {
|
||||
Packets::System(SystemPacket::Close { reason }) => {
|
||||
Message::Close(Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: reason.into(),
|
||||
}))
|
||||
}
|
||||
Packets::System(SystemPacket::Pong) => {
|
||||
let Ok(encoded_message) = d.encode() else {
|
||||
tracing::error!(
|
||||
"Unable to encode message for sending: {:#?}",
|
||||
d
|
||||
);
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
continue;
|
||||
};
|
||||
Message::Binary(encoded_message)
|
||||
}
|
||||
d => {
|
||||
tracing::info!("sending packet: {:?}", &d);
|
||||
let Ok(encoded_message) = d.encode() else {
|
||||
tracing::error!(
|
||||
"Unable to encode message for sending: {:#?}",
|
||||
d
|
||||
);
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
continue;
|
||||
};
|
||||
Message::Binary(encoded_message)
|
||||
}
|
||||
};
|
||||
|
||||
match write.send(message).await {
|
||||
Err(tungstenite::Error::AlreadyClosed) => break,
|
||||
Err(e) => {
|
||||
tracing::error!("Error writing to socket: {:?}", e);
|
||||
break;
|
||||
}
|
||||
Ok(()) => {}
|
||||
};
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
});
|
||||
|
||||
let ias = ia_sender.clone();
|
||||
let es = event_sender.clone();
|
||||
let scs = simconnect_sender.clone();
|
||||
futures.spawn(async move {
|
||||
loop {
|
||||
let message = match read.next().await {
|
||||
Some(data) => match data {
|
||||
Ok(message) => message,
|
||||
Err(e) => {
|
||||
tracing::error!("{:?}", e);
|
||||
let _ = es.send(Event::Disconnected);
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
None => break,
|
||||
};
|
||||
|
||||
if let Ok(data) = Packets::decode(&message.into_data()) {
|
||||
if data == Packets::System(SystemPacket::Ping) {
|
||||
let _ = ias.send(Packets::System(SystemPacket::Pong));
|
||||
continue;
|
||||
}
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
|
||||
let data = message.to_text();
|
||||
tracing::debug!("{:?}", data);
|
||||
// From Socket -> SimConnect
|
||||
if let Packets::SimConnect(sim_connect_packet) = data {
|
||||
tracing::info!("packet received: {:?}", &sim_connect_packet);
|
||||
let _ = scs.send(sim_connect_packet);
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
});
|
||||
|
||||
// Data from simconnect -> Socket
|
||||
let mut socket_receiver = socket_receiver.resubscribe();
|
||||
let ias = ia_sender.clone();
|
||||
futures.spawn(async move {
|
||||
loop {
|
||||
if let Ok(message) = socket_receiver.try_recv() {
|
||||
let _ = ias.send(message);
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
});
|
||||
|
||||
tracing::info!("Connected");
|
||||
let _ = event_sender.send(Event::Connected);
|
||||
}
|
||||
Event::Logout => {
|
||||
if let Some(mut write) = writer {
|
||||
write
|
||||
.send(tungstenite::Message::Close(Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: Cow::from("User Logout"),
|
||||
})))
|
||||
.await?;
|
||||
writer = None;
|
||||
tracing::debug!("Disconnected");
|
||||
event_sender.send(Event::Disconnected)?;
|
||||
}
|
||||
let _ = ia_sender.send(Packets::System(SystemPacket::Close {
|
||||
reason: "User Logout".to_string(),
|
||||
}));
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
futures.abort_all();
|
||||
}
|
||||
Event::Quit => {
|
||||
tracing::info!("Shutting down Client");
|
||||
if let Some(mut write) = writer {
|
||||
write
|
||||
.send(tungstenite::Message::Close(Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: Cow::from("Application Shutdown"),
|
||||
})))
|
||||
.await?;
|
||||
tracing::debug!("Disconnected");
|
||||
}
|
||||
let _ = ia_sender.send(Packets::System(SystemPacket::Close {
|
||||
reason: "Quit".to_string(),
|
||||
}));
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
futures.abort_all();
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
|
@@ -18,7 +18,7 @@ use lock::Lock;
|
||||
use oauth::{start_code_listener, start_code_to_token};
|
||||
use pipe::Pipe;
|
||||
use state_machine::Event;
|
||||
use tokio::task::JoinSet;
|
||||
use tokio::{sync::broadcast::channel, task::JoinSet};
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
||||
|
||||
@@ -44,8 +44,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
|
||||
init_logging()?;
|
||||
|
||||
// let (socket_sender, socket_receiver) = tokio::sync::broadcast::channel(1);
|
||||
let (event_sender, event_receiver) = tokio::sync::broadcast::channel(10);
|
||||
let (event_sender, event_receiver) = channel(10);
|
||||
let args = Arguments::parse();
|
||||
|
||||
if handle_single_instance(&args).await? {
|
||||
@@ -93,14 +92,21 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
let receiver = event_receiver.resubscribe();
|
||||
futures.spawn(start_code_to_token(c, pipe_receiver, sender, receiver));
|
||||
|
||||
// Prepare channels for socket <-> simconnect
|
||||
let (simconnect_sender, __simconnect_receiver) = channel(10);
|
||||
let (__socket_sender, socket_receiver) = channel(10);
|
||||
|
||||
// Start the websocket client
|
||||
// The socket client will just sit there until TokenReceivedEvent comes in to authenticate with the socket server
|
||||
// The server needs to not accept any messages until the authentication is verified
|
||||
let c = config.clone();
|
||||
let sender = event_sender.clone();
|
||||
let receiver = event_receiver.resubscribe();
|
||||
futures.spawn(client::start(sender, receiver));
|
||||
|
||||
// We need 2 way channels (2 channels, both with tx/rx) to send data from the socket to simconnect and back
|
||||
futures.spawn(client::start(
|
||||
c,
|
||||
simconnect_sender,
|
||||
socket_receiver,
|
||||
sender,
|
||||
receiver,
|
||||
));
|
||||
|
||||
// Start the simconnect listener
|
||||
// The simconnect sends data to the webscoket
|
||||
|
@@ -106,7 +106,7 @@ pub async fn start_code_to_token(
|
||||
|
||||
config.set_token(Some(token.clone()))?;
|
||||
|
||||
event_sender.send(Event::TokenReceived { token })?;
|
||||
event_sender.send(Event::TokenReceived)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
pub struct Client {
|
||||
pub struct SimConnect {
|
||||
// whatever we need
|
||||
}
|
||||
|
||||
impl Client {
|
||||
impl SimConnect {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
// websocket receiver
|
||||
@@ -25,5 +25,5 @@ impl Client {
|
||||
}
|
||||
|
||||
pub async fn start() -> Result<(), anyhow::Error> {
|
||||
Client::new().run().await?
|
||||
SimConnect::new().run().await?
|
||||
}
|
||||
|
@@ -5,18 +5,15 @@ use tokio::{
|
||||
time::sleep,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
models::{CodeChallengeMethod, CodeVerifier},
|
||||
oauth,
|
||||
};
|
||||
use crate::{config::Config, oauth};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum State {
|
||||
Init,
|
||||
AppStart,
|
||||
Shutdown,
|
||||
Authenticate,
|
||||
Connect { token: String },
|
||||
Connect,
|
||||
WaitForSim,
|
||||
InSim,
|
||||
}
|
||||
@@ -24,8 +21,8 @@ pub enum State {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Event {
|
||||
Ready,
|
||||
StartAuthenticate, // should not be string
|
||||
TokenReceived { token: String }, // AppStart and Authenticate can fire off TokenReceived to transition into Connect
|
||||
StartAuthenticate, // should not be string
|
||||
TokenReceived, // 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
|
||||
@@ -42,16 +39,20 @@ impl State {
|
||||
(_, Event::Logout) => State::AppStart,
|
||||
(_, Event::StartAuthenticate) => Self::Authenticate, // Goto Authenticate
|
||||
|
||||
(_, Event::TokenReceived { token }) => State::Connect { token },
|
||||
(_, Event::TokenReceived) => State::Connect,
|
||||
|
||||
(_, Event::Connected) => State::WaitForSim, // Goto WaitForSim
|
||||
|
||||
(_, Event::SimConnected) => todo!(), // Goto InSim
|
||||
(_, Event::Disconnected) => {
|
||||
sleep(Duration::from_secs(5)).await; // wait 5 seconds before reconnecting
|
||||
tracing::info!("Attempting reconnect");
|
||||
State::AppStart // Goto Connect
|
||||
}
|
||||
|
||||
(_, Event::Disconnected) => State::AppStart, // Goto Connect
|
||||
(_, Event::SimConnected) => State::InSim, // Goto InSim
|
||||
(_, Event::SimDisconnected) => State::WaitForSim, // Goto WaitForSim
|
||||
|
||||
(_, Event::Quit) => todo!(), // All events can go into quit, to shutdown the application
|
||||
(_, Event::Quit) => State::Shutdown, // All events can go into quit, to shutdown the application
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +60,8 @@ impl State {
|
||||
match self {
|
||||
State::Init => Ok(()),
|
||||
State::AppStart => {
|
||||
if let Some(token) = config.token() {
|
||||
signal.send(Event::TokenReceived {
|
||||
token: token.to_string(),
|
||||
})?;
|
||||
if config.token().is_some() {
|
||||
signal.send(Event::TokenReceived)?;
|
||||
} else {
|
||||
signal.send(Event::StartAuthenticate)?;
|
||||
}
|
||||
@@ -75,12 +74,10 @@ impl State {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
State::Connect { .. } => Ok(()),
|
||||
State::WaitForSim => {
|
||||
tracing::info!("Waiting for sim!");
|
||||
Ok(())
|
||||
}
|
||||
State::Connect => Ok(()),
|
||||
State::WaitForSim => Ok(()),
|
||||
State::InSim => Ok(()),
|
||||
State::Shutdown => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user