integrate the new gateway, more or less

This commit is contained in:
hippoz 2021-03-27 07:27:57 +02:00
parent e3bb4ce125
commit 6111d1dfa3
Signed by: hippoz
GPG key ID: 7C52899193467641
13 changed files with 370 additions and 679 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,21 +3,20 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",
"@testing-library/user-event": "^12.6.0",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.0.16",
"nord": "^0.2.1",
"react": "^17.0.1",
"react-content-loader": "^5.1.4",
"react-dom": "^17.0.1",
"react": "^17.0.2",
"react-content-loader": "^6.0.2",
"react-dom": "^17.0.2",
"react-media-hook": "^0.4.9",
"react-redux": "^7.2.2",
"react-redux": "^7.2.3",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"react-scripts": "4.0.3",
"redux": "^4.0.5",
"sass": "^1.32.0",
"socket.io-client": "^3.0.5",
"web-vitals": "^0.2.4"
"sass": "^1.32.8",
"web-vitals": "^1.1.1"
},
"scripts": {
"start": "react-scripts start",

View file

@ -1,4 +1,5 @@
import config from '../Config';
import token from "./TokenManager";
async function APIRequest(endpoint, options) {
let res;
@ -9,11 +10,6 @@ async function APIRequest(endpoint, options) {
if (!options) options = {};
if (!options.headers) options.headers = {};
options = {
credentials: 'include',
...options
};
try {
res = await fetch(`${config.apiUrl}${endpoint}`, options);
json = await res.json();
@ -41,12 +37,15 @@ APIRequest.authenticated = async function(endpoint, options) {
if (!options) options = {};
if (!options.headers) options.headers = {};
options = {
credentials: 'include',
...options
console.log(options);
options.headers = {
"Authorization": token.getToken(),
...options.headers
};
try {
console.log(options);
res = await fetch(`${config.apiUrl}${endpoint}`, options);
json = await res.json();
isOK = true;

View file

@ -1,34 +1,15 @@
import APIRequest from './APIRequest';
import gatewayConnection from '../API/Gateway/globalGatewayConnection';
import Logger from '../Util/Logger';
import gateway from "./Gateway/globalGatewayConnection";
const { log: authLog, error: authError } = Logger([ 'Authenticator' ]);
const { log: authLog } = Logger([ 'Authenticator' ]);
const Authenticator = {
getToken: function() {
return localStorage.getItem("token");
},
getLoggedInUserFromCookie: async function() {
authLog('Fetching current logged in user status...');
const { json, isOK, err } = await APIRequest.authenticated('/api/v1/users/current/info');
if (!isOK && err) {
authLog('Exception while fetching current logged in user status', err);
throw new Error(err);
}
if (!isOK && !err) {
authError('User is not authenticated');
return undefined;
}
if (!json || !json.user) {
authError('User is not authenticated');
return undefined;
}
authLog(`Logged in as "${json.user.username || '[undefined username]'}"`);
// NOTE(hippoz): this function has a stupid side-effect but this will have to do for now...
gatewayConnection.connect(json.user.token);
return json.user;
login: async function() {
authLog('Logging in through gateway...');
return gateway.connect();
}
};

View file

@ -1,6 +1,6 @@
import logger from "../../Util/Logger";
const logGateway = logger([ "Gateway" ]);
const { log: logGateway } = logger([ "Gateway" ]);
const opcodes = {
0: { name: "HELLO", data: "JSON" },
@ -46,57 +46,62 @@ const getOpcodeByName = (name) => {
class GatewayConnection {
constructor(token, gatewayUrl) {
this.ws = new WebSocket(gatewayUrl);
this.handshakeCompleted = false;
this.sessionInformation = null;
this.ws.onopen = () => logGateway("Open");
this.ws.onclose = (e) => {
this.handshakeCompleted = false;
logGateway(`Close: ${e.code}:${e.reason}`);
this.fire("onclose", e);
};
this.ws.onmessage = (message) => {
try {
const packet = parseMessage(message.data);
if (!packet) return console.error("gateway: invalid packet from server");
switch (packet.opcodeType) {
case "HELLO": {
// Got HELLO from server, send YOO as soon as possible
logGateway("Got HELLO", packet.data);
logGateway("Sending YOO");
this.ws.send(this.packet("YOO", { token }));
break;
}
case "YOO_ACK": {
// Server accepted connection
logGateway("Got YOO_ACK", packet.data);
this.handshakeCompleted = true;
this.sessionInformation = packet.data;
logGateway("Handshake complete");
this.fire("onopen", packet.data);
break;
}
case "EVENT_CREATE_MESSAGE": {
// New message
// console.log("gateway: got new message", packet.data);
this.fire("onmessage", packet.data);
break;
}
default: {
logGateway("Got unknown packet", message.data);
break;
}
}
} catch(e) {
return console.error("err: gateway:", e);
}
};
this.token = token;
this.gatewayUrl = gatewayUrl;
}
}
GatewayConnection.prototype.connect = function() {
this.ws = new WebSocket(this.gatewayUrl);
this.handshakeCompleted = false;
this.sessionInformation = null;
this.ws.onopen = () => logGateway("Open");
this.ws.onclose = (e) => {
this.handshakeCompleted = false;
logGateway(`Close: ${e.code}:${e.reason}`);
this.fire("onclose", e);
};
this.ws.onmessage = (message) => {
try {
const packet = parseMessage(message.data);
if (!packet) return console.error("gateway: invalid packet from server");
switch (packet.opcodeType) {
case "HELLO": {
// Got HELLO from server, send YOO as soon as possible
logGateway("Got HELLO", packet.data);
logGateway("Sending YOO");
this.ws.send(this.packet("YOO", { token: this.token }));
break;
}
case "YOO_ACK": {
// Server accepted connection
logGateway("Got YOO_ACK", packet.data);
this.handshakeCompleted = true;
this.sessionInformation = packet.data;
logGateway("Handshake complete");
this.fire("onopen", packet.data);
break;
}
case "EVENT_CREATE_MESSAGE": {
// New message
// console.log("gateway: got new message", packet.data);
this.fire("onmessage", packet.data);
break;
}
default: {
logGateway("Got unknown packet", message.data);
break;
}
}
} catch(e) {
return console.error("err: gateway:", e);
}
};
};
GatewayConnection.prototype.sendMessage = function(content, channelId) {
if (!this.sessionInformation) throw new Error("gateway: tried to send message before handshake completion");

View file

@ -1,15 +1,14 @@
import GatewayConnection from './GatewayConnection';
import config from '../../Config';
import Authenticator from '../Authenticator';
import token from '../TokenManager';
import store from '../../Global/store';
console.log(Authenticator);
const globalGatewayConnection = new GatewayConnection(Authenticator.getToken(), config.gatewayUrl);
const globalGatewayConnection = new GatewayConnection(token.getToken(), config.gatewayUrl);
globalGatewayConnection.onopen = (sessionData) => {
store.dispatch({ type: 'gateway/connectionstatus', gateway: { isConnected: true } });
store.dispatch({ type: 'authenticator/updatelocaluserobject', user: sessionData.user });
store.dispatch({ type: 'channels/updatechannellist', user: sessionData.channels })
store.dispatch({ type: 'channels/updatechannellist', channels: sessionData.channels })
};
globalGatewayConnection.onmessage = (message) => {
@ -17,6 +16,7 @@ globalGatewayConnection.onmessage = (message) => {
};
globalGatewayConnection.onclose = function() {
store.dispatch({ type: 'authenticator/updatelocaluserobject', user: undefined });
store.dispatch({ type: 'gateway/connectionstatus', gateway: { isConnected: false } });
};

View file

@ -0,0 +1,6 @@
const Auth = {
getToken: () => localStorage.getItem("token"),
setToken: (token) => localStorage.setItem("token", token)
};
export default Auth;

View file

@ -1,15 +1,14 @@
import { useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import Notification from '../UI/Notification';
import APIRequest from '../../API/APIRequest';
import Authenticator from '../../API/Authenticator';
import token from "../../API/TokenManager";
import { getLoginMessageFromError } from '../../Util/Errors'
export default function Login() {
const history = useHistory();
const dispatch = useDispatch();
const [ usernameInput, setUsernameInput ] = useState();
const [ passwordInput, setPasswordInput ] = useState();
@ -30,8 +29,7 @@ export default function Login() {
},
body: JSON.stringify({
username: usernameInput,
password: passwordInput,
alsoSetCookie: true
password: passwordInput
})
});
@ -43,9 +41,9 @@ export default function Login() {
setInfo('Something went wrong');
return;
}
const res = await Authenticator.getLoggedInUserFromCookie();
dispatch({ type: 'authenticator/updatelocaluserobject', user: res });
token.setToken(json.token);
await Authenticator.login();
history.push('/');
}

View file

@ -10,7 +10,7 @@ export default function ChannelButton({ channel, selected }) {
if (selected) buttonClasses += ' pressed';
const handleClick = () => {
if (gatewayConnection.isConnected) {
if (gatewayConnection.handshakeCompleted) {
history.push(`/channels/${channel._id}`);
}
};

View file

@ -1,65 +1,19 @@
import ChannelListLoader from './ChannelListLoader';
import ChannelButton from './ChannelButton';
import APIRequest from '../../API/APIRequest';
import { couldNotReach } from '../../Util/Errors';
import { connect, useDispatch } from 'react-redux'
import { useState, useEffect } from 'react';
import Logger from '../../Util/Logger';
import gatewayConnection from '../../API/Gateway/globalGatewayConnection';
import { connect } from 'react-redux'
const { log: loaderLog } = Logger([ 'ChannelList', 'Loader' ]);
function ChannelList({ selectedChannelId, gatewayStatus }) {
const [ channelList, setChannelList ] = useState();
const [ error, setError ] = useState();
const dispatch = useDispatch();
useEffect(() => {
loaderLog('Loading ChannelList...');
APIRequest.authenticated('/api/v1/content/channel/list?count=50')
.then(({ isOK, json }) => {
if (!isOK) return setError(true);
loaderLog('Got channel list from server, dispatching...');
setChannelList(json.channels || []);
dispatch({ type: 'channels/updatechannellist', channels: json.channels });
loaderLog('Subscribing to all channels...');
})
.catch(() => {
setError(true);
});
}, [dispatch]);
useEffect(() => {
if (!channelList) return;
if (!gatewayStatus.isConnected) return;
// TODO: IMPORTANT: Subscribing to a lot of channels puts strain on the server
gatewayConnection.subscribeToChannelChats(channelList.map(channel => channel._id));
}, [gatewayStatus, channelList]);
if (!channelList) {
if (error) {
return (
<div>
<p>
{ couldNotReach }
</p>
</div>
);
} else {
return (
<div>
<ChannelListLoader />
</div>
);
}
function ChannelList({ selectedChannelId, channels }) {
if (!channels) {
return (
<div>
<ChannelListLoader />
</div>
);
} else {
return (
<div className="channel-list">
{ channelList.map((channel) => ( <ChannelButton key={ channel._id } channel={ channel } selected={ (channel._id === selectedChannelId) } /> )) }
{ channels.map((channel) => ( <ChannelButton key={ channel._id } channel={ channel } selected={ (channel._id === selectedChannelId) } /> )) }
</div>
);
}
@ -68,7 +22,7 @@ function ChannelList({ selectedChannelId, gatewayStatus }) {
const stateToProps = (state) => {
return {
selectedChannelId: state?.selectedChannelId,
gatewayStatus: state?.gateway,
channels: state?.channels
};
};

View file

@ -26,7 +26,7 @@ function ChannelView({ channels, messages }) {
const handleTextboxKeydown = (e) => {
if (e.key === 'Enter') {
gatewayConnection.sendMessage(id, textInput);
gatewayConnection.sendMessage(textInput, id);
textInputRef.current.value = '';
setTextInput('');
}
@ -36,13 +36,13 @@ function ChannelView({ channels, messages }) {
if (!channels) return;
setChannel(channels.find(x => x._id === id));
}, [ channels, id ]);
}, [channels, id]);
useEffect(() => {
if (!channel) return;
dispatch({ type: 'channels/selectchannel', channelId: channel._id });
}, [ channel, dispatch ]);
}, [channel, dispatch]);
if (channel) {
let messagesView = messages.map(m => <Message key={ m._id } message={ m } />);

View file

@ -3,7 +3,6 @@ import Root from '../Home/Root';
import Authenticator from '../../API/Authenticator';
import Notification from '../UI/Notification';
import './../../Styles/App.scss';
import { couldNotReach } from '../../Util/Errors';
import ChannelView from '../Channels/ChannelView';
import Sidebar from '../UI/Sidebar';
@ -12,20 +11,13 @@ import { useDispatch, connect } from 'react-redux'
import { BrowserRouter, Switch, Route } from 'react-router-dom';
function App({ user }) {
const [ notificationText, setNotificationText ] = useState('');
const [ hasError, setHasError ] = useState(false);
const [ notificationText ] = useState('');
const [ hasError ] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
Authenticator.getLoggedInUserFromCookie()
.then((res) => {
dispatch({ type: 'authenticator/updatelocaluserobject', user: res });
})
.catch(() => {
setNotificationText(couldNotReach);
setHasError(true);
});
Authenticator.login();
}, [dispatch]);
if (user === null && !hasError) {

View file

@ -1,6 +1,6 @@
const config = {
apiUrl: 'http://localhost:3000',
gatewayUrl: '', // Leave blank for it to look for the gateway on the current page, if that makes sense
gatewayUrl: 'ws://localhost:3005/gateway'
};
export default config;