diff --git a/.tokeignore b/.tokeignore new file mode 100644 index 0000000..aad4577 --- /dev/null +++ b/.tokeignore @@ -0,0 +1 @@ +frontend/pnpm-lock.yaml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 53fdde4..5c3878a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,7 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", + "chrono", "common", "dashmap", "ed25519-compact", @@ -788,6 +789,7 @@ dependencies = [ "anyhow", "capnp", "capnpc", + "chrono", "serde", ] @@ -1675,6 +1677,7 @@ dependencies = [ name = "frontend" version = "0.0.0" dependencies = [ + "chrono", "common", "futures", "reqwest", diff --git a/README.md b/README.md index 5c0843c..73d366f 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,11 @@ ## Roadmap -- [ ] send messages from one client to another +- [x] send messages from one client to another - [ ] save messages to database +- [ ] get rid of unwraps +- [ ] handle disconnect correctly +- [ ] user accounts - [ ] retrieve history - [ ] notifications - [ ] multiple clients diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 4ef83ef..c9b3458 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -19,3 +19,4 @@ ring = "0.17.7" serde = "1.0.193" serde_json = "1.0.108" tokio = { version = "1.35.1", features = ["full"] } +chrono = { version = "0.4.31", features = ["serde"] } diff --git a/backend/src/main.rs b/backend/src/main.rs index cae9d19..c94e9e6 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -3,7 +3,7 @@ extern crate serde; mod prisma; -use std::{future::Future, sync::Arc}; +use std::sync::Arc; use anyhow::Result; use axum::{ @@ -11,13 +11,13 @@ use axum::{ ws::{Message as WsMessage, WebSocket}, Query, State, WebSocketUpgrade, }, - response::{sse::Event, Response}, + response::Response, routing::{get, post}, Json, Router, }; -use common::Message; +use chrono::Utc; +use common::{ClientMessage, Message}; use dashmap::DashMap; -use futures::FutureExt; use prisma::PrismaClient; use serde_json::{json, Value}; use tokio::sync::broadcast::{self, Sender}; @@ -56,14 +56,18 @@ async fn main() -> Result<()> { #[derive(Debug, Deserialize)] struct SendMessageRequest { #[serde(flatten)] - message: Message, + message: ClientMessage, } async fn send_message( State(state): State, Json(request): Json, ) -> Json { - state.room_tx.send(request.message).unwrap(); + let wrapped_message = Message { + inner: request.message, + server_timestamp: Utc::now(), + }; + state.room_tx.send(wrapped_message).unwrap(); println!("Got message from client, forwarding to room..."); Json(json!({})) @@ -95,6 +99,11 @@ async fn event_stream( let result = room_rx.recv().await.unwrap(); println!("Received message: {result:?}"); let payload = serde_json::to_string(&result).unwrap(); - socket.send(WsMessage::Text(payload)).await.unwrap(); + match socket.send(WsMessage::Text(payload)).await { + Ok(_) => {} + Err(err) => { + eprintln!("Error: {err}") + } + } } } diff --git a/common/Cargo.toml b/common/Cargo.toml index 582a1c7..8c32a7f 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.76", features = ["backtrace"] } capnp = "0.18.10" +chrono = { version = "0.4.31", features = ["serde"] } serde = { version = "1.0.193", features = ["derive"] } [build-dependencies] diff --git a/common/src/lib.rs b/common/src/lib.rs index da0170d..46c0527 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,3 +1,5 @@ +use chrono::{DateTime, Utc}; + #[macro_use] extern crate serde; @@ -6,7 +8,14 @@ pub mod clientserver_capnp { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Message { +pub struct ClientMessage { pub author: String, pub body: String, } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Message { + #[serde(flatten)] + pub inner: ClientMessage, + pub server_timestamp: DateTime, +} diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index c6038ff..063a30b 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -23,6 +23,7 @@ tokio = { version = "1.35.1", features = ["full"] } reqwest = { version = "0.11.23", features = ["json"] } futures = "0.3.30" url = "2.5.0" +chrono = { version = "0.4.31", features = ["serde"] } [features] # this feature is used for production builds or when `devPath` points to the filesystem diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index 5e7bf46..7eb81dc 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -1,7 +1,8 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use common::Message; +use chrono::Utc; +use common::{ClientMessage, Message}; use futures::{stream::SplitSink, SinkExt, StreamExt}; use tauri::{State, Window}; use tokio::{net::TcpStream, sync::RwLock}; @@ -46,7 +47,8 @@ async fn connect( // &format!("/v1/events?name={username}")); println!("connecting to {url}"); - let (socket, response) = tokio_tungstenite::connect_async(url).await.unwrap(); + let (socket, _response) = + tokio_tungstenite::connect_async(url).await.unwrap(); let (socket_write, mut socket_read) = socket.split(); @@ -88,7 +90,7 @@ async fn send_message( match *state_ref { Connection::Unconnected => return Err(format!("L")), Connection::Connected(ref state) => { - let message = Message { + let message = ClientMessage { author: state.username.clone(), body: content, }; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ace9fae..98d3cbd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,6 +3,7 @@ import { invoke } from "@tauri-apps/api/tauri"; import "./App.css"; import { listen } from "@tauri-apps/api/event"; import { createStore } from "solid-js/store"; +import { ServerMessage } from "./lib/interfaces"; const initialRandomNumber = Math.floor(Math.random() * 9000) + 1000; @@ -15,7 +16,7 @@ function App() { status: "unconnected", }); const [messageContent, setMessageContent] = createSignal(""); - const [messages, setMessages] = createStore([]); + const [messages, setMessages] = createStore([]); // const [greetMsg, setGreetMsg] = createSignal(""); // const [name, setName] = createSignal(""); @@ -35,13 +36,6 @@ function App() { setConnectionStatus({ status: "connected" }); }; - const sendMessage = async (evt) => { - evt.preventDefault(); - - await invoke("send_message", { content: messageContent() }); - console.log("SHIET"); - }; - const action = () => { switch (connectionStatus().status) { case "unconnected": @@ -55,7 +49,7 @@ function App() { const disabled = () => connectionStatus().status === "connecting"; createEffect(async () => { - const unlisten = await listen("new_message", (event) => { + const unlisten = await listen("new_message", (event) => { console.log("new message", event); setMessages([...messages, event.payload]); }); @@ -71,11 +65,13 @@ function App() { setServerAddress(evt.currentTarget.value)} disabled={disabled()} /> setUsername(evt.currentTarget.value)} disabled={disabled()} /> @@ -93,11 +89,19 @@ function App() {
-
+ { + evt.preventDefault(); + await invoke("send_message", { content: messageContent() }); + setMessageContent(""); + console.log("SHIET"); + }} + > setMessageContent(evt.target.value)} + onChange={(evt) => setMessageContent(evt.currentTarget.value)} + disabled={connectionStatus().status !== "connected"} />
diff --git a/frontend/src/lib/interfaces.ts b/frontend/src/lib/interfaces.ts new file mode 100644 index 0000000..68df702 --- /dev/null +++ b/frontend/src/lib/interfaces.ts @@ -0,0 +1,5 @@ +export interface ServerMessage { + author: string; + body: string; + server_timestamp: number; +} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index f7de85b..e69de29 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1,109 +0,0 @@ -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color: #0f0f0f; - background-color: #f6f6f6; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -.container { - margin: 0; - padding-top: 10vh; - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: 0.75s; -} - -.logo.tauri:hover { - filter: drop-shadow(0 0 2em #24c8db); -} - -.row { - display: flex; - justify-content: center; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} - -a:hover { - color: #535bf2; -} - -h1 { - text-align: center; -} - -input, -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - color: #0f0f0f; - background-color: #ffffff; - transition: border-color 0.25s; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); -} - -button { - cursor: pointer; -} - -button:hover { - border-color: #396cd8; -} -button:active { - border-color: #396cd8; - background-color: #e8e8e8; -} - -input, -button { - outline: none; -} - -#greet-input { - margin-right: 5px; -} - -@media (prefers-color-scheme: dark) { - :root { - color: #f6f6f6; - background-color: #2f2f2f; - } - - a:hover { - color: #24c8db; - } - - input, - button { - color: #ffffff; - background-color: #0f0f0f98; - } - button:active { - background-color: #0f0f0f69; - } -}