Solved issue previously ignored by statically rendering components through
unrelated bug Enter keypress event fires the value presence checker incorrectly This commit allows further functionality to be expanded to the application such as proper registration support and complex user types
This commit is contained in:
parent
728bf33856
commit
847ecd9a69
5 changed files with 193 additions and 135 deletions
44
src/App.tsx
44
src/App.tsx
|
@ -1,19 +1,31 @@
|
|||
import React, { useState } from "react";
|
||||
import ChatWrapper from "./Chat/Chat";
|
||||
import ChatWrapper from "./Chat/ChatWrapper";
|
||||
import "./App.css";
|
||||
import { Message } from "./Chat/types";
|
||||
import { MessageContainer } from "./Chat/MessageContainer";
|
||||
const App = (): React.ReactElement => {
|
||||
const [username, setUsername] = useState<string>()
|
||||
if (!username) {
|
||||
const newName = prompt("Username:") as string
|
||||
setUsername(newName)
|
||||
}
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>Local Area Network Chat Application</h1>
|
||||
<pre>This web application was built for the purposes of an EPQ project.</pre>
|
||||
|
||||
{<ChatWrapper user={username as string} brokerURL=""/> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default App;
|
||||
const [username, setUsername] = useState<string>();
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
if (!username) {
|
||||
const newName = prompt("Username:") as string;
|
||||
setUsername(newName);
|
||||
}
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>Local Area Network Chat Application</h1>
|
||||
<pre>
|
||||
This web application was built for the purposes of an EPQ
|
||||
project.
|
||||
</pre>
|
||||
{messages.map((message) => {
|
||||
return <MessageContainer {...message} />;
|
||||
})}
|
||||
{
|
||||
<ChatWrapper
|
||||
user={username as string}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default App;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
overflow-y: auto;
|
||||
overflow-wrap: normal;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
.entry-box {
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { MessageContainer } from "./MessageContainer";
|
||||
import { Client, Stomp, StompHeaders } from "@stomp/stompjs";
|
||||
import { Message, MessageType } from "./types";
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import './Chat.css';
|
||||
// The last bit of magic sauce to make this work
|
||||
// EXPLANATION
|
||||
//
|
||||
const domain = window.location.hostname
|
||||
const port = "8080"
|
||||
const connectionAddress = `ws://${domain}:${port}/ws`
|
||||
const endpoints = {
|
||||
destination: "/app/chat",
|
||||
subscription: "/sub/chat",
|
||||
history: "/api/v1/chat/history/"
|
||||
}
|
||||
const ChatWrapper = (
|
||||
{
|
||||
user,
|
||||
brokerURL,
|
||||
}:
|
||||
{
|
||||
user: string,
|
||||
brokerURL: string,
|
||||
}
|
||||
): React.ReactElement => {
|
||||
const stompClient = new Client({
|
||||
brokerURL: connectionAddress
|
||||
})
|
||||
// TODO solve issue with non-static markup
|
||||
stompClient.onConnect = (frame) => {
|
||||
stompClient.subscribe(endpoints.subscription, (message) => {
|
||||
console.log(`Collected new message: ${message.body}`);
|
||||
const messageBody = JSON.parse(message.body) as Message
|
||||
// if (messageBody.type !== MessageType.MESSAGE) {return;}
|
||||
const messageElement = <MessageContainer {...messageBody} />
|
||||
console.log(messageElement);
|
||||
|
||||
// Temporary solution
|
||||
// The solution lacks interactibility - because it's static markup
|
||||
const container = document.getElementById("chat-inner") as HTMLDivElement
|
||||
|
||||
// Truly horrible and disgusting
|
||||
container.innerHTML += renderToStaticMarkup(messageElement)
|
||||
});
|
||||
stompClient.publish({
|
||||
body: JSON.stringify({
|
||||
type: MessageType.HELLO,
|
||||
fromUserId: user,
|
||||
toUserId: "everyone",
|
||||
content: `${user} has joined the server!`,
|
||||
timeMillis: Date.now()
|
||||
}),
|
||||
destination: endpoints.destination
|
||||
})
|
||||
}
|
||||
|
||||
// Generic error handlers
|
||||
stompClient.onWebSocketError = (error) => {
|
||||
console.error('Error with websocket', error);
|
||||
};
|
||||
|
||||
stompClient.onStompError = (frame) => {
|
||||
console.error('Broker reported error: ' + frame.headers['message']);
|
||||
console.error('Additional details: ' + frame.body);
|
||||
};
|
||||
|
||||
// Button press event handler.
|
||||
const sendData = () => {
|
||||
console.log("WebSockets handler invoked.")
|
||||
// There must be a react-native and non-document-getElementById way to do this
|
||||
// TODO Explore
|
||||
const entryElement: HTMLInputElement = document.getElementById("data-entry") as HTMLInputElement
|
||||
if (!entryElement.value) {alert("Message cannot be empty!"); return;}
|
||||
const messageData: Message =
|
||||
{
|
||||
type: MessageType.MESSAGE,
|
||||
fromUserId: user,
|
||||
toUserId: "everyone",
|
||||
content: entryElement.value,
|
||||
timeMillis: Date.now()
|
||||
}
|
||||
console.log(`STOMP connection status: ${stompClient.connected}`);
|
||||
stompClient.publish({
|
||||
body: JSON.stringify(messageData),
|
||||
destination: endpoints.destination,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
});
|
||||
entryElement.value = "";
|
||||
}
|
||||
useEffect(() => {
|
||||
// Stomp client is disconnected after each re-render
|
||||
// This should be actively avoided
|
||||
stompClient.activate()
|
||||
return () => {
|
||||
stompClient.deactivate()
|
||||
}
|
||||
}, [stompClient])
|
||||
// https://www.w3schools.com/jsref/obj_keyboardevent.asp
|
||||
document.addEventListener("keypress", (ev: KeyboardEvent) => {
|
||||
if (ev.key == "Enter") {
|
||||
sendData();
|
||||
}
|
||||
})
|
||||
return (
|
||||
<div className="chat">
|
||||
<div className="chat-inner-wrapper">
|
||||
<div id="chat-inner">
|
||||
</div>
|
||||
</div>
|
||||
<span className="entry-box"><input id="data-entry"></input><button onClick={() => sendData()}>Send</button></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ChatWrapper;
|
120
src/Chat/ChatWrapper.tsx
Normal file
120
src/Chat/ChatWrapper.tsx
Normal file
|
@ -0,0 +1,120 @@
|
|||
import React, { ReactElement, useEffect, useRef, useState } from "react";
|
||||
import { MessageContainer } from "./MessageContainer";
|
||||
import { Client, Stomp, StompHeaders } from "@stomp/stompjs";
|
||||
import { Message, MessageType } from "./types";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import "./Chat.css";
|
||||
// The last bit of magic sauce to make this work
|
||||
// EXPLANATION
|
||||
//
|
||||
const domain = window.location.hostname;
|
||||
const port = "8080";
|
||||
const connectionAddress = `ws://${domain}:${port}/ws`;
|
||||
const endpoints = {
|
||||
destination: "/app/chat",
|
||||
subscription: "/sub/chat",
|
||||
history: "/api/v1/msg/",
|
||||
};
|
||||
const ChatWrapper = ({ user }: { user: string }): React.ReactElement => {
|
||||
const [messages, setMessages] = useState<ReactElement[]>([]);
|
||||
let stompClientRef = useRef(
|
||||
new Client({
|
||||
brokerURL: connectionAddress,
|
||||
})
|
||||
);
|
||||
// TODO solve issue with non-static markup
|
||||
stompClientRef.current.onConnect = (frame) => {
|
||||
stompClientRef.current.subscribe(endpoints.subscription, (message) => {
|
||||
console.log(`Collected new message: ${message.body}`);
|
||||
const messageBody = JSON.parse(message.body) as Message;
|
||||
console.log(messageBody);
|
||||
setMessages((message) => {
|
||||
return message.concat([
|
||||
<MessageContainer
|
||||
key={`${messageBody.type}@${messageBody.timeMillis}`}
|
||||
{...messageBody}
|
||||
/>,
|
||||
]);
|
||||
});
|
||||
console.log(messages);
|
||||
});
|
||||
stompClientRef.current.publish({
|
||||
body: JSON.stringify({
|
||||
type: MessageType.HELLO,
|
||||
fromUserId: user,
|
||||
toUserId: "everyone",
|
||||
content: `${user} has joined the server!`,
|
||||
timeMillis: Date.now(),
|
||||
}),
|
||||
destination: endpoints.destination,
|
||||
});
|
||||
};
|
||||
|
||||
// Generic error handlers
|
||||
stompClientRef.current.onWebSocketError = (error) => {
|
||||
console.error("Error with websocket", error);
|
||||
};
|
||||
|
||||
stompClientRef.current.onStompError = (frame) => {
|
||||
console.error("Broker reported error: " + frame.headers["message"]);
|
||||
console.error("Additional details: " + frame.body);
|
||||
};
|
||||
|
||||
// Button press event handler.
|
||||
const sendData = () => {
|
||||
console.log("WebSockets handler invoked.");
|
||||
// There must be a react-native and non-document-getElementById way to do this
|
||||
// TODO Explore
|
||||
const entryElement: HTMLInputElement = document.getElementById(
|
||||
"data-entry"
|
||||
) as HTMLInputElement;
|
||||
if (!entryElement.value) {
|
||||
alert("Message cannot be empty!");
|
||||
return;
|
||||
}
|
||||
const messageData: Message = {
|
||||
type: MessageType.MESSAGE,
|
||||
fromUserId: user,
|
||||
toUserId: "everyone",
|
||||
content: entryElement.value,
|
||||
timeMillis: Date.now(),
|
||||
};
|
||||
console.log(
|
||||
`STOMP connection status: ${stompClientRef.current.connected}`
|
||||
);
|
||||
stompClientRef.current.publish({
|
||||
body: JSON.stringify(messageData),
|
||||
destination: endpoints.destination,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
});
|
||||
entryElement.value = "";
|
||||
};
|
||||
useEffect(() => {
|
||||
// Stomp client is disconnected after each re-render
|
||||
// This should be actively avoided
|
||||
stompClientRef.current.activate();
|
||||
return () => {
|
||||
stompClientRef.current.deactivate();
|
||||
};
|
||||
}, [stompClientRef]);
|
||||
// https://www.w3schools.com/jsref/obj_keyboardevent.asp
|
||||
document.addEventListener("keypress", (ev: KeyboardEvent) => {
|
||||
if (ev.key == "Enter") {
|
||||
sendData();
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div className="chat">
|
||||
<div className="chat-inner-wrapper">
|
||||
{messages}
|
||||
</div>
|
||||
<span className="entry-box">
|
||||
<input id="data-entry"></input>
|
||||
<button onClick={() => sendData()}>Send</button>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default ChatWrapper;
|
44
src/Chat/server.ts
Normal file
44
src/Chat/server.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { Client } from "@stomp/stompjs";
|
||||
import { Message, MessageType } from "./types";
|
||||
const domain = window.location.hostname
|
||||
const port = "8080"
|
||||
const connectionAddress = `ws://${domain}:${port}/ws`
|
||||
const endpoints = {
|
||||
destination: "/app/chat",
|
||||
subscription: "/sub/chat",
|
||||
history: "/api/v1/msg/"
|
||||
}
|
||||
export const createStompConnection = (user: string, subUpdateHandler: (message: Message) => void) => {
|
||||
const stompClient = new Client({
|
||||
brokerURL: connectionAddress
|
||||
})
|
||||
stompClient.onConnect = (frame) => {
|
||||
stompClient.subscribe(endpoints.subscription, (message) => {
|
||||
console.log(`Collected new message: ${message.body}`);
|
||||
const messageBody = JSON.parse(message.body) as Message
|
||||
console.log(messageBody);
|
||||
subUpdateHandler(messageBody);
|
||||
});
|
||||
stompClient.publish({
|
||||
body: JSON.stringify({
|
||||
type: MessageType.HELLO,
|
||||
fromUserId: user,
|
||||
toUserId: "everyone",
|
||||
content: `${user} has joined the server!`,
|
||||
timeMillis: Date.now()
|
||||
}),
|
||||
destination: endpoints.destination
|
||||
})
|
||||
}
|
||||
|
||||
// Generic error handlers
|
||||
stompClient.onWebSocketError = (error) => {
|
||||
console.error('Error with websocket', error);
|
||||
};
|
||||
|
||||
stompClient.onStompError = (frame) => {
|
||||
console.error('Broker reported error: ' + frame.headers['message']);
|
||||
console.error('Additional details: ' + frame.body);
|
||||
};
|
||||
return stompClient;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue