Initial commit
This commit is contained in:
commit
9c44bc892f
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
/dist
|
||||||
|
chat_log
|
2404
Cargo.lock
generated
Normal file
2404
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
members = [
|
||||||
|
"frontend",
|
||||||
|
"server",
|
||||||
|
]
|
7
dev.sh
Executable file
7
dev.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
(trap 'kill 0' SIGINT; \
|
||||||
|
bash -c 'cd frontend; trunk serve' & \
|
||||||
|
bash -c 'cargo watch -i chat_log -- cargo run --bin server -- --port 8081')
|
17
frontend/Cargo.toml
Normal file
17
frontend/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "frontend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
console_error_panic_hook = "0.1.7"
|
||||||
|
gloo-console = "0.3.0"
|
||||||
|
gloo-net = "0.6.0"
|
||||||
|
gloo-timers = "0.3.0"
|
||||||
|
itertools = "0.13.0"
|
||||||
|
log = "0.4.22"
|
||||||
|
wasm-logger = "0.2.0"
|
||||||
|
web-sys = { version = "0.3.70", features = ["Navigator"] }
|
||||||
|
yew = { version = "0.21.0", features = ["csr"] }
|
||||||
|
yew-router = "0.18.0"
|
||||||
|
yew-websocket = "1.21.0"
|
13
frontend/Trunk.toml
Normal file
13
frontend/Trunk.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[build]
|
||||||
|
target = "index.html"
|
||||||
|
dist = "../dist"
|
||||||
|
|
||||||
|
[[proxy]]
|
||||||
|
backend = "http://localhost:8081/api"
|
||||||
|
|
||||||
|
[[proxy]]
|
||||||
|
backend = "ws://localhost:8081/api/chat_ws"
|
||||||
|
ws = true
|
||||||
|
|
||||||
|
[serve]
|
||||||
|
addresses = ["::"]
|
27
frontend/index.html
Normal file
27
frontend/index.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html data-bs-theme="dark" style = "max-height: 100%">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<title>Yew App</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
function adjustHeight() {
|
||||||
|
document.documentElement.style.setProperty('--vh', `${window.innerHeight}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the function initially
|
||||||
|
adjustHeight();
|
||||||
|
|
||||||
|
// Update the height when the window is resized (e.g., when the address bar disappears)
|
||||||
|
window.addEventListener('resize', adjustHeight);
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.myvh-100 {
|
||||||
|
height: calc(var(--vh, 1vh));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style = "max-height: 100%"></body>
|
||||||
|
</html>
|
242
frontend/src/main.rs
Normal file
242
frontend/src/main.rs
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use gloo_console::log;
|
||||||
|
use gloo_timers::future::sleep;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
use yew_websocket::{
|
||||||
|
format::Text,
|
||||||
|
websocket::{WebSocketService, WebSocketStatus, WebSocketTask},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Routable, PartialEq)]
|
||||||
|
enum Route {
|
||||||
|
#[at("/")]
|
||||||
|
Home,
|
||||||
|
#[not_found]
|
||||||
|
#[at("/404")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch(routes: Route) -> Html {
|
||||||
|
match routes {
|
||||||
|
Route::Home => html! { <Home /> },
|
||||||
|
Route::NotFound => html! {
|
||||||
|
<>
|
||||||
|
<Nav />
|
||||||
|
<h1>{ "Page Not Found" }</h1>
|
||||||
|
</>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HomeMessage {
|
||||||
|
SubmittedMessage(String),
|
||||||
|
RecievedMessage(String),
|
||||||
|
WsStateChange(WebSocketStatus),
|
||||||
|
WsReconnect,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Home {
|
||||||
|
messages: Vec<String>,
|
||||||
|
chat_ws: WebSocketTask,
|
||||||
|
ws_state: WebSocketStatus,
|
||||||
|
ws_reconnecting: bool,
|
||||||
|
message_container_ref: NodeRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Home {
|
||||||
|
fn connect_ws(ctx: &Context<Self>) -> WebSocketTask {
|
||||||
|
let location = web_sys::window().unwrap().location();
|
||||||
|
let ws_proto = if location.protocol().unwrap() == "https:" {
|
||||||
|
"wss"
|
||||||
|
} else {
|
||||||
|
"ws"
|
||||||
|
};
|
||||||
|
let api_url = format!("{}://{}/api/chat_ws", ws_proto, location.host().unwrap());
|
||||||
|
log!("Connecting to ", &api_url);
|
||||||
|
WebSocketService::connect_text(
|
||||||
|
&api_url,
|
||||||
|
ctx.link()
|
||||||
|
.callback(|msg: Text| HomeMessage::RecievedMessage(msg.unwrap())),
|
||||||
|
ctx.link()
|
||||||
|
.callback(HomeMessage::WsStateChange),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Home {
|
||||||
|
type Message = HomeMessage;
|
||||||
|
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
let chat_ws = Self::connect_ws(ctx);
|
||||||
|
Self {
|
||||||
|
messages: Vec::new(),
|
||||||
|
chat_ws,
|
||||||
|
ws_state: WebSocketStatus::Closed,
|
||||||
|
ws_reconnecting: false,
|
||||||
|
message_container_ref: NodeRef::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
let msg_keypress = {
|
||||||
|
ctx.link().batch_callback(|press: KeyboardEvent| {
|
||||||
|
if press.key() == "Enter" {
|
||||||
|
let input = press
|
||||||
|
.target_dyn_into::<web_sys::HtmlInputElement>()
|
||||||
|
.unwrap();
|
||||||
|
let msg = input.value();
|
||||||
|
input.set_value("");
|
||||||
|
if on_mobile() {
|
||||||
|
input.blur().unwrap();
|
||||||
|
}
|
||||||
|
Some(HomeMessage::SubmittedMessage(msg))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let disable_input = self.ws_state != WebSocketStatus::Opened;
|
||||||
|
html! {
|
||||||
|
<div class="myvh-100 d-flex flex-column">
|
||||||
|
<Nav />
|
||||||
|
<div class="container-fluid d-flex flex-column flex-grow-1 mt-3">
|
||||||
|
if disable_input {
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
{ "Connection to backend lost, trying to reconnect" }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div ref={self.message_container_ref.clone()} class="border rounded flex-grow-1 overflow-auto mb-3" style = "flex-basis: 0">
|
||||||
|
{ self.messages.iter().map(|msg| html!{<div>{msg}</div>}).collect::<Vec<_>>() }
|
||||||
|
</div>
|
||||||
|
<div class="bottom-0 mb-3 mt-3">
|
||||||
|
<input
|
||||||
|
onkeydown={msg_keypress}
|
||||||
|
disabled={disable_input}
|
||||||
|
type="text"
|
||||||
|
id="message"
|
||||||
|
class="w-100 form-control"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
HomeMessage::SubmittedMessage(msg) => {
|
||||||
|
self.messages.push(msg.clone());
|
||||||
|
self.chat_ws.send(msg);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
HomeMessage::RecievedMessage(msg) => {
|
||||||
|
self.messages.push(msg.clone());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
HomeMessage::WsStateChange(state) => {
|
||||||
|
if state != self.ws_state {
|
||||||
|
if state != WebSocketStatus::Opened {
|
||||||
|
log!("WS connection closed");
|
||||||
|
if self.ws_state != WebSocketStatus::Opened {
|
||||||
|
log!("Already closed");
|
||||||
|
if !self.ws_reconnecting {
|
||||||
|
log!("Reconnecting in 5s");
|
||||||
|
self.ws_reconnecting = true;
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
sleep(Duration::from_secs(5)).await;
|
||||||
|
HomeMessage::WsReconnect
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log!("Reconnecting");
|
||||||
|
self.chat_ws = Self::connect_ws(ctx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log!("WS connection opened");
|
||||||
|
self.messages.clear();
|
||||||
|
}
|
||||||
|
self.ws_state = state.clone();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
if state != WebSocketStatus::Opened {
|
||||||
|
log!("Close/error state while closed/errored, marking closed and reconnecting");
|
||||||
|
self.ws_state = WebSocketStatus::Closed;
|
||||||
|
self.chat_ws = Self::connect_ws(ctx);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HomeMessage::WsReconnect => {
|
||||||
|
if self.ws_state != WebSocketStatus::Opened {
|
||||||
|
log!("Reconnecting");
|
||||||
|
self.ws_reconnecting = false;
|
||||||
|
self.chat_ws = Self::connect_ws(ctx);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
|
||||||
|
if let Some(message_container) = self.message_container_ref.cast::<web_sys::Element>() {
|
||||||
|
message_container.set_scroll_top(message_container.scroll_height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn Nav() -> Html {
|
||||||
|
html! {
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">{ "Chat" }</a>
|
||||||
|
<div class="nav-item dropdown navbar-nav">
|
||||||
|
<a
|
||||||
|
class="nav-link dropdown-toggle"
|
||||||
|
href="#"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
{ "PJHT" }
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">{ "Options" }</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">{ "Logout" }</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn App() -> Html {
|
||||||
|
html! {
|
||||||
|
<BrowserRouter>
|
||||||
|
<Switch<Route> render={switch} />
|
||||||
|
</BrowserRouter>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
yew::Renderer::<App>::new().render();
|
||||||
|
}
|
9
prod.sh
Executable file
9
prod.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
pushd frontend
|
||||||
|
trunk build
|
||||||
|
popd
|
||||||
|
|
||||||
|
cargo run --bin server --release -- --port 8080 --static-dir ./dist
|
18
server/Cargo.toml
Normal file
18
server/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = { version = "0.7.7", features = ["ws"] }
|
||||||
|
axum-client-ip = "0.6.1"
|
||||||
|
axum_static = "1.7.1"
|
||||||
|
clap = { version = "4.5.19", features = ["derive"] }
|
||||||
|
futures = "0.3.31"
|
||||||
|
log = "0.4.22"
|
||||||
|
slab = "0.4.9"
|
||||||
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
|
tower = "0.5.1"
|
||||||
|
tower-http = { version = "0.6.1", features = ["full"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = "0.3.18"
|
188
server/src/main.rs
Normal file
188
server/src/main.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use axum::body::Body;
|
||||||
|
use axum::extract::ws::{self, CloseFrame, Message, WebSocket};
|
||||||
|
use axum::extract::{Request, State, WebSocketUpgrade};
|
||||||
|
use axum::http::header::CONTENT_TYPE;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::response::Response;
|
||||||
|
use axum::{routing::get, Router};
|
||||||
|
use clap::Parser;
|
||||||
|
use futures::stream::SplitSink;
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use slab::Slab;
|
||||||
|
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::fs::{self, OpenOptions};
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tower::{ServiceBuilder, ServiceExt};
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
// Setup the command line interface with clap.
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(name = "server")]
|
||||||
|
struct Opt {
|
||||||
|
/// set the log level
|
||||||
|
#[clap(short = 'l', long = "log", default_value = "debug")]
|
||||||
|
log_level: String,
|
||||||
|
|
||||||
|
/// set the listen addr
|
||||||
|
#[clap(short = 'a', long = "addr", default_value = "::1")]
|
||||||
|
addr: String,
|
||||||
|
|
||||||
|
/// set the listen port
|
||||||
|
#[clap(short = 'p', long = "port", default_value = "8080")]
|
||||||
|
port: u16,
|
||||||
|
|
||||||
|
/// set the directory where static files are to be found
|
||||||
|
#[clap(long = "static-dir", default_value = "./dist")]
|
||||||
|
static_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
//#[derive(Clone)]
|
||||||
|
struct ServState {
|
||||||
|
client_sends: Slab<SplitSink<WebSocket, Message>>,
|
||||||
|
chat_log: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Vec<_>>();
|
||||||
|
debug!("{:?}", chat_log);
|
||||||
|
Self {
|
||||||
|
client_sends: Slab::new(),
|
||||||
|
chat_log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let opt = Opt::parse();
|
||||||
|
|
||||||
|
// Setup logging & RUST_LOG from args
|
||||||
|
if std::env::var("RUST_LOG").is_err() {
|
||||||
|
// nothing we can do but hope and pray. env vars are very thread-unsafe. we shouldn't have
|
||||||
|
// any other threads yet but who knows?
|
||||||
|
unsafe { std::env::set_var("RUST_LOG", format!("{},hyper=info,mio=info", opt.log_level)) }
|
||||||
|
}
|
||||||
|
// enable console logging
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let api = Router::new().route("/chat_ws", get(chat_ws));
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.nest("/api", api)
|
||||||
|
.fallback(|req: Request| async move {
|
||||||
|
if req.uri().path().starts_with("/api") {
|
||||||
|
return Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Body::from(""))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
match ServeDir::new(&opt.static_dir)
|
||||||
|
.append_index_html_on_directories(false)
|
||||||
|
.oneshot(req)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => {
|
||||||
|
if res.status() == StatusCode::NOT_FOUND {
|
||||||
|
let index_path = PathBuf::from(&opt.static_dir).join("index.html");
|
||||||
|
let index_content = match fs::read_to_string(index_path).await {
|
||||||
|
Err(_) => {
|
||||||
|
return Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Body::from("index file not found"))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
Ok(index_content) => index_content,
|
||||||
|
};
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header(CONTENT_TYPE, "text/html")
|
||||||
|
.body(Body::from(index_content))
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
res.map(Body::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Response::builder()
|
||||||
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.body(Body::from(format!("error: {err}")))
|
||||||
|
.expect("error response"),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_state(Arc::new(Mutex::new(ServState::new())))
|
||||||
|
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()));
|
||||||
|
|
||||||
|
let sock_addr = SocketAddr::from((
|
||||||
|
IpAddr::from_str(opt.addr.as_str()).unwrap_or(IpAddr::V6(Ipv6Addr::LOCALHOST)),
|
||||||
|
opt.port,
|
||||||
|
));
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(sock_addr).await.unwrap();
|
||||||
|
|
||||||
|
log::info!("listening on http://{}", sock_addr);
|
||||||
|
|
||||||
|
axum::serve(listener, app)
|
||||||
|
.await
|
||||||
|
.expect("Unable to start server");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn chat_ws(State(state): State<Arc<Mutex<ServState>>>, ws: WebSocketUpgrade) -> Response {
|
||||||
|
ws.on_upgrade(move |socket| async move {
|
||||||
|
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 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,
|
||||||
|
_ => {
|
||||||
|
close_code = ws::close_code::UNSUPPORTED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.lock().await.chat_log.push(msg.clone());
|
||||||
|
let mut log_file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open("chat_log")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
log_file.write_all(msg.as_bytes()).await.unwrap();
|
||||||
|
log_file.write_all(&[b'\n']).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;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
close_code = ws::close_code::PROTOCOL;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
debug!("Client disconnected");
|
||||||
|
let mut tx = state.lock().await.client_sends.remove(tx_idx);
|
||||||
|
let _ = tx
|
||||||
|
.send(Message::Close(Some(CloseFrame {
|
||||||
|
code: close_code,
|
||||||
|
reason: "".into(),
|
||||||
|
})))
|
||||||
|
.await;
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user