From 7d4953fea6d50eb15120f2be34ffa22302fb4713 Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Sat, 20 Jan 2024 11:26:57 +0200 Subject: [PATCH 01/13] Refactor and cleanup code --- src/App.tsx | 4 +- src/Chat/Chat.tsx | 4 +- src/Login/Login.tsx | 2 +- .../MessageDisplay.css | 0 .../MessageDisplay.tsx | 2 +- src/context.ts | 2 +- src/handlers/server.ts | 47 ------------------- src/{Chat => type}/messageTypes.ts | 0 src/{Chat/userTypes.tsx => type/userTypes.ts} | 0 9 files changed, 7 insertions(+), 54 deletions(-) rename src/{Chat => MessageDisplay}/MessageDisplay.css (100%) rename src/{Chat => MessageDisplay}/MessageDisplay.tsx (96%) delete mode 100644 src/handlers/server.ts rename src/{Chat => type}/messageTypes.ts (100%) rename src/{Chat/userTypes.tsx => type/userTypes.ts} (100%) diff --git a/src/App.tsx b/src/App.tsx index 70d41b2..47d2f05 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import React, { useContext, useEffect, useState } from "react"; import Chat from "./Chat/Chat"; import "./App.css"; -import { LangType, Message } from "./Chat/messageTypes"; -import { MessageDisplay } from "./Chat/MessageDisplay"; +import { LangType, Message } from "./type/messageTypes"; +import { MessageDisplay } from "./MessageDisplay/MessageDisplay"; import strings from "./Intl/strings.json"; import { LangContext, LoginContext, LoginType } from "./context"; import { contentTypes, domain, endpoints, port } from "./consts"; diff --git a/src/Chat/Chat.tsx b/src/Chat/Chat.tsx index aa19502..00e0d0f 100644 --- a/src/Chat/Chat.tsx +++ b/src/Chat/Chat.tsx @@ -5,9 +5,9 @@ import React, { useRef, useState, } from "react"; -import { MessageDisplay } from "./MessageDisplay"; +import { MessageDisplay } from "../MessageDisplay/MessageDisplay"; import { Client } from "@stomp/stompjs"; -import { Message, MessageType } from "./messageTypes"; +import { Message, MessageType } from "../type/messageTypes"; import "./Chat.css"; import strings from "../Intl/strings.json"; import { LangContext } from "../context"; diff --git a/src/Login/Login.tsx b/src/Login/Login.tsx index 6629f4c..6de932c 100644 --- a/src/Login/Login.tsx +++ b/src/Login/Login.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { contentTypes, domain, endpoints, port } from "../consts"; import { LoginType } from "../context"; -import { User } from "../Chat/userTypes"; +import { User } from "../type/userTypes"; import "./Login.css"; const encrypt = (rawPasswordString: string) => { // TODO Encryption method stub diff --git a/src/Chat/MessageDisplay.css b/src/MessageDisplay/MessageDisplay.css similarity index 100% rename from src/Chat/MessageDisplay.css rename to src/MessageDisplay/MessageDisplay.css diff --git a/src/Chat/MessageDisplay.tsx b/src/MessageDisplay/MessageDisplay.tsx similarity index 96% rename from src/Chat/MessageDisplay.tsx rename to src/MessageDisplay/MessageDisplay.tsx index d4a6e2b..19853ca 100644 --- a/src/Chat/MessageDisplay.tsx +++ b/src/MessageDisplay/MessageDisplay.tsx @@ -1,5 +1,5 @@ import React, { useContext } from "react"; -import { Message, MessageType } from "./messageTypes"; +import { Message, MessageType } from "../type/messageTypes"; import { LangContext } from "../context"; import strings from "../Intl/strings.json"; import "./MessageDisplay.css"; diff --git a/src/context.ts b/src/context.ts index b826fb8..25d2cf5 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,5 +1,5 @@ import { createContext } from "react"; -import { LangType } from "./Chat/messageTypes"; +import { LangType } from "./type/messageTypes"; export type LoginType = { username: string; lastSeen: number; diff --git a/src/handlers/server.ts b/src/handlers/server.ts deleted file mode 100644 index 494614b..0000000 --- a/src/handlers/server.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Client } from "@stomp/stompjs"; -import { Message, MessageType } from "../Chat/messageTypes"; -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; -}; diff --git a/src/Chat/messageTypes.ts b/src/type/messageTypes.ts similarity index 100% rename from src/Chat/messageTypes.ts rename to src/type/messageTypes.ts diff --git a/src/Chat/userTypes.tsx b/src/type/userTypes.ts similarity index 100% rename from src/Chat/userTypes.tsx rename to src/type/userTypes.ts From 56feab2ea11d3da6032e02b7845235ef1e01c9f0 Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Sat, 20 Jan 2024 12:06:34 +0200 Subject: [PATCH 02/13] format and use more aggressive tabstop widths --- src/App.tsx | 9 - src/Chat/Chat.tsx | 241 +++++++++++----------- src/Login/Login.css | 4 +- src/Login/Login.tsx | 278 ++++++++++++++------------ src/MessageDisplay/MessageDisplay.tsx | 111 +++++----- src/type/messageTypes.ts | 57 +++--- src/type/userTypes.ts | 10 +- 7 files changed, 366 insertions(+), 344 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 47d2f05..e5d3328 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -90,15 +90,6 @@ const App = ({ } }); }; - // if (!username) { - // var newName = prompt(home.userNamePrompt) as string; - // while (!validateName(newName)) { - // console.log(newName); - - // prompt("Username invalid! Please enter again.") as string; - // } - // setUsername(newName); - // } if (!login) { return <>; } else diff --git a/src/Chat/Chat.tsx b/src/Chat/Chat.tsx index 00e0d0f..7aff06f 100644 --- a/src/Chat/Chat.tsx +++ b/src/Chat/Chat.tsx @@ -1,9 +1,9 @@ import React, { - ReactElement, - useContext, - useEffect, - useRef, - useState, + ReactElement, + useContext, + useEffect, + useRef, + useState, } from "react"; import { MessageDisplay } from "../MessageDisplay/MessageDisplay"; import { Client } from "@stomp/stompjs"; @@ -13,117 +13,128 @@ import strings from "../Intl/strings.json"; import { LangContext } from "../context"; import { connectionAddress, endpoints } from "../consts"; const Chat = ({ user }: { user: string }): React.ReactElement => { - const lang = useContext(LangContext); - const chatPage = strings[lang].chat; - const [messages, setMessages] = useState([]); - 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([ - , - ]); - }); - }); - 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, - }); - }; + const lang = useContext(LangContext); + const chatPage = strings[lang].chat; + const [messages, setMessages] = useState([]); + 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([ + , + ]); + }); + } + ); + 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); + }; - // 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); + }; - stompClientRef.current.onStompError = (frame) => { - console.error("Broker reported error: " + frame.headers["message"]); - console.error("Additional details: " + frame.body); - }; - - // Button press event handler. - const sendData = () => { - const entryElement: HTMLInputElement = document.getElementById( - "data-entry" - ) as HTMLInputElement; - if (entryElement.value === "") { - 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(); - }; - }, []); - // https://www.w3schools.com/jsref/obj_keyboardevent.asp - document.addEventListener("keydown", (ev: KeyboardEvent) => { - if (ev.key === "Enter") { - sendData(); - } - }); - useEffect(() => { - try { - const elem = document.querySelector(".chat-inner-wrapper"); - if (elem) { - elem.scrollTop = elem.scrollHeight; - } else { - } - } catch (err) { - console.log("error encountered"); - } - return () => {}; - }, [messages]); - return ( -
- - Logged in as {user} - -
{messages}
- - - - -
- ); + // Button press event handler. + const sendData = () => { + const entryElement: HTMLInputElement = document.getElementById( + "data-entry" + ) as HTMLInputElement; + if (entryElement.value === "") { + 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(); + }; + }, []); + // https://www.w3schools.com/jsref/obj_keyboardevent.asp + document.addEventListener("keydown", (ev: KeyboardEvent) => { + if (ev.key === "Enter") { + sendData(); + } + }); + useEffect(() => { + try { + const elem = document.querySelector( + ".chat-inner-wrapper" + ); + if (elem) { + elem.scrollTop = elem.scrollHeight; + } else { + } + } catch (err) { + console.log("error encountered"); + } + return () => {}; + }, [messages]); + return ( +
+ + Logged in as {user} + +
{messages}
+ + + + +
+ ); }; export default Chat; diff --git a/src/Login/Login.css b/src/Login/Login.css index 957484e..21bceff 100644 --- a/src/Login/Login.css +++ b/src/Login/Login.css @@ -1,3 +1,3 @@ .uname-error-text { - color: red; -} \ No newline at end of file + color: red; +} diff --git a/src/Login/Login.tsx b/src/Login/Login.tsx index 6de932c..7f5ff79 100644 --- a/src/Login/Login.tsx +++ b/src/Login/Login.tsx @@ -4,138 +4,156 @@ import { LoginType } from "../context"; import { User } from "../type/userTypes"; import "./Login.css"; const encrypt = (rawPasswordString: string) => { - // TODO Encryption method stub - return rawPasswordString; + // TODO Encryption method stub + return rawPasswordString; }; export const Login = ({ - setLogin, + setLogin, }: { - setLogin: (newLogin: LoginType | undefined) => void; + setLogin: (newLogin: LoginType | undefined) => void; }): React.ReactElement => { - const [valid, setValid] = useState(true); - const [validText, setValidText] = useState(); - const registrationHandler = () => { - const uname = (document.getElementById("username") as HTMLInputElement) - .value; - const passwd = encrypt( - (document.getElementById("passwd") as HTMLInputElement).value - ); - fetch(`http://${domain}:${port}${endpoints.user}`, { - method: "POST", - mode: "cors", - headers: contentTypes.json, - body: JSON.stringify({ - userName: uname, - dateJoined: Date.now(), - passwordHash: passwd, - }), - }).then((response) => { - if (response.status === 400) { - // 400 Bad request - console.log("Username is taken or invalid!"); - setValid(false); - setValidText("Username is taken or invalid!"); - } else if (response.status === 200) { - // 200 OK - const futureDate = new Date(); - futureDate.setHours(futureDate.getHours() + 2); - setLogin({ - username: uname, - lastSeen: Date.now(), - validUntil: futureDate.getUTCMilliseconds(), - }); - document.title = `IRC User ${uname}`; - } - }); - }; - // login button press handler - const loginHandler = () => { - const uname = (document.getElementById("username") as HTMLInputElement) - .value; - const passwd = encrypt( - (document.getElementById("passwd") as HTMLInputElement).value - ); - // async invocation of Fetch API - fetch(`http://${domain}:${port}${endpoints.user}?name=${uname}`, { - method: "GET", - mode: "cors", - }) - .then((res) => { - if (res.status === 404) { - console.log("404 not found encountered"); - throw new Error("Username does not exist"); - } else if (res.status === 200) { - console.log("200 OK"); - } - return res.json(); - }) - .then((userObject) => { - if (!userObject) { - return; - } - const user = userObject as User; - const validLogin = passwd === user.passwordHash; - if (!validLogin) { - // login invalid - throw new Error("Password incorrect!"); - } else { - // login valid - const validUntilDate: Date = new Date(); - validUntilDate.setHours(validUntilDate.getHours() + 2); - setLogin({ - username: user.userName, - lastSeen: user.lastSeen, - validUntil: validUntilDate.getUTCMilliseconds(), - }); - document.title = `IRC User ${uname}`; - } - }) - .catch((reason: Error) => { - setValid(false); - setValidText(reason.message); - }); - }; - return ( -
-
- Login window -

- {valid && valid !== undefined ? "" : validText} -

- -
- -
- -
- -
- - - -
-
- ); + const [valid, setValid] = useState(true); + const [validText, setValidText] = useState(); + const registrationHandler = () => { + const uname = ( + document.getElementById("username") as HTMLInputElement + ).value; + const passwd = encrypt( + (document.getElementById("passwd") as HTMLInputElement) + .value + ); + fetch(`http://${domain}:${port}${endpoints.user}`, { + method: "POST", + mode: "cors", + headers: contentTypes.json, + body: JSON.stringify({ + userName: uname, + dateJoined: Date.now(), + passwordHash: passwd, + }), + }).then((response) => { + if (response.status === 400) { + // 400 Bad request + console.log("Username is taken or invalid!"); + setValid(false); + setValidText("Username is taken or invalid!"); + } else if (response.status === 200) { + // 200 OK + const futureDate = new Date(); + futureDate.setHours(futureDate.getHours() + 2); + setLogin({ + username: uname, + lastSeen: Date.now(), + validUntil: futureDate.getUTCMilliseconds(), + }); + document.title = `IRC User ${uname}`; + } + }); + }; + // login button press handler + const loginHandler = () => { + const uname = ( + document.getElementById("username") as HTMLInputElement + ).value; + const passwd = encrypt( + (document.getElementById("passwd") as HTMLInputElement) + .value + ); + // async invocation of Fetch API + fetch( + `http://${domain}:${port}${endpoints.user}?name=${uname}`, + { + method: "GET", + mode: "cors", + } + ) + .then((res) => { + if (res.status === 404) { + console.log( + "404 not found encountered" + ); + throw new Error( + "Username does not exist" + ); + } else if (res.status === 200) { + console.log("200 OK"); + } + return res.json(); + }) + .then((userObject) => { + if (!userObject) { + return; + } + const user = userObject as User; + const validLogin = passwd === user.passwordHash; + if (!validLogin) { + // login invalid + throw new Error("Password incorrect!"); + } else { + // login valid + const validUntilDate: Date = new Date(); + validUntilDate.setHours( + validUntilDate.getHours() + 2 + ); + setLogin({ + username: user.userName, + lastSeen: user.lastSeen, + validUntil: validUntilDate.getUTCMilliseconds(), + }); + document.title = `IRC User ${uname}`; + } + }) + .catch((reason: Error) => { + setValid(false); + setValidText(reason.message); + }); + }; + return ( +
+
+ Login window +

+ {valid && valid !== undefined + ? "" + : validText} +

+ +
+ +
+ +
+ +
+ + + +
+
+ ); }; diff --git a/src/MessageDisplay/MessageDisplay.tsx b/src/MessageDisplay/MessageDisplay.tsx index 19853ca..92e9e79 100644 --- a/src/MessageDisplay/MessageDisplay.tsx +++ b/src/MessageDisplay/MessageDisplay.tsx @@ -4,57 +4,64 @@ import { LangContext } from "../context"; import strings from "../Intl/strings.json"; import "./MessageDisplay.css"; export const MessageDisplay = ({ - type, - fromUserId, - toUserId, - content, - timeMillis, + type, + fromUserId, + toUserId, + content, + timeMillis, }: Message): React.ReactElement => { - const dateTime: Date = new Date(timeMillis); - const lang = useContext(LangContext); - const msgPage = strings[lang].chat; - /* FIXED funny error - * DESCRIPTION - * The line below was - * return (

[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...

) - * The line incorrectly generated a value of "UTC" as the parameter to toLocaleString() - * While "UTC" is an accepted string value, in EEST, aka. "Europe/Athens" timezone string is not an acceptable parameter. - * This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db. - * Funny clown moment 🤡 - */ - const timeString = `${ - dateTime.getHours() > 12 - ? dateTime.getHours() - 12 - : dateTime.getHours() - }:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`; - switch (type) { - case MessageType.HELLO as MessageType: - return ( -

- [{timeString}]{" "} - {msgPage.joinMessage.replace("$userName", fromUserId)} -

- ); - case MessageType.MESSAGE as MessageType: - return ( -

- [{timeString}]{" "} - {msgPage.serverMessage - .replace("$userName", fromUserId) - .replace("$content", content)} -

- ); - case MessageType.DATA as MessageType: - return <>; - case MessageType.CHNAME as MessageType: - return <>; - default: - console.error("Illegal MessageType reported!"); - return ( -

- [{timeString}] **THIS MESSAGE CANNOT BE CORRECTLY SHOWN - BECAUSE THE CLIENT ENCOUNTERED AN ERROR** -

- ); - } + const dateTime: Date = new Date(timeMillis); + const lang = useContext(LangContext); + const msgPage = strings[lang].chat; + /* FIXED funny error + * DESCRIPTION + * The line below was + * return (

[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...

) + * The line incorrectly generated a value of "UTC" as the parameter to toLocaleString() + * While "UTC" is an accepted string value, in EEST, aka. "Europe/Athens" timezone string is not an acceptable parameter. + * This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db. + * Funny clown moment 🤡 + */ + const timeString = `${ + dateTime.getHours() > 12 + ? dateTime.getHours() - 12 + : dateTime.getHours() + }:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`; + switch (type) { + case MessageType.HELLO as MessageType: + return ( +

+ [{timeString}]{" "} + {msgPage.joinMessage.replace( + "$userName", + fromUserId + )} +

+ ); + case MessageType.MESSAGE as MessageType: + return ( +

+ [{timeString}]{" "} + {msgPage.serverMessage + .replace( + "$userName", + fromUserId + ) + .replace("$content", content)} +

+ ); + case MessageType.DATA as MessageType: + return <>; + case MessageType.CHNAME as MessageType: + return <>; + default: + console.error("Illegal MessageType reported!"); + return ( +

+ [{timeString}] **THIS MESSAGE CANNOT BE + CORRECTLY SHOWN BECAUSE THE CLIENT + ENCOUNTERED AN ERROR** +

+ ); + } }; diff --git a/src/type/messageTypes.ts b/src/type/messageTypes.ts index 3243d25..f83594a 100644 --- a/src/type/messageTypes.ts +++ b/src/type/messageTypes.ts @@ -1,51 +1,46 @@ export const enum MessageType { - MESSAGE = "MESSAGE", - CHNAME = "CHNAME", - HELLO = "HELLO", - DATA = "DATA", + MESSAGE = "MESSAGE", + CHNAME = "CHNAME", + HELLO = "HELLO", + DATA = "DATA", } export enum SystemMessageCode { - REQ, - RES, - ERR, + REQ, + RES, + ERR, } export type HistoryFetchResult = { - count: number; - items: Array; + count: number; + items: Array; }; export type ErrorResult = { - text: string; + text: string; }; export type TimestampSendRequest = { - ts: number; + ts: number; }; export type SystemMessage = { - code: SystemMessageCode; - data: HistoryFetchResult | ErrorResult | TimestampSendRequest; + code: SystemMessageCode; + data: HistoryFetchResult | ErrorResult | TimestampSendRequest; }; export type ChatMessage = { - fromUserId: string; - toUserId: string; - content: string; - timeMillis: number; + fromUserId: string; + toUserId: string; + content: string; + timeMillis: number; }; export type HelloMessage = { - fromUserId: string; - timeMillis: number; + fromUserId: string; + timeMillis: number; }; export type DataMessage = {}; export type Message = { - type: MessageType; - // data: SystemMessage | ChatMessage | HelloMessage - fromUserId: string; - toUserId: string; - content: string; - timeMillis: number; + type: MessageType; + // data: SystemMessage | ChatMessage | HelloMessage + fromUserId: string; + toUserId: string; + content: string; + timeMillis: number; }; -export const acceptedLangs = [ - "en_US", - "zh_TW", - "el_GR", - "ar_SA" -] as const; +export const acceptedLangs = ["en_US", "zh_TW", "el_GR", "ar_SA"] as const; export type LangType = (typeof acceptedLangs)[number]; diff --git a/src/type/userTypes.ts b/src/type/userTypes.ts index 7d03592..b888745 100644 --- a/src/type/userTypes.ts +++ b/src/type/userTypes.ts @@ -1,7 +1,7 @@ export type User = { - id: number; - userName: string; - dateJoined: number; - lastSeen: number; - passwordHash: string; + id: number; + userName: string; + dateJoined: number; + lastSeen: number; + passwordHash: string; }; From 4df2355001dda33d2831b081466dd84d65794fc6 Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Wed, 24 Jan 2024 22:49:50 +0200 Subject: [PATCH 03/13] sync misc fmt changes --- src/App.css | 10 +- src/App.tsx | 23 +-- src/Chat/Chat.css | 20 +-- src/Chat/Chat.tsx | 5 +- src/Intl/strings.json | 196 ++++++++++++++++++-------- src/Login/Login.tsx | 64 +++++---- src/MessageDisplay/MessageDisplay.css | 4 +- src/MessageDisplay/MessageDisplay.tsx | 1 + src/MessageDisplay/messageHandler.tsx | 5 + src/index.css | 16 +-- src/type/userTypes.ts | 6 + 11 files changed, 216 insertions(+), 134 deletions(-) create mode 100644 src/MessageDisplay/messageHandler.tsx diff --git a/src/App.css b/src/App.css index 05482ad..e4849cc 100644 --- a/src/App.css +++ b/src/App.css @@ -1,6 +1,6 @@ body { - background-color: black; - color: #00FF33; - margin: 1%; - min-height: 100vh; -} \ No newline at end of file + background-color: black; + color: #00FF33; + margin: 1%; + min-height: 100vh; +} diff --git a/src/App.tsx b/src/App.tsx index e5d3328..7d908e3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -67,29 +67,10 @@ const App = ({ }: { changeLang: (value: string) => void; }): React.ReactElement => { - const [username, setUsername] = useState(); const [messages, setMessages] = useState([]); const login = useContext(LoginContext); const lang = useContext(LangContext); const home = strings[lang].homepage; - // TODO refine setName logic -- move to Login handler - const setNamePrompt = () => { - var newName = prompt(home.userNamePrompt) as string; - while (!validateName(newName)) { - console.log(newName); - - prompt("Username invalid! Please enter again.") as string; - } - setNameOnServer(newName).then((value) => { - if (!value.success) { - alert(value.reason); - return true; - } else { - setUsername(newName); - return false; - } - }); - }; if (!login) { return <>; } else @@ -116,8 +97,8 @@ const App = ({ }) .then((responseBody: { success: boolean }) => { if (responseBody.success) { - setUsername(newUsername as string); - } else { + // TODO Put new username response true handler method stub + } else { console.error( "Server POST message failed." ); diff --git a/src/Chat/Chat.css b/src/Chat/Chat.css index d92e700..76b19f6 100644 --- a/src/Chat/Chat.css +++ b/src/Chat/Chat.css @@ -1,15 +1,17 @@ .chat-inner-wrapper { - height: 50vh; - overflow-y:scroll; - /* overflow-wrap: normal; */ - display: flex; - flex-direction: column; + height: 50vh; + overflow-y: scroll; + /* overflow-wrap: normal; */ + display: flex; + flex-direction: column; } + .entry-box { - margin-top: 5px; + margin-top: 5px; } + .chat { - /* min-height: 80vh; */ - position: relative; -} \ No newline at end of file + /* min-height: 80vh; */ + position: relative; +} diff --git a/src/Chat/Chat.tsx b/src/Chat/Chat.tsx index 7aff06f..1aa884a 100644 --- a/src/Chat/Chat.tsx +++ b/src/Chat/Chat.tsx @@ -125,7 +125,10 @@ const Chat = ({ user }: { user: string }): React.ReactElement => { return (
- Logged in as {user} + {chatPage.window.title.replaceAll( + "$userName", + user + )}
{messages}
diff --git a/src/Intl/strings.json b/src/Intl/strings.json index 7dd6bc1..ceff215 100644 --- a/src/Intl/strings.json +++ b/src/Intl/strings.json @@ -1,64 +1,136 @@ { - "variableNames": ["userName", "content"], - "acceptedLangs": ["en_US", "zh_TW", "el_GR"], - "en_US": { - "homepage": { - "userNamePrompt": "Your username: ", - "title": "LAN Chat Server", - "description": "This web application was built for the purposes of an EPQ project.", - "copyrightText": "Copyright 2024 - 2025 Zhongheng Liu @ Byron College", - "switchLang": "Switch Language", - "newLangPrompt": "Input your ISO-639_ISO-3166 language-contry code below - for example: \"en_US\": " - }, - "chat": { - "sendButtonPrompt": "Send", - "serverMessage": "Message from $userName: $content", - "joinMessage": "$userName joined the server" - } - }, - "zh_TW": { - "homepage": { - "userNamePrompt": "您的用戶名: ", - "title": "本地聊天伺服器", - "description": "該網絡伺服器應用程式專爲Edexcel Level 3 EPQ而製作", - "copyrightText": "版權所有 2024 - 2025 Zhongheng Liu @ Byron College", - "switchLang": "切換語言", - "newLangPrompt": "在下方輸入您想使用的語言的ISO-639_ISO-3166組合語言代碼 - 例如:\"en_US\":" - }, - "chat": { - "sendButtonPrompt": "發送", - "serverMessage": "來自 $userName 的訊息:$content", - "joinMessage": "$userName 加入了伺服器!" - } - }, - "el_GR": { - "homepage": { - "userNamePrompt": "το όνομα χρήστη σας: ", - "title": "Διακομιστής τοπικού δικτύου συνομιλίας", - "description": "Αυτή η διαδικτυακή εφαρμογή δημιουργήθηκε για τους σκοπούς ενός έργου EPQ.", - "copyrightText": "Πνευματικά δικαιώματα 2024 - 2025 Zhongheng Liu @ Byron College", - "switchLang": "Αλλαγή γλώσσας", - "newLangPrompt": "Εισαγάγετε τον κωδικό γλώσσας-χώρας ISO-639 ISO-3166 παρακάτω - για παράδειγμα: \"en_US\":" - }, - "chat": { - "sendButtonPrompt": "Στείλετε", - "joinMessage": "$userName έγινε μέλος του διακομιστή", - "serverMessage": "μήνυμα από $userName: $content" - } - }, - "ar_SA": { - "homepage": { - "userNamePrompt": "اسم المستخدم الخاص بك:", - "title": "خادم الدردشة LAN", - "description": "تم إنشاء تطبيق الويب هذا لأغراض مشروع EPQ.", - "copyrightText": "حقوق الطبع والنشر 2024 - 2025 Zhongheng Liu @ كلية بايرون", - "switchLang": "تبديل اللغة", - "newLangPrompt": "أدخل رمز اللغة ISO-639_ISO-3166 أدناه - على سبيل المثال: \"en_US\":" - }, - "chat": { - "sendButtonPrompt": "يرسل", - "joinMessage": "$userName انضم إلى الخادم", - "serverMessage": "رسالة من $userName: $content" - } - } + "variableNames": ["userName", "content"], + "acceptedLangs": ["en_US", "zh_TW", "el_GR"], + "en_US": { + "homepage": { + "userNamePrompt": "Your username: ", + "title": "LAN Chat Server", + "description": "This web application was built for the purposes of an EPQ project.", + "copyrightText": "Copyright 2024 - 2025 Zhongheng Liu @ Byron College", + "switchLang": "Switch Language", + "newLangPrompt": "Input your ISO-639_ISO-3166 language-contry code below - for example: \"en_US\": " + }, + "chat": { + "window": { + "title": "Logged in as $userName" + }, + "sendButtonPrompt": "Send", + "serverMessage": "Message from $userName: $content", + "joinMessage": "$userName joined the server" + }, + "login": { + "error": { + "unameTakenOrInvalid": "Username is taken or invalid!", + "unameNotExists": "Username does not exist!", + "passwdInvalid": "Password incorrect!" + }, + "window": { + "title": "Login window", + "uname": "Username: ", + "login": "Login", + "passwd": "Password: ", + "register": "Register", + "logout": "Logout" + } + } + }, + "zh_TW": { + "homepage": { + "userNamePrompt": "您的用戶名: ", + "title": "本地聊天伺服器", + "description": "該網絡伺服器應用程式專爲Edexcel Level 3 EPQ而製作", + "copyrightText": "版權所有 2024 - 2025 Zhongheng Liu @ Byron College", + "switchLang": "切換語言", + "newLangPrompt": "在下方輸入您想使用的語言的ISO-639_ISO-3166組合語言代碼 - 例如:\"en_US\":" + }, + "chat": { + "window": { + "title": "當前以 $userName 登入" + }, + "sendButtonPrompt": "發送", + "serverMessage": "來自 $userName 的訊息:$content", + "joinMessage": "$userName 加入了伺服器!" + }, + "login": { + "error": { + "unameTakenOrInvalid": "用戶名已存在或不合規!", + "unameNotExists": "該用戶不存在!", + "passwdInvalid": "密碼不正確!" + }, + "window": { + "title": "登入窗口", + "uname": "用戶名:", + "login": "登入", + "passwd": "密碼:", + "register": "註冊", + "logout": "登出" + } + } + }, + "el_GR": { + "homepage": { + "userNamePrompt": "το όνομα χρήστη σας: ", + "title": "Διακομιστής τοπικού δικτύου συνομιλίας", + "description": "Αυτή η διαδικτυακή εφαρμογή δημιουργήθηκε για τους σκοπούς ενός έργου EPQ.", + "copyrightText": "Πνευματικά δικαιώματα 2024 - 2025 Zhongheng Liu @ Byron College", + "switchLang": "Αλλαγή γλώσσας", + "newLangPrompt": "Εισαγάγετε τον κωδικό γλώσσας-χώρας ISO-639 ISO-3166 παρακάτω - για παράδειγμα: \"en_US\":" + }, + "chat": { + "window": { + "title": "Συνδεδεμένος ως $userName" + }, + "sendButtonPrompt": "Στείλετε", + "joinMessage": "$userName έγινε μέλος του διακομιστή", + "serverMessage": "μήνυμα από $userName: $content" + }, + "login": { + "error": { + "unameTakenOrInvalid": "Το όνομα χρήστη έχει ληφθεί ή δεν είναι έγκυρο!", + "unameNotExists": "Το όνομα χρήστη δεν υπάρχει!", + "passwdInvalid": "Λάθος κωδικός!" + }, + "window": { + "title": "Παράθυρο σύνδεσης", + "uname": "Όνομα χρήστη: ", + "login": "Σύνδεση", + "passwd": "Κωδικός πρόσβασης: ", + "register": "Εγγραφή", + "logout": "Αποσύνδεση" + } + } + }, + "ar_SA": { + "homepage": { + "userNamePrompt": "اسم المستخدم الخاص بك:", + "title": "خادم الدردشة LAN", + "description": "تم إنشاء تطبيق الويب هذا لأغراض مشروع EPQ.", + "copyrightText": "حقوق الطبع والنشر 2024 - 2025 Zhongheng Liu @ كلية بايرون", + "switchLang": "تبديل اللغة", + "newLangPrompt": "أدخل رمز اللغة ISO-639_ISO-3166 أدناه - على سبيل المثال: \"en_US\":" + }, + "chat": { + "window": { + "title": "Logged in as $userName" + }, + "sendButtonPrompt": "يرسل", + "joinMessage": "$userName انضم إلى الخادم", + "serverMessage": "رسالة من $userName: $content" + }, + "login": { + "error": { + "unameTakenOrInvalid": "Username is taken or invalid!", + "unameNotExists": "Username does not exist!", + "passwdInvalid": "Password incorrect!" + }, + "window": { + "title": "Login window", + "uname": "Username: ", + "login": "Login", + "passwd": "Password: ", + "register": "Register", + "logout": "Logout" + } + } + } } diff --git a/src/Login/Login.tsx b/src/Login/Login.tsx index 7f5ff79..6518eff 100644 --- a/src/Login/Login.tsx +++ b/src/Login/Login.tsx @@ -1,8 +1,9 @@ -import { useState } from "react"; +import { useContext, useState } from "react"; import { contentTypes, domain, endpoints, port } from "../consts"; -import { LoginType } from "../context"; +import { LangContext, LoginType } from "../context"; import { User } from "../type/userTypes"; import "./Login.css"; +import strings from "../Intl/strings.json"; const encrypt = (rawPasswordString: string) => { // TODO Encryption method stub return rawPasswordString; @@ -12,8 +13,10 @@ export const Login = ({ }: { setLogin: (newLogin: LoginType | undefined) => void; }): React.ReactElement => { - const [valid, setValid] = useState(true); + const [valid, setValid] = useState(undefined); const [validText, setValidText] = useState(); + const lang = useContext(LangContext); + const loginPage = strings[lang].login; const registrationHandler = () => { const uname = ( document.getElementById("username") as HTMLInputElement @@ -36,7 +39,9 @@ export const Login = ({ // 400 Bad request console.log("Username is taken or invalid!"); setValid(false); - setValidText("Username is taken or invalid!"); + setValidText( + loginPage.error.unameTakenOrInvalid + ); } else if (response.status === 200) { // 200 OK const futureDate = new Date(); @@ -73,7 +78,7 @@ export const Login = ({ "404 not found encountered" ); throw new Error( - "Username does not exist" + loginPage.error.unameNotExists ); } else if (res.status === 200) { console.log("200 OK"); @@ -88,9 +93,12 @@ export const Login = ({ const validLogin = passwd === user.passwordHash; if (!validLogin) { // login invalid - throw new Error("Password incorrect!"); + throw new Error( + loginPage.error.passwdInvalid + ); } else { // login valid + setValid(true); const validUntilDate: Date = new Date(); validUntilDate.setHours( validUntilDate.getHours() + 2 @@ -111,47 +119,51 @@ export const Login = ({ return (
- Login window + {loginPage.window.title}

- {valid && valid !== undefined - ? "" - : validText} + {!valid && valid !== undefined + ? validText + : ""}

- +

- +

- +
diff --git a/src/MessageDisplay/MessageDisplay.css b/src/MessageDisplay/MessageDisplay.css index 15af7eb..7ca7722 100644 --- a/src/MessageDisplay/MessageDisplay.css +++ b/src/MessageDisplay/MessageDisplay.css @@ -1,3 +1,3 @@ .msg .msg-err { - overflow-wrap: break-word; -} \ No newline at end of file + overflow-wrap: break-word; +} diff --git a/src/MessageDisplay/MessageDisplay.tsx b/src/MessageDisplay/MessageDisplay.tsx index 92e9e79..18a9277 100644 --- a/src/MessageDisplay/MessageDisplay.tsx +++ b/src/MessageDisplay/MessageDisplay.tsx @@ -3,6 +3,7 @@ import { Message, MessageType } from "../type/messageTypes"; import { LangContext } from "../context"; import strings from "../Intl/strings.json"; import "./MessageDisplay.css"; +import { queryByRole } from "@testing-library/react"; export const MessageDisplay = ({ type, fromUserId, diff --git a/src/MessageDisplay/messageHandler.tsx b/src/MessageDisplay/messageHandler.tsx new file mode 100644 index 0000000..801692e --- /dev/null +++ b/src/MessageDisplay/messageHandler.tsx @@ -0,0 +1,5 @@ +import {Message, MessageType } from "../type/messageTypes" +export const dataMsgHandler = (msg: Message) => { + if (msg.type !== MessageType.DATA) {return <>} +} +export const chnameMsgHandler = () => {} diff --git a/src/index.css b/src/index.css index ec2585e..a158d83 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,13 @@ body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } diff --git a/src/type/userTypes.ts b/src/type/userTypes.ts index b888745..d5c1e37 100644 --- a/src/type/userTypes.ts +++ b/src/type/userTypes.ts @@ -1,7 +1,13 @@ +import { URL } from "url"; + +export type UserAvatar = { + iconUrls: URL[]; +}; export type User = { id: number; userName: string; dateJoined: number; lastSeen: number; passwordHash: string; + // avatar: UserAvatar; }; From 47b0925db94e56c114f2d4b22d352e9f553fa1b7 Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Wed, 31 Jan 2024 21:05:44 +0200 Subject: [PATCH 04/13] Update TODO with things to do --- TODO.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b7d50d8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,10 @@ +# TODOs for the project +## UI/UX +1. Beautify interface +2. Implement avatar support +## Cybersecurity +1. Implement SSH-based encryption/decryption in transmission +Client 1 => encrypt with pubcert_s => Server => decrypt with privcert_s => encrypt with pubcert_2 => Client 2 => decrypt with privcert2 +plaintext => ciphertext => ciphertext => plaintext => ciphertext => ciphertext => plaintext +Clients need to have server's pubcert, +Server needs to contain pubcerts of each client, From 7e5f5bdf774a2f3b5d65b90c55ced6553419a960 Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Wed, 31 Jan 2024 21:05:57 +0200 Subject: [PATCH 05/13] Removed copyright statement temporarily --- public/index.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/index.html b/public/index.html index 3fd63e5..835317f 100644 --- a/public/index.html +++ b/public/index.html @@ -29,9 +29,6 @@
-