605.607 WIP! working get-id and disconnect
The frontend "server" and backend server must be served via the same port/address. This can be done by using nginx as the server, and using reverse-proxies from nginx to the frontend and backend servers.
This commit is contained in:
parent
8712d4cd8b
commit
8c84aae173
7 changed files with 137 additions and 143 deletions
16
back_end/Cargo.lock
generated
16
back_end/Cargo.lock
generated
|
@ -161,6 +161,8 @@ dependencies = [
|
||||||
"oorandom",
|
"oorandom",
|
||||||
"rand",
|
"rand",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"warp",
|
"warp",
|
||||||
|
@ -776,6 +778,20 @@ name = "serde"
|
||||||
version = "1.0.136"
|
version = "1.0.136"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
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]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
|
|
|
@ -14,3 +14,5 @@ rusqlite = "0.27.0"
|
||||||
rand = "0.8.4"
|
rand = "0.8.4"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
oorandom = "11.1.3"
|
oorandom = "11.1.3"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_derive = "1.0"
|
||||||
|
|
|
@ -43,19 +43,29 @@ async fn main() {
|
||||||
let db_tx_clone = db_tx_clone.clone();
|
let db_tx_clone = db_tx_clone.clone();
|
||||||
let s_helper_tx_clone = s_helper_tx.clone();
|
let s_helper_tx_clone = s_helper_tx.clone();
|
||||||
async move {
|
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 {
|
if let Ok(body_str) = body_str_result {
|
||||||
let json_result = serde_json::from_str(body_str);
|
let json_result = serde_json::from_str(body_str);
|
||||||
if let Ok(json_value) = json_result {
|
if let Ok(json_value) = json_result {
|
||||||
Ok(warp::reply::json(
|
Ok(warp::reply::with_header(
|
||||||
&json_handlers::handle_json(json_value, db_tx_clone, s_helper_tx_clone)
|
json_handlers::handle_json(json_value, db_tx_clone, s_helper_tx_clone)
|
||||||
.unwrap_or_else(|e| e),
|
.unwrap_or_else(|e| e),
|
||||||
|
"Content-Type",
|
||||||
|
"application/json",
|
||||||
))
|
))
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
Ok::<warp::reply::Json, Rejection>(warp::reply::json(&String::from("{\"type\": \"invalid_syntax\"}")))
|
Ok::<warp::reply::WithHeader<String>, Rejection>(warp::reply::with_header(
|
||||||
|
String::from("{\"type\": \"invalid_syntax\"}"),
|
||||||
|
"Content-Type",
|
||||||
|
"application/json",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,4 +15,4 @@ pub const PLAYER_CLEANUP_TIMEOUT: u64 = 300;
|
||||||
pub const BACKEND_TICK_DURATION_MILLIS: i32 = 500;
|
pub const BACKEND_TICK_DURATION_MILLIS: i32 = 500;
|
||||||
|
|
||||||
// TODO: Change this to "https://asdm.seodisparate.com/api" when backend is installed
|
// 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";
|
||||||
|
|
|
@ -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 web_sys::{window, Document, Request, RequestInit, Window};
|
||||||
|
|
||||||
|
use crate::constants::BACKEND_URL;
|
||||||
|
|
||||||
pub fn get_window_document() -> Result<(Window, Document), String> {
|
pub fn get_window_document() -> Result<(Window, Document), String> {
|
||||||
let window = window().ok_or_else(|| String::from("Failed to get window"))?;
|
let window = window().ok_or_else(|| String::from("Failed to get window"))?;
|
||||||
let document = window
|
let document = window
|
||||||
|
@ -106,3 +111,52 @@ pub fn create_json_request(target_url: &str, json_body: &str) -> Result<Request,
|
||||||
|
|
||||||
Ok(request)
|
Ok(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_to_backend(entries: HashMap<String, String>) -> Result<String, String> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ use crate::ai::AIDifficulty;
|
||||||
use crate::constants::{COLS, ROWS};
|
use crate::constants::{COLS, ROWS};
|
||||||
use crate::game_logic::{check_win_draw, WinType};
|
use crate::game_logic::{check_win_draw, WinType};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::hash_set::HashSet;
|
use std::collections::hash_set::HashSet;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
@ -424,6 +426,14 @@ pub fn string_from_board(board: BoardType, placed: usize) -> (String, Option<Boa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PairingRequestResponse {
|
||||||
|
pub r#type: String,
|
||||||
|
pub id: u32,
|
||||||
|
pub status: String,
|
||||||
|
pub color: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -6,12 +6,15 @@ use crate::constants::{
|
||||||
use crate::game_logic::{check_win_draw, WinType};
|
use crate::game_logic::{check_win_draw, WinType};
|
||||||
use crate::html_helper::{
|
use crate::html_helper::{
|
||||||
append_to_info_text, create_json_request, element_append_class, element_remove_class,
|
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::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::cell::Cell;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use js_sys::{Function, Promise};
|
use js_sys::{Function, Promise};
|
||||||
|
@ -285,153 +288,51 @@ impl Wrapper {
|
||||||
fn get_networked_player_id(&mut self, ctx: &Context<Self>) {
|
fn get_networked_player_id(&mut self, ctx: &Context<Self>) {
|
||||||
// make a request to get the player_id
|
// make a request to get the player_id
|
||||||
ctx.link().send_future(async {
|
ctx.link().send_future(async {
|
||||||
// get window
|
let mut json_entries = HashMap::new();
|
||||||
let window = web_sys::window().expect("Should be able to get Window");
|
json_entries.insert("type".into(), "pairing_request".into());
|
||||||
// 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<Response, _> = 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);
|
|
||||||
|
|
||||||
// get and check "type" in JSON
|
let send_to_backend_result = send_to_backend(json_entries).await;
|
||||||
let type_opt = json_value.get("type");
|
if let Err(e) = send_to_backend_result {
|
||||||
if type_opt.is_none() {
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
return WrapperMsg::BackendResponse(BREnum::Error(
|
|
||||||
"ERROR: No \"type\" entry in JSON".into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let json_type = type_opt.unwrap();
|
|
||||||
if let Some(type_string) = json_type.as_str() {
|
let request_result: Result<PairingRequestResponse, _> =
|
||||||
if type_string != "pairing_response" {
|
serde_json::from_str(&send_to_backend_result.unwrap());
|
||||||
return WrapperMsg::BackendResponse(BREnum::Error(
|
if let Err(e) = request_result {
|
||||||
"ERROR: Invalid \"type\" from response JSON".into(),
|
return WrapperMsg::BackendResponse(BREnum::Error(format!("{:?}", e)));
|
||||||
));
|
}
|
||||||
}
|
let request = request_result.unwrap();
|
||||||
} else {
|
|
||||||
|
if request.r#type != "pairing_response" {
|
||||||
return WrapperMsg::BackendResponse(BREnum::Error(
|
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
|
// set up onbeforeunload to disconnect with the received id
|
||||||
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<u32, _> = 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
|
|
||||||
let function = Function::new_no_args(&format!(
|
let function = Function::new_no_args(&format!(
|
||||||
"
|
"
|
||||||
window.onunload = function(event) {{
|
window.addEventListener(\"beforeunload\", function(event) {{
|
||||||
let request_conf = {{}};
|
let xhr = new XMLHttpRequest();
|
||||||
request_conf.method = 'POST';
|
xhr.open('POST', '{}');
|
||||||
request_conf.headers = \"'Content-Type': 'application/json'\";
|
xhr.send('{{\"type\": \"disconnect\", \"id\": {}}}');
|
||||||
request_conf.body = \"{{ \"type\": \"disconnect\", \"id\": {} }}\";
|
}});
|
||||||
|
|
||||||
const request = new Request('{}', request_conf);
|
|
||||||
|
|
||||||
fetch(request);
|
|
||||||
}};
|
|
||||||
",
|
",
|
||||||
player_id, BACKEND_URL
|
BACKEND_URL, request.id
|
||||||
));
|
));
|
||||||
function.call0(&function).ok();
|
function.call0(&function).ok();
|
||||||
|
|
||||||
if status == Status::Paired {
|
if let Some(color) = request.color {
|
||||||
// Get which side current player is on if paired
|
WrapperMsg::BackendResponse(BREnum::GotID(
|
||||||
// TODO
|
request.id,
|
||||||
return WrapperMsg::BackendResponse(BREnum::Error("ERROR: unimplemented".into()));
|
if color == "cyan" {
|
||||||
|
Some(Turn::CyanPlayer)
|
||||||
|
} else {
|
||||||
|
Some(Turn::MagentaPlayer)
|
||||||
|
},
|
||||||
|
))
|
||||||
} else {
|
} 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) => {
|
BREnum::GotID(id, turn_opt) => {
|
||||||
self.player_id = Some(id);
|
self.player_id = Some(id);
|
||||||
|
log::warn!("Got player id {}", id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
} // match (msg)
|
} // match (msg)
|
||||||
|
|
Loading…
Reference in a new issue