upd
This commit is contained in:
parent
d0d8d7acaa
commit
b5902d67ea
12 changed files with 62 additions and 132 deletions
1
.tokeignore
Normal file
1
.tokeignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
frontend/pnpm-lock.yaml
|
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -261,6 +261,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
"chrono",
|
||||||
"common",
|
"common",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"ed25519-compact",
|
"ed25519-compact",
|
||||||
|
@ -788,6 +789,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"capnp",
|
"capnp",
|
||||||
"capnpc",
|
"capnpc",
|
||||||
|
"chrono",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1675,6 +1677,7 @@ dependencies = [
|
||||||
name = "frontend"
|
name = "frontend"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"common",
|
"common",
|
||||||
"futures",
|
"futures",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [ ] send messages from one client to another
|
- [x] send messages from one client to another
|
||||||
- [ ] save messages to database
|
- [ ] save messages to database
|
||||||
|
- [ ] get rid of unwraps
|
||||||
|
- [ ] handle disconnect correctly
|
||||||
|
- [ ] user accounts
|
||||||
- [ ] retrieve history
|
- [ ] retrieve history
|
||||||
- [ ] notifications
|
- [ ] notifications
|
||||||
- [ ] multiple clients
|
- [ ] multiple clients
|
||||||
|
|
|
@ -19,3 +19,4 @@ ring = "0.17.7"
|
||||||
serde = "1.0.193"
|
serde = "1.0.193"
|
||||||
serde_json = "1.0.108"
|
serde_json = "1.0.108"
|
||||||
tokio = { version = "1.35.1", features = ["full"] }
|
tokio = { version = "1.35.1", features = ["full"] }
|
||||||
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
|
|
|
@ -3,7 +3,7 @@ extern crate serde;
|
||||||
|
|
||||||
mod prisma;
|
mod prisma;
|
||||||
|
|
||||||
use std::{future::Future, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::{
|
use axum::{
|
||||||
|
@ -11,13 +11,13 @@ use axum::{
|
||||||
ws::{Message as WsMessage, WebSocket},
|
ws::{Message as WsMessage, WebSocket},
|
||||||
Query, State, WebSocketUpgrade,
|
Query, State, WebSocketUpgrade,
|
||||||
},
|
},
|
||||||
response::{sse::Event, Response},
|
response::Response,
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use common::Message;
|
use chrono::Utc;
|
||||||
|
use common::{ClientMessage, Message};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use futures::FutureExt;
|
|
||||||
use prisma::PrismaClient;
|
use prisma::PrismaClient;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use tokio::sync::broadcast::{self, Sender};
|
use tokio::sync::broadcast::{self, Sender};
|
||||||
|
@ -56,14 +56,18 @@ async fn main() -> Result<()> {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct SendMessageRequest {
|
struct SendMessageRequest {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
message: Message,
|
message: ClientMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_message(
|
async fn send_message(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(request): Json<SendMessageRequest>,
|
Json(request): Json<SendMessageRequest>,
|
||||||
) -> Json<Value> {
|
) -> Json<Value> {
|
||||||
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...");
|
println!("Got message from client, forwarding to room...");
|
||||||
|
|
||||||
Json(json!({}))
|
Json(json!({}))
|
||||||
|
@ -95,6 +99,11 @@ async fn event_stream(
|
||||||
let result = room_rx.recv().await.unwrap();
|
let result = room_rx.recv().await.unwrap();
|
||||||
println!("Received message: {result:?}");
|
println!("Received message: {result:?}");
|
||||||
let payload = serde_json::to_string(&result).unwrap();
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.76", features = ["backtrace"] }
|
anyhow = { version = "1.0.76", features = ["backtrace"] }
|
||||||
capnp = "0.18.10"
|
capnp = "0.18.10"
|
||||||
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
||||||
|
@ -6,7 +8,14 @@ pub mod clientserver_capnp {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Message {
|
pub struct ClientMessage {
|
||||||
pub author: String,
|
pub author: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Message {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub inner: ClientMessage,
|
||||||
|
pub server_timestamp: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ tokio = { version = "1.35.1", features = ["full"] }
|
||||||
reqwest = { version = "0.11.23", features = ["json"] }
|
reqwest = { version = "0.11.23", features = ["json"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
url = "2.5.0"
|
url = "2.5.0"
|
||||||
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![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 futures::{stream::SplitSink, SinkExt, StreamExt};
|
||||||
use tauri::{State, Window};
|
use tauri::{State, Window};
|
||||||
use tokio::{net::TcpStream, sync::RwLock};
|
use tokio::{net::TcpStream, sync::RwLock};
|
||||||
|
@ -46,7 +47,8 @@ async fn connect(
|
||||||
// &format!("/v1/events?name={username}"));
|
// &format!("/v1/events?name={username}"));
|
||||||
|
|
||||||
println!("connecting to {url}");
|
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();
|
let (socket_write, mut socket_read) = socket.split();
|
||||||
|
|
||||||
|
@ -88,7 +90,7 @@ async fn send_message(
|
||||||
match *state_ref {
|
match *state_ref {
|
||||||
Connection::Unconnected => return Err(format!("L")),
|
Connection::Unconnected => return Err(format!("L")),
|
||||||
Connection::Connected(ref state) => {
|
Connection::Connected(ref state) => {
|
||||||
let message = Message {
|
let message = ClientMessage {
|
||||||
author: state.username.clone(),
|
author: state.username.clone(),
|
||||||
body: content,
|
body: content,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { invoke } from "@tauri-apps/api/tauri";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { createStore } from "solid-js/store";
|
import { createStore } from "solid-js/store";
|
||||||
|
import { ServerMessage } from "./lib/interfaces";
|
||||||
|
|
||||||
const initialRandomNumber = Math.floor(Math.random() * 9000) + 1000;
|
const initialRandomNumber = Math.floor(Math.random() * 9000) + 1000;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ function App() {
|
||||||
status: "unconnected",
|
status: "unconnected",
|
||||||
});
|
});
|
||||||
const [messageContent, setMessageContent] = createSignal("");
|
const [messageContent, setMessageContent] = createSignal("");
|
||||||
const [messages, setMessages] = createStore([]);
|
const [messages, setMessages] = createStore<ServerMessage[]>([]);
|
||||||
|
|
||||||
// const [greetMsg, setGreetMsg] = createSignal("");
|
// const [greetMsg, setGreetMsg] = createSignal("");
|
||||||
// const [name, setName] = createSignal("");
|
// const [name, setName] = createSignal("");
|
||||||
|
@ -35,13 +36,6 @@ function App() {
|
||||||
setConnectionStatus({ status: "connected" });
|
setConnectionStatus({ status: "connected" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendMessage = async (evt) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
await invoke("send_message", { content: messageContent() });
|
|
||||||
console.log("SHIET");
|
|
||||||
};
|
|
||||||
|
|
||||||
const action = () => {
|
const action = () => {
|
||||||
switch (connectionStatus().status) {
|
switch (connectionStatus().status) {
|
||||||
case "unconnected":
|
case "unconnected":
|
||||||
|
@ -55,7 +49,7 @@ function App() {
|
||||||
const disabled = () => connectionStatus().status === "connecting";
|
const disabled = () => connectionStatus().status === "connecting";
|
||||||
|
|
||||||
createEffect(async () => {
|
createEffect(async () => {
|
||||||
const unlisten = await listen("new_message", (event) => {
|
const unlisten = await listen<ServerMessage>("new_message", (event) => {
|
||||||
console.log("new message", event);
|
console.log("new message", event);
|
||||||
setMessages([...messages, event.payload]);
|
setMessages([...messages, event.payload]);
|
||||||
});
|
});
|
||||||
|
@ -71,11 +65,13 @@ function App() {
|
||||||
<input
|
<input
|
||||||
placeholder="Server address..."
|
placeholder="Server address..."
|
||||||
value={serverAddress()}
|
value={serverAddress()}
|
||||||
|
onChange={(evt) => setServerAddress(evt.currentTarget.value)}
|
||||||
disabled={disabled()}
|
disabled={disabled()}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
placeholder="Username..."
|
placeholder="Username..."
|
||||||
value={username()}
|
value={username()}
|
||||||
|
onChange={(evt) => setUsername(evt.currentTarget.value)}
|
||||||
disabled={disabled()}
|
disabled={disabled()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -93,11 +89,19 @@ function App() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={sendMessage}>
|
<form
|
||||||
|
onSubmit={async (evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
await invoke("send_message", { content: messageContent() });
|
||||||
|
setMessageContent("");
|
||||||
|
console.log("SHIET");
|
||||||
|
}}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
placeholder="Send a message..."
|
placeholder="Send a message..."
|
||||||
value={messageContent()}
|
value={messageContent()}
|
||||||
onChange={(evt) => setMessageContent(evt.target.value)}
|
onChange={(evt) => setMessageContent(evt.currentTarget.value)}
|
||||||
|
disabled={connectionStatus().status !== "connected"}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
5
frontend/src/lib/interfaces.ts
Normal file
5
frontend/src/lib/interfaces.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export interface ServerMessage {
|
||||||
|
author: string;
|
||||||
|
body: string;
|
||||||
|
server_timestamp: number;
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue