use std::error::Error;
use futures::stream::{SplitSink, SplitStream};
use futures::StreamExt;
use log::{debug, error, info, Level, trace, warn};
use wasm_bindgen::prelude::*;
use ws_stream_wasm::{WsMessage, WsMeta, WsStream};
use protocol::State;
use protocol::PROTOCOL_VERSION;
use protocol::MessageS2C;
use protocol::MessageC2S;
use futures::SinkExt;
use lazy_static::lazy_static;
use std::sync::Arc;
use std::sync::RwLock;
use futures::FutureExt;
#[macro_use]
pub mod macros;
pub mod chat;
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub async fn rust_init(gateway: &str, username: &str) -> Result<(), JsError> {
console_log::init_with_level(Level::Debug).unwrap();
info!("Logger setup successfully");
match main(gateway, username).await {
Ok(c) => c,
Err(e) => {
error!("Error initializing gateway client: {}", e);
return Err(JsError::new(&e.to_string()));
}
};
info!("Gateway client set up successfully");
Ok(())
}
pub struct Client {
pub client_data: Option<ClientData>,
}
pub struct ClientData {
pub state: State,
pub tx: SplitSink<WsStream, WsMessage>,
pub rx: SplitStream<WsStream>
}
lazy_static! {
pub static ref CLIENT: Arc<RwLock<Client>> = Arc::new(RwLock::new(Client {
client_data: None
}));
}
pub async fn main(gateway: &str, username: &str) -> Result<(), Box<dyn Error>> {
info!("FAST CONNECT: {}", gateway);
let gateway_url = url::Url::parse(gateway)?;
trace!("Gateway URL parsed");
let (_ws, ws_stream) = WsMeta::connect(gateway_url, None).await?;
trace!("Connected to gateway socket");
let (tx, rx) = ws_stream.split();
let mut client_data = ClientData {
state: State::Handshake,
tx,
rx
};
trace!("Split stream, handshaking with server");
send!(client_data.tx, &MessageC2S::Hello {
next_state: State::Play,
version: PROTOCOL_VERSION,
requested_username: username.to_string()
}).await?;
trace!("Sent handshake start packet");
if let Some(msg) = recv_now!(client_data.rx)? {
let typed_msg: MessageS2C = msg;
match typed_msg {
MessageS2C::Hello { version, given_username, next_state } => {
info!("FAST CONNECT - connected to server protocol {} given username {}, switching to state {:?}", version, given_username, next_state);
client_data.state = next_state;
},
MessageS2C::Goodbye { reason } => {
error!("server disconnected before finishing handshake: {:?}", reason);
return Err(format!("disconnected by server: {:?}", reason).into());
},
_ => {
warn!("received unexpected packet from server: {:?}", typed_msg);
}
}
} else {
error!("Server closed the connection")
}
CLIENT.write()?.client_data = Some(client_data);
Ok(())
}
#[wasm_bindgen]
pub async fn update_socket() -> Result<(), JsError> {
let mut client = CLIENT.write()?;
if client.client_data.is_none() {
return Err(JsError::new("Client not yet initialized"));
}
let client_data = client.client_data.as_mut().unwrap();
let maybe_msg: Option<MessageS2C> = match recv!(client_data.rx) {
Ok(r) => r,
Err(e) => {
return Err(JsError::new(e))
}
};
if let Some(msg) = maybe_msg {
match msg {
MessageS2C::Goodbye { reason } => {
info!("server sent disconnect: {:?}", reason);
client.client_data = None;
return Err(JsError::new("disconnected by server"));
}
MessageS2C::Chat { from, message } => {
info!("[CHAT] {}: {}", from, message);
// TODO: Handle
},
_ => {
warn!("server sent unexpected packet {:?}, ignoring", msg);
}
}
}
Ok(())
}