Added support for CORS and integrated login handler
This commit is contained in:
parent
27db90cd58
commit
fdcd08dc50
4 changed files with 125 additions and 43 deletions
50
src/App.tsx
50
src/App.tsx
|
@ -1,8 +1,8 @@
|
||||||
import React, { createContext, useContext, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import Chat from "./Chat/Chat";
|
import Chat from "./Chat/Chat";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { LangType, Message } from "./Chat/messageTypes";
|
import { LangType, Message } from "./Chat/messageTypes";
|
||||||
import { MessageContainer } from "./Chat/MessageContainer";
|
import { MessageDisplay } from "./Chat/MessageDisplay";
|
||||||
import strings from "./Intl/strings.json";
|
import strings from "./Intl/strings.json";
|
||||||
import { LangContext, LoginContext, LoginType } from "./context";
|
import { LangContext, LoginContext, LoginType } from "./context";
|
||||||
import { contentTypes, domain, endpoints, port } from "./consts";
|
import { contentTypes, domain, endpoints, port } from "./consts";
|
||||||
|
@ -11,19 +11,31 @@ import { Login } from "./Login/Login";
|
||||||
const Wrapper = (): React.ReactElement => {
|
const Wrapper = (): React.ReactElement => {
|
||||||
const [lang, setLang] = useState<LangType>("en_US");
|
const [lang, setLang] = useState<LangType>("en_US");
|
||||||
const [login, setLogin] = useState<LoginType | undefined>(undefined);
|
const [login, setLogin] = useState<LoginType | undefined>(undefined);
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = login
|
||||||
|
? `IRC logged in as ${login.username}`
|
||||||
|
: "IRC Chat";
|
||||||
|
}, [login]);
|
||||||
return (
|
return (
|
||||||
<LangContext.Provider value={lang}>
|
<LangContext.Provider value={lang}>
|
||||||
|
<h1>{strings[lang].homepage.title}</h1>
|
||||||
|
<p>{strings[lang].homepage.description}</p>
|
||||||
<LoginContext.Provider value={login}>
|
<LoginContext.Provider value={login}>
|
||||||
|
{/* callbacks for altering the Lang/Login contexts */}
|
||||||
<Login
|
<Login
|
||||||
setLogin={(value) => {
|
setLogin={(value) => {
|
||||||
setLogin(value);
|
setLogin(value);
|
||||||
}}
|
}}
|
||||||
></Login>
|
></Login>
|
||||||
<App
|
{login ? (
|
||||||
changeLang={(value: string) => {
|
<App
|
||||||
setLang(value as LangType);
|
changeLang={(value: string) => {
|
||||||
}}
|
setLang(value as LangType);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</LoginContext.Provider>
|
</LoginContext.Provider>
|
||||||
</LangContext.Provider>
|
</LangContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -33,6 +45,7 @@ const setNameOnServer = async (name: string) => {
|
||||||
`http://${domain}:${port}${endpoints.user}`,
|
`http://${domain}:${port}${endpoints.user}`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
mode: "cors",
|
||||||
headers: contentTypes.json,
|
headers: contentTypes.json,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userName: name,
|
userName: name,
|
||||||
|
@ -77,23 +90,20 @@ const App = ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if (!username) {
|
// if (!username) {
|
||||||
var newName = prompt(home.userNamePrompt) as string;
|
// var newName = prompt(home.userNamePrompt) as string;
|
||||||
while (!validateName(newName)) {
|
// while (!validateName(newName)) {
|
||||||
console.log(newName);
|
// console.log(newName);
|
||||||
|
|
||||||
prompt("Username invalid! Please enter again.") as string;
|
// prompt("Username invalid! Please enter again.") as string;
|
||||||
}
|
// }
|
||||||
setUsername(newName);
|
// setUsername(newName);
|
||||||
}
|
// }
|
||||||
if (!login) {
|
if (!login) {
|
||||||
return <></>;
|
return <></>;
|
||||||
} else
|
} else
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<h1>{home.title}</h1>
|
|
||||||
<pre>{home.description}</pre>
|
|
||||||
<h3>Your name is: {username}</h3>
|
|
||||||
<button
|
<button
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
const selection = prompt(home.newLangPrompt);
|
const selection = prompt(home.newLangPrompt);
|
||||||
|
@ -130,9 +140,9 @@ const App = ({
|
||||||
Change Username
|
Change Username
|
||||||
</button>
|
</button>
|
||||||
{messages.map((message) => {
|
{messages.map((message) => {
|
||||||
return <MessageContainer {...message} />;
|
return <MessageDisplay {...message} />;
|
||||||
})}
|
})}
|
||||||
{<Chat user={username as string} />}
|
{<Chat user={login.username as string} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { MessageContainer } from "./MessageContainer";
|
import { MessageDisplay } from "./MessageDisplay";
|
||||||
import { Client } from "@stomp/stompjs";
|
import { Client } from "@stomp/stompjs";
|
||||||
import { Message, MessageType } from "./messageTypes";
|
import { Message, MessageType } from "./messageTypes";
|
||||||
import "./Chat.css";
|
import "./Chat.css";
|
||||||
|
@ -29,7 +29,7 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
|
||||||
console.log(messageBody);
|
console.log(messageBody);
|
||||||
setMessages((message) => {
|
setMessages((message) => {
|
||||||
return message.concat([
|
return message.concat([
|
||||||
<MessageContainer
|
<MessageDisplay
|
||||||
key={`${messageBody.type}@${messageBody.timeMillis}`}
|
key={`${messageBody.type}@${messageBody.timeMillis}`}
|
||||||
{...messageBody}
|
{...messageBody}
|
||||||
/>,
|
/>,
|
||||||
|
@ -99,8 +99,23 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
|
||||||
sendData();
|
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 (
|
return (
|
||||||
<div className="chat">
|
<fieldset className="chat">
|
||||||
|
<legend>
|
||||||
|
Logged in as <b>{user}</b>
|
||||||
|
</legend>
|
||||||
<div className="chat-inner-wrapper">{messages}</div>
|
<div className="chat-inner-wrapper">{messages}</div>
|
||||||
<span className="entry-box">
|
<span className="entry-box">
|
||||||
<input id="data-entry"></input>
|
<input id="data-entry"></input>
|
||||||
|
@ -108,7 +123,7 @@ const Chat = ({ user }: { user: string }): React.ReactElement => {
|
||||||
{chatPage.sendButtonPrompt}
|
{chatPage.sendButtonPrompt}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</fieldset>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Chat;
|
export default Chat;
|
||||||
|
|
|
@ -2,7 +2,8 @@ import React, { useContext } from "react";
|
||||||
import { Message, MessageType } from "./messageTypes";
|
import { Message, MessageType } from "./messageTypes";
|
||||||
import { LangContext } from "../context";
|
import { LangContext } from "../context";
|
||||||
import strings from "../Intl/strings.json";
|
import strings from "../Intl/strings.json";
|
||||||
export const MessageContainer = ({
|
import "./MessageDisplay.css";
|
||||||
|
export const MessageDisplay = ({
|
||||||
type,
|
type,
|
||||||
fromUserId,
|
fromUserId,
|
||||||
toUserId,
|
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.
|
* This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db.
|
||||||
* Funny clown moment 🤡
|
* Funny clown moment 🤡
|
||||||
*/
|
*/
|
||||||
|
const timeString = `${
|
||||||
|
dateTime.getHours() > 12
|
||||||
|
? dateTime.getHours() - 12
|
||||||
|
: dateTime.getHours()
|
||||||
|
}:${dateTime.getMinutes()} ${dateTime.getHours() > 12 ? "PM" : "AM"}`;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MessageType.HELLO as MessageType:
|
case MessageType.HELLO as MessageType:
|
||||||
return (
|
return (
|
||||||
<p>
|
<p className="msg">
|
||||||
[{dateTime.toLocaleString()}]{" "}
|
[{timeString}]{" "}
|
||||||
{msgPage.joinMessage.replace("$userName", fromUserId)}
|
{msgPage.joinMessage.replace("$userName", fromUserId)}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
case MessageType.MESSAGE as MessageType:
|
case MessageType.MESSAGE as MessageType:
|
||||||
return (
|
return (
|
||||||
<p>
|
<p className="msg">
|
||||||
[{dateTime.toLocaleString()}]{" "}
|
[{timeString}]{" "}
|
||||||
{msgPage.serverMessage
|
{msgPage.serverMessage
|
||||||
.replace("$userName", fromUserId)
|
.replace("$userName", fromUserId)
|
||||||
.replace("$content", content)}
|
.replace("$content", content)}
|
||||||
|
@ -46,9 +51,9 @@ export const MessageContainer = ({
|
||||||
default:
|
default:
|
||||||
console.error("Illegal MessageType reported!");
|
console.error("Illegal MessageType reported!");
|
||||||
return (
|
return (
|
||||||
<p>
|
<p className="msg-err">
|
||||||
[{dateTime.toLocaleString()}] **THIS MESSAGE CANNOT BE
|
[{timeString}] **THIS MESSAGE CANNOT BE CORRECTLY SHOWN
|
||||||
CORRECTLY SHOWN BECAUSE THE CLIENT ENCOUNTERED AN ERROR**
|
BECAUSE THE CLIENT ENCOUNTERED AN ERROR**
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
||||||
import { contentTypes, domain, endpoints, port } from "../consts";
|
import { contentTypes, domain, endpoints, port } from "../consts";
|
||||||
import { LoginType } from "../context";
|
import { LoginType } from "../context";
|
||||||
import { User } from "../Chat/userTypes";
|
import { User } from "../Chat/userTypes";
|
||||||
|
import "./Login.css";
|
||||||
const encrypt = (rawPasswordString: string) => {
|
const encrypt = (rawPasswordString: string) => {
|
||||||
// TODO Encryption method stub
|
// TODO Encryption method stub
|
||||||
return rawPasswordString;
|
return rawPasswordString;
|
||||||
|
@ -9,9 +10,10 @@ const encrypt = (rawPasswordString: string) => {
|
||||||
export const Login = ({
|
export const Login = ({
|
||||||
setLogin,
|
setLogin,
|
||||||
}: {
|
}: {
|
||||||
setLogin: (newLogin: LoginType) => void;
|
setLogin: (newLogin: LoginType | undefined) => void;
|
||||||
}): React.ReactElement => {
|
}): React.ReactElement => {
|
||||||
const [valid, setValid] = useState(true);
|
const [valid, setValid] = useState<boolean | undefined>(true);
|
||||||
|
const [validText, setValidText] = useState<string | undefined>();
|
||||||
const registrationHandler = () => {
|
const registrationHandler = () => {
|
||||||
const uname = (document.getElementById("username") as HTMLInputElement)
|
const uname = (document.getElementById("username") as HTMLInputElement)
|
||||||
.value;
|
.value;
|
||||||
|
@ -20,6 +22,7 @@ export const Login = ({
|
||||||
);
|
);
|
||||||
fetch(`http://${domain}:${port}${endpoints.user}`, {
|
fetch(`http://${domain}:${port}${endpoints.user}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
mode: "cors",
|
||||||
headers: contentTypes.json,
|
headers: contentTypes.json,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userName: uname,
|
userName: uname,
|
||||||
|
@ -28,8 +31,12 @@ export const Login = ({
|
||||||
}),
|
}),
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
if (response.status === 400) {
|
if (response.status === 400) {
|
||||||
|
// 400 Bad request
|
||||||
console.log("Username is taken or invalid!");
|
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();
|
const futureDate = new Date();
|
||||||
futureDate.setHours(futureDate.getHours() + 2);
|
futureDate.setHours(futureDate.getHours() + 2);
|
||||||
setLogin({
|
setLogin({
|
||||||
|
@ -37,6 +44,7 @@ export const Login = ({
|
||||||
lastSeen: Date.now(),
|
lastSeen: Date.now(),
|
||||||
validUntil: futureDate.getUTCMilliseconds(),
|
validUntil: futureDate.getUTCMilliseconds(),
|
||||||
});
|
});
|
||||||
|
document.title = `IRC User ${uname}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -48,16 +56,28 @@ export const Login = ({
|
||||||
(document.getElementById("passwd") as HTMLInputElement).value
|
(document.getElementById("passwd") as HTMLInputElement).value
|
||||||
);
|
);
|
||||||
// async invocation of Fetch API
|
// async invocation of Fetch API
|
||||||
fetch(`http://${domain}:${port}${endpoints.user}?user=${uname}`, {
|
fetch(`http://${domain}:${port}${endpoints.user}?name=${uname}`, {
|
||||||
method: "GET",
|
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) => {
|
.then((userObject) => {
|
||||||
|
if (!userObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const user = userObject as User;
|
const user = userObject as User;
|
||||||
const validLogin = passwd === user.passwordHash;
|
const validLogin = passwd === user.passwordHash;
|
||||||
if (!validLogin) {
|
if (!validLogin) {
|
||||||
// login invalid
|
// login invalid
|
||||||
setValid(false); // triggers page re-render -- should refresh the page
|
throw new Error("Password incorrect!");
|
||||||
} else {
|
} else {
|
||||||
// login valid
|
// login valid
|
||||||
const validUntilDate: Date = new Date();
|
const validUntilDate: Date = new Date();
|
||||||
|
@ -67,22 +87,54 @@ export const Login = ({
|
||||||
lastSeen: user.lastSeen,
|
lastSeen: user.lastSeen,
|
||||||
validUntil: validUntilDate.getUTCMilliseconds(),
|
validUntil: validUntilDate.getUTCMilliseconds(),
|
||||||
});
|
});
|
||||||
|
document.title = `IRC User ${uname}`;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((reason: Error) => {
|
||||||
|
setValid(false);
|
||||||
|
setValidText(reason.message);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="login">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Login window</legend>
|
<legend>Login window</legend>
|
||||||
<p className="uname-error-text">
|
<p className="uname-error-text">
|
||||||
{valid ? "Error in your username or password" : ""}
|
{valid && valid !== undefined ? "" : validText}
|
||||||
</p>
|
</p>
|
||||||
<label htmlFor="username">Username: </label>
|
<label htmlFor="username">Username: </label>
|
||||||
|
<br />
|
||||||
<input id="username" type="text"></input>
|
<input id="username" type="text"></input>
|
||||||
|
<br />
|
||||||
<label htmlFor="passwd">Password: </label>
|
<label htmlFor="passwd">Password: </label>
|
||||||
|
<br />
|
||||||
<input id="passwd" type="password"></input>
|
<input id="passwd" type="password"></input>
|
||||||
<button type="submit">Login</button>
|
<br />
|
||||||
<button type="submit">Register</button>
|
<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>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue