diff --git a/back_end/Cargo.lock b/back_end/Cargo.lock index 829423c..c5d4c50 100644 --- a/back_end/Cargo.lock +++ b/back_end/Cargo.lock @@ -161,6 +161,8 @@ dependencies = [ "oorandom", "rand", "rusqlite", + "serde", + "serde_derive", "serde_json", "tokio", "warp", @@ -776,6 +778,20 @@ name = "serde" version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serde_json" diff --git a/back_end/Cargo.toml b/back_end/Cargo.toml index 0c7f4cc..64b2d70 100644 --- a/back_end/Cargo.toml +++ b/back_end/Cargo.toml @@ -14,3 +14,5 @@ rusqlite = "0.27.0" rand = "0.8.4" futures = "0.3" oorandom = "11.1.3" +serde = { version = "1.0", features = ["derive"] } +serde_derive = "1.0" diff --git a/back_end/src/main.rs b/back_end/src/main.rs index 04c86cf..b078ae5 100644 --- a/back_end/src/main.rs +++ b/back_end/src/main.rs @@ -43,19 +43,29 @@ async fn main() { let db_tx_clone = db_tx_clone.clone(); let s_helper_tx_clone = s_helper_tx.clone(); async move { - let body_str_result = dbg!(std::str::from_utf8(bytes.as_ref())); + let body_str_result = std::str::from_utf8(bytes.as_ref()); if let Ok(body_str) = body_str_result { let json_result = serde_json::from_str(body_str); if let Ok(json_value) = json_result { - Ok(warp::reply::json( - &json_handlers::handle_json(json_value, db_tx_clone, s_helper_tx_clone) + Ok(warp::reply::with_header( + json_handlers::handle_json(json_value, db_tx_clone, s_helper_tx_clone) .unwrap_or_else(|e| e), + "Content-Type", + "application/json", )) } else { - Ok(warp::reply::json(&String::from("{\"type\": \"invalid_syntax\"}"))) + Ok(warp::reply::with_header( + String::from("{\"type\": \"invalid_syntax\"}"), + "Content-Type", + "application/json", + )) } } else { - Ok::(warp::reply::json(&String::from("{\"type\": \"invalid_syntax\"}"))) + Ok::, Rejection>(warp::reply::with_header( + String::from("{\"type\": \"invalid_syntax\"}"), + "Content-Type", + "application/json", + )) } } }); diff --git a/front_end/src/constants.rs b/front_end/src/constants.rs index 7b63ae2..1c813a8 100644 --- a/front_end/src/constants.rs +++ b/front_end/src/constants.rs @@ -15,4 +15,4 @@ pub const PLAYER_CLEANUP_TIMEOUT: u64 = 300; pub const BACKEND_TICK_DURATION_MILLIS: i32 = 500; // TODO: Change this to "https://asdm.seodisparate.com/api" when backend is installed -pub const BACKEND_URL: &str = "http://localhost:1237/"; +pub const BACKEND_URL: &str = "http://testlocalhost/api"; diff --git a/front_end/src/html_helper.rs b/front_end/src/html_helper.rs index 3d76d3d..3d61b27 100644 --- a/front_end/src/html_helper.rs +++ b/front_end/src/html_helper.rs @@ -1,6 +1,11 @@ -use wasm_bindgen::JsValue; +use js_sys::{Function, JsString, Promise}; +use std::collections::HashMap; +use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen_futures::JsFuture; use web_sys::{window, Document, Request, RequestInit, Window}; +use crate::constants::BACKEND_URL; + pub fn get_window_document() -> Result<(Window, Document), String> { let window = window().ok_or_else(|| String::from("Failed to get window"))?; let document = window @@ -106,3 +111,52 @@ pub fn create_json_request(target_url: &str, json_body: &str) -> Result) -> Result { + let mut send_json_string = String::from("{"); + for (key, value) in entries { + send_json_string.push('"'); + send_json_string.push_str(&key); + send_json_string.push_str("\":\""); + send_json_string.push_str(&value); + send_json_string.push_str("\","); + } + send_json_string.truncate(send_json_string.len() - 1); + send_json_string.push('}'); + + // TODO check usage of "no-cors" + let function = Function::new_no_args(&format!( + " + let fetch_settings = {{}}; + fetch_settings.method = 'POST'; + fetch_settings.headers = {{}}; + fetch_settings.headers['Content-Type'] = 'application/json'; + fetch_settings.headers['Accept'] = 'text/html,application/json'; + //fetch_settings.mode = 'no-cors'; + fetch_settings.body = '{}'; + + return fetch('{}', fetch_settings) + .then((response) => {{ + return response.text(); + }}); + ", + send_json_string, BACKEND_URL, + )); + + let jsvalue: JsValue = function + .call0(&function) + .map_err(|e| format!("Failed to POST to backend: {:?}", e))?; + let promise: Promise = jsvalue.dyn_into().map_err(|e| { + format!( + "Failed to get Promise out of JsValue when POSTing to backend: {:?}", + e + ) + })?; + let future_result: JsValue = JsFuture::from(promise) + .await + .map_err(|e| format!("Failed to await promise when POSTing to backend: {:?}", e))?; + + let json_string = String::from(JsString::from(future_result)); + + Ok(json_string) +} diff --git a/front_end/src/state.rs b/front_end/src/state.rs index 2ceb3f9..4183a90 100644 --- a/front_end/src/state.rs +++ b/front_end/src/state.rs @@ -2,6 +2,8 @@ use crate::ai::AIDifficulty; use crate::constants::{COLS, ROWS}; use crate::game_logic::{check_win_draw, WinType}; +use serde::{Deserialize, Serialize}; + use std::cell::Cell; use std::collections::hash_set::HashSet; use std::fmt::Display; @@ -424,6 +426,14 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option, +} + #[cfg(test)] mod tests { use super::*; diff --git a/front_end/src/yew_components.rs b/front_end/src/yew_components.rs index 7c03453..582822b 100644 --- a/front_end/src/yew_components.rs +++ b/front_end/src/yew_components.rs @@ -6,12 +6,15 @@ use crate::constants::{ use crate::game_logic::{check_win_draw, WinType}; use crate::html_helper::{ append_to_info_text, create_json_request, element_append_class, element_remove_class, - get_window_document, + get_window_document, send_to_backend, }; use crate::random_helper::get_seeded_random; -use crate::state::{BoardState, GameState, MainMenuMessage, SharedState, Turn}; +use crate::state::{ + BoardState, GameState, MainMenuMessage, PairingRequestResponse, SharedState, Turn, +}; use std::cell::Cell; +use std::collections::HashMap; use std::rc::Rc; use js_sys::{Function, Promise}; @@ -285,153 +288,51 @@ impl Wrapper { fn get_networked_player_id(&mut self, ctx: &Context) { // make a request to get the player_id ctx.link().send_future(async { - // get window - let window = web_sys::window().expect("Should be able to get Window"); - // get request - let request = create_json_request(BACKEND_URL, "{\"type\": \"pairing_request\"}") - .expect("Should be able to create the JSON request for player_id"); - // send request - let promise = window.fetch_with_request(&request); - // get request result - let jsvalue_result = JsFuture::from(promise) - .await - .map_err(|e| format!("{:?}", e)); - if let Err(e) = jsvalue_result { - return WrapperMsg::BackendResponse(BREnum::Error(format!( - "ERROR jsvalue_result: {:?}", - e - ))); - } - log::warn!("{:?}", jsvalue_result.as_ref().unwrap()); - // get response from request result - let response_result: Result = jsvalue_result.unwrap().dyn_into(); - if let Err(e) = response_result { - return WrapperMsg::BackendResponse(BREnum::Error(format!( - "ERROR response_result: {:?}", - e - ))); - } - let json_jsvalue_promise_result = response_result.unwrap().json(); - if let Err(e) = json_jsvalue_promise_result { - return WrapperMsg::BackendResponse(BREnum::Error(format!( - "ERROR json_jsvalue_promise_result: {:?}", - e - ))); - } - let json_jsvalue_result = JsFuture::from(json_jsvalue_promise_result.unwrap()).await; - if let Err(e) = json_jsvalue_result { - return WrapperMsg::BackendResponse(BREnum::Error(format!( - "ERROR json_jsvalue_result: {:?}", - e - ))); - } - let json_result = json_jsvalue_result.unwrap().into_serde(); - if let Err(e) = json_result { - return WrapperMsg::BackendResponse(BREnum::Error(format!( - "ERROR json_result: {:?}", - e - ))); - } - // get serde json Value from result - let json_value: SerdeJSONValue = json_result.unwrap(); - log::warn!("{:?}", json_value); + let mut json_entries = HashMap::new(); + json_entries.insert("type".into(), "pairing_request".into()); - // get and check "type" in JSON - let type_opt = json_value.get("type"); - if type_opt.is_none() { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: No \"type\" entry in JSON".into(), - )); + let send_to_backend_result = send_to_backend(json_entries).await; + if let Err(e) = send_to_backend_result { + return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e))); } - let json_type = type_opt.unwrap(); - if let Some(type_string) = json_type.as_str() { - if type_string != "pairing_response" { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: Invalid \"type\" from response JSON".into(), - )); - } - } else { + + let request_result: Result = + serde_json::from_str(&send_to_backend_result.unwrap()); + if let Err(e) = request_result { + return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e))); + } + let request = request_result.unwrap(); + + if request.r#type != "pairing_response" { return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: Missing \"type\" from response JSON".into(), + "Backend returned invalid type for pairing_request".into(), )); } - // get and check "id" in JSON - let player_id: u32; - if let Some(wrapped_player_id) = json_value.get("id") { - if let Some(player_id_u64) = wrapped_player_id.as_u64() { - let player_id_conv_result: Result = player_id_u64.try_into(); - if player_id_conv_result.is_err() { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: \"id\" is too large".into(), - )); - } - player_id = player_id_conv_result.unwrap(); - } else { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: \"id\" is not a u64".into(), - )); - } - } else { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: Missing \"id\" from response JSON".into(), - )); - } - - // get and check status - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - enum Status { - Waiting, - Paired, - } - let mut status: Status; - if let Some(status_value) = json_value.get("status") { - if let Some(status_str) = status_value.as_str() { - if status_str == "waiting" { - status = Status::Waiting; - } else if status_str == "paired" { - status = Status::Paired; - } else { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: Got invalid \"status\" response in JSON".into(), - )); - } - } else { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: \"status\" response in JSON is not a str".into(), - )); - } - } else { - return WrapperMsg::BackendResponse(BREnum::Error( - "ERROR: \"status\" response is missing in JSON".into(), - )); - } - - // set "disconnect" callback here so that the client sends - // disconnect message when the page is closed + // set up onbeforeunload to disconnect with the received id let function = Function::new_no_args(&format!( " - window.onunload = function(event) {{ - let request_conf = {{}}; - request_conf.method = 'POST'; - request_conf.headers = \"'Content-Type': 'application/json'\"; - request_conf.body = \"{{ \"type\": \"disconnect\", \"id\": {} }}\"; - - const request = new Request('{}', request_conf); - - fetch(request); - }}; + window.addEventListener(\"beforeunload\", function(event) {{ + let xhr = new XMLHttpRequest(); + xhr.open('POST', '{}'); + xhr.send('{{\"type\": \"disconnect\", \"id\": {}}}'); + }}); ", - player_id, BACKEND_URL + BACKEND_URL, request.id )); function.call0(&function).ok(); - if status == Status::Paired { - // Get which side current player is on if paired - // TODO - return WrapperMsg::BackendResponse(BREnum::Error("ERROR: unimplemented".into())); + if let Some(color) = request.color { + WrapperMsg::BackendResponse(BREnum::GotID( + request.id, + if color == "cyan" { + Some(Turn::CyanPlayer) + } else { + Some(Turn::MagentaPlayer) + }, + )) } else { - WrapperMsg::BackendResponse(BREnum::GotID(player_id, None)) + WrapperMsg::BackendResponse(BREnum::GotID(request.id, None)) } }); } @@ -1057,6 +958,7 @@ impl Component for Wrapper { } BREnum::GotID(id, turn_opt) => { self.player_id = Some(id); + log::warn!("Got player id {}", id); } }, } // match (msg)