Added support for CORS and integrated login handler

This commit is contained in:
Zhongheng Liu 2024-01-19 20:59:30 +02:00
commit fdcd08dc50
No known key found for this signature in database
4 changed files with 125 additions and 43 deletions

View file

@ -1,8 +1,8 @@
import React, { createContext, useContext, useState } from "react";
import React, { useContext, useEffect, useState } from "react";
import Chat from "./Chat/Chat";
import "./App.css";
import { LangType, Message } from "./Chat/messageTypes";
import { MessageContainer } from "./Chat/MessageContainer";
import { MessageDisplay } from "./Chat/MessageDisplay";
import strings from "./Intl/strings.json";
import { LangContext, LoginContext, LoginType } from "./context";
import { contentTypes, domain, endpoints, port } from "./consts";
@ -11,19 +11,31 @@ import { Login } from "./Login/Login";
const Wrapper = (): React.ReactElement => {
const [lang, setLang] = useState<LangType>("en_US");
const [login, setLogin] = useState<LoginType | undefined>(undefined);
useEffect(() => {
document.title = login
? `IRC logged in as ${login.username}`
: "IRC Chat";
}, [login]);
return (
<LangContext.Provider value={lang}>
<h1>{strings[lang].homepage.title}</h1>
<p>{strings[lang].homepage.description}</p>
<LoginContext.Provider value={login}>
{/* callbacks for altering the Lang/Login contexts */}
<Login
setLogin={(value) => {
setLogin(value);
}}
></Login>
<App
changeLang={(value: string) => {
setLang(value as LangType);
}}
/>
{login ? (
<App
changeLang={(value: string) => {
setLang(value as LangType);
}}
/>
) : (
<></>
)}
</LoginContext.Provider>
</LangContext.Provider>
);
@ -33,6 +45,7 @@ const setNameOnServer = async (name: string) => {
`http://${domain}:${port}${endpoints.user}`,
{
method: "POST",
mode: "cors",
headers: contentTypes.json,
body: JSON.stringify({
userName: name,
@ -77,23 +90,20 @@ const App = ({
}
});
};
if (!username) {
var newName = prompt(home.userNamePrompt) as string;
while (!validateName(newName)) {
console.log(newName);
// 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);
}
// prompt("Username invalid! Please enter again.") as string;
// }
// setUsername(newName);
// }
if (!login) {
return <></>;
} else
return (
<div className="App">
<h1>{home.title}</h1>
<pre>{home.description}</pre>
<h3>Your name is: {username}</h3>
<button
onClick={(ev) => {
const selection = prompt(home.newLangPrompt);
@ -130,9 +140,9 @@ const App = ({
Change Username
</button>
{messages.map((message) => {
return <MessageContainer {...message} />;
return <MessageDisplay {...message} />;
})}
{<Chat user={username as string} />}
{<Chat user={login.username as string} />}
</div>
);
};

View file

@ -5,7 +5,7 @@ import React, {
useRef,
useState,
} from "react";
import { MessageContainer } from "./MessageContainer";
import { MessageDisplay } from "./MessageDisplay";
import { Client } from "@stomp/stompjs";
import { Message, MessageType } from "./messageTypes";
import "./Chat.css";
@ -29,7 +29,7 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
console.log(messageBody);
setMessages((message) => {
return message.concat([
<MessageContainer
<MessageDisplay
key={`${messageBody.type}@${messageBody.timeMillis}`}
{...messageBody}
/>,
@ -99,8 +99,23 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
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 (
<div className="chat">
<fieldset className="chat">
<legend>
Logged in as <b>{user}</b>
</legend>
<div className="chat-inner-wrapper">{messages}</div>
<span className="entry-box">
<input id="data-entry"></input>
@ -108,7 +123,7 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
{chatPage.sendButtonPrompt}
</button>
</span>
</div>
</fieldset>
);
};
export default Chat;

View file

@ -2,7 +2,8 @@ import React, { useContext } from "react";
import { Message, MessageType } from "./messageTypes";
import { LangContext } from "../context";
import strings from "../Intl/strings.json";
export const MessageContainer = ({
import "./MessageDisplay.css";
export const MessageDisplay = ({
type,
fromUserId,
toUserId,
@ -21,19 +22,23 @@ export const MessageContainer = ({
* 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 (
<p>
[{dateTime.toLocaleString()}]{" "}
<p className="msg">
[{timeString}]{" "}
{msgPage.joinMessage.replace("$userName", fromUserId)}
</p>
);
case MessageType.MESSAGE as MessageType:
return (
<p>
[{dateTime.toLocaleString()}]{" "}
<p className="msg">
[{timeString}]{" "}
{msgPage.serverMessage
.replace("$userName", fromUserId)
.replace("$content", content)}
@ -46,9 +51,9 @@ export const MessageContainer = ({
default:
console.error("Illegal MessageType reported!");
return (
<p>
[{dateTime.toLocaleString()}] **THIS MESSAGE CANNOT BE
CORRECTLY SHOWN BECAUSE THE CLIENT ENCOUNTERED AN ERROR**
<p className="msg-err">
[{timeString}] **THIS MESSAGE CANNOT BE CORRECTLY SHOWN
BECAUSE THE CLIENT ENCOUNTERED AN ERROR**
</p>
);
}

View file

@ -2,6 +2,7 @@ import { useState } from "react";
import { contentTypes, domain, endpoints, port } from "../consts";
import { LoginType } from "../context";
import { User } from "../Chat/userTypes";
import "./Login.css";
const encrypt = (rawPasswordString: string) => {
// TODO Encryption method stub
return rawPasswordString;
@ -9,9 +10,10 @@ const encrypt = (rawPasswordString: string) => {
export const Login = ({
setLogin,
}: {
setLogin: (newLogin: LoginType) => void;
setLogin: (newLogin: LoginType | undefined) => void;
}): React.ReactElement => {
const [valid, setValid] = useState(true);
const [valid, setValid] = useState<boolean | undefined>(true);
const [validText, setValidText] = useState<string | undefined>();
const registrationHandler = () => {
const uname = (document.getElementById("username") as HTMLInputElement)
.value;
@ -20,6 +22,7 @@ export const Login = ({
);
fetch(`http://${domain}:${port}${endpoints.user}`, {
method: "POST",
mode: "cors",
headers: contentTypes.json,
body: JSON.stringify({
userName: uname,
@ -28,8 +31,12 @@ export const Login = ({
}),
}).then((response) => {
if (response.status === 400) {
// 400 Bad request
console.log("Username is taken or invalid!");
} else {
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({
@ -37,6 +44,7 @@ export const Login = ({
lastSeen: Date.now(),
validUntil: futureDate.getUTCMilliseconds(),
});
document.title = `IRC User ${uname}`;
}
});
};
@ -48,16 +56,28 @@ export const Login = ({
(document.getElementById("passwd") as HTMLInputElement).value
);
// async invocation of Fetch API
fetch(`http://${domain}:${port}${endpoints.user}?user=${uname}`, {
fetch(`http://${domain}:${port}${endpoints.user}?name=${uname}`, {
method: "GET",
mode: "cors",
})
.then((res) => res.json())
.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
setValid(false); // triggers page re-render -- should refresh the page
throw new Error("Password incorrect!");
} else {
// login valid
const validUntilDate: Date = new Date();
@ -67,22 +87,54 @@ export const Login = ({
lastSeen: user.lastSeen,
validUntil: validUntilDate.getUTCMilliseconds(),
});
document.title = `IRC User ${uname}`;
}
})
.catch((reason: Error) => {
setValid(false);
setValidText(reason.message);
});
};
return (
<div>
<div className="login">
<fieldset>
<legend>Login window</legend>
<p className="uname-error-text">
{valid ? "Error in your username or password" : ""}
{valid && valid !== undefined ? "" : validText}
</p>
<label htmlFor="username">Username: </label>
<br />
<input id="username" type="text"></input>
<br />
<label htmlFor="passwd">Password: </label>
<br />
<input id="passwd" type="password"></input>
<button type="submit">Login</button>
<button type="submit">Register</button>
<br />
<button
type="submit"
onClick={() => {
loginHandler();
}}
>
Login
</button>
<button
type="submit"
onClick={() => {
registrationHandler();
}}
>
Register
</button>
<button
type="submit"
onClick={() => {
setLogin(undefined);
setValid(false);
}}
>
Logout
</button>
</fieldset>
</div>
);