diff --git a/Cargo.lock b/Cargo.lock index ead0344..e139416 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.15" @@ -312,6 +327,48 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.5.19" @@ -358,6 +415,14 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -368,6 +433,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -386,6 +457,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -463,6 +540,9 @@ dependencies = [ name = "frontend" version = "0.1.0" dependencies = [ + "chrono", + "ciborium", + "common", "console_error_panic_hook", "gloo-console 0.3.0", "gloo-net 0.6.0", @@ -985,6 +1065,16 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.15.0" @@ -1101,6 +1191,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "implicit-clone" version = "0.4.9" @@ -1273,6 +1386,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -1622,7 +1744,9 @@ dependencies = [ "axum", "axum-client-ip", "axum_static", + "ciborium", "clap", + "common", "futures", "log", "slab", @@ -2180,6 +2304,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 8ea80bf..c1436d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = [ +members = [ "common", "frontend", "server", ] diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..ec2bf0f --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = { version = "0.4.38", features = ["serde"] } +serde = { version = "1.0.210", features = ["derive"] } diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..e08751c --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,8 @@ +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ChatMessage { + pub message: String, + pub time: DateTime, +} diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 69d5bfc..befc92e 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +chrono = { version = "0.4.38", features = ["wasmbind"] } +ciborium = "0.2.2" +common = { version = "0.1.0", path = "../common" } console_error_panic_hook = "0.1.7" gloo-console = "0.3.0" gloo-net = "0.6.0" diff --git a/frontend/src/main.rs b/frontend/src/main.rs index 70d25d9..024f371 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,11 +1,13 @@ use std::time::Duration; +use chrono::Local; +use common::ChatMessage; use gloo_console::log; use gloo_timers::future::sleep; use yew::prelude::*; use yew_router::prelude::*; use yew_websocket::{ - format::Text, + format::Binary, websocket::{WebSocketService, WebSocketStatus, WebSocketTask}, }; @@ -32,13 +34,13 @@ fn switch(routes: Route) -> Html { enum HomeMessage { SubmittedMessage(String), - RecievedMessage(String), + RecievedMessage(ChatMessage), WsStateChange(WebSocketStatus), WsReconnect, } struct Home { - messages: Vec, + messages: Vec, chat_ws: WebSocketTask, ws_state: WebSocketStatus, ws_reconnecting: bool, @@ -55,12 +57,14 @@ impl Home { }; let api_url = format!("{}://{}/api/chat_ws", ws_proto, location.host().unwrap()); log!("Connecting to ", &api_url); - WebSocketService::connect_text( + WebSocketService::connect_binary( &api_url, - ctx.link() - .callback(|msg: Text| HomeMessage::RecievedMessage(msg.unwrap())), - ctx.link() - .callback(HomeMessage::WsStateChange), + ctx.link().callback(|msg: Binary| { + let msg = msg.unwrap(); + let msg = ciborium::from_reader(msg.as_slice()).unwrap(); + HomeMessage::RecievedMessage(msg) + }), + ctx.link().callback(HomeMessage::WsStateChange), ) .unwrap() } @@ -69,7 +73,7 @@ impl Home { fn on_mobile() -> bool { let window = web_sys::window().unwrap(); let navigator = window.navigator(); - + navigator.max_touch_points() > 0 || window.inner_width().unwrap().as_f64().unwrap() < 768.0 } @@ -117,8 +121,17 @@ impl Component for Home { { "Connection to backend lost, trying to reconnect" } } -
- { self.messages.iter().map(|msg| html!{
{msg}
}).collect::>() } +
+ { self.messages.iter().map(|msg| html!{ +
+
{msg.message.clone()}
+
{msg.time.format("%I:%M:%S %p").to_string()}
+
} + ).collect::>() }
, msg: Self::Message) -> bool { match msg { HomeMessage::SubmittedMessage(msg) => { + let msg = ChatMessage { + message: msg.clone(), + time: Local::now(), + }; self.messages.push(msg.clone()); - self.chat_ws.send(msg); + let mut buf = Vec::new(); + ciborium::into_writer(&msg, &mut buf).unwrap(); + self.chat_ws.send_binary(buf); true } HomeMessage::RecievedMessage(msg) => { - self.messages.push(msg.clone()); + self.messages.push(msg); true } HomeMessage::WsStateChange(state) => { diff --git a/server/Cargo.toml b/server/Cargo.toml index 19eaac4..1a7b0b7 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -7,7 +7,9 @@ edition = "2021" axum = { version = "0.7.7", features = ["ws"] } axum-client-ip = "0.6.1" axum_static = "1.7.1" +ciborium = "0.2.2" clap = { version = "4.5.19", features = ["derive"] } +common = { version = "0.1.0", path = "../common" } futures = "0.3.31" log = "0.4.22" slab = "0.4.9" diff --git a/server/src/main.rs b/server/src/main.rs index 651f7fc..66b2b0a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -6,6 +6,7 @@ use axum::http::StatusCode; use axum::response::Response; use axum::{routing::get, Router}; use clap::Parser; +use common::ChatMessage; use futures::stream::SplitSink; use futures::{SinkExt, StreamExt}; use slab::Slab; @@ -19,7 +20,7 @@ use tokio::sync::Mutex; use tower::{ServiceBuilder, ServiceExt}; use tower_http::services::ServeDir; use tower_http::trace::TraceLayer; -use tracing::debug; +use tracing::{debug, warn}; // Setup the command line interface with clap. #[derive(Parser, Debug)] @@ -42,21 +43,16 @@ struct Opt { static_dir: PathBuf, } -//#[derive(Clone)] struct ServState { client_sends: Slab>, - chat_log: Vec, + chat_log: Vec, } impl ServState { fn new() -> Self { - let chat_log = std::fs::read_to_string("chat_log") - .unwrap_or_default() - .split('\n') - .filter(|x| !x.is_empty()) - .map(|x| x.to_string()) - .collect::>(); - debug!("{:?}", chat_log); + let chat_log = std::fs::read("chat_log").map_or(Vec::new(), |chat_log| { + ciborium::from_reader(chat_log.as_slice()).unwrap() + }); Self { client_sends: Slab::new(), chat_log, @@ -142,37 +138,43 @@ async fn chat_ws(State(state): State>>, ws: WebSocketUpgrad let (mut tx, mut rx) = socket.split(); debug!("Client connected"); for msg in &state.lock().await.chat_log { - tx.send(Message::Text(msg.clone())).await.unwrap(); + let mut buf = Vec::new(); + ciborium::into_writer(&msg, &mut buf).unwrap(); + tx.send(Message::Binary(buf)).await.unwrap(); } let tx_idx = state.lock().await.client_sends.insert(tx); let mut close_code = ws::close_code::NORMAL; while let Some(msg) = rx.next().await { if let Ok(msg) = msg { - let msg = match msg { - Message::Text(msg) => msg, + let msg_bytes = match msg { + Message::Binary(msg) => msg, _ => { close_code = ws::close_code::UNSUPPORTED; + warn!("Got unsupported message"); break; } }; - state.lock().await.chat_log.push(msg.clone()); + let msg: ChatMessage = ciborium::from_reader(msg_bytes.as_slice()).unwrap(); + state.lock().await.chat_log.push(msg); let mut log_file = OpenOptions::new() .write(true) - .append(true) .create(true) + .truncate(true) .open("chat_log") .await .unwrap(); - log_file.write_all(msg.as_bytes()).await.unwrap(); - log_file.write_all(&[b'\n']).await.unwrap(); + let mut buf = Vec::new(); + ciborium::into_writer(&state.lock().await.chat_log, &mut buf).unwrap(); + log_file.write_all(&buf).await.unwrap(); for (i, client_tx) in state.lock().await.client_sends.iter_mut() { if i == tx_idx { continue; } - let _ = client_tx.send(Message::Text(msg.clone())).await; + let _ = client_tx.send(Message::Binary(msg_bytes.clone())).await; } } else { close_code = ws::close_code::PROTOCOL; + warn!("Websocket protocol error"); break; }; }