diff --git a/bridge/config.yml b/bridge/config.yml
index de51fee..aa4ba1c 100644
--- a/bridge/config.yml
+++ b/bridge/config.yml
@@ -1,2 +1,2 @@
allowedServers:
- - "195.201.123.169:16000"
+ - "127.0.0.1:16000"
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 7dc5839..41c050d 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,12 +1,26 @@
-import { AppShell, Box, Button, Grid, Group, Input, Stack } from "@mantine/core"
+import { AppShell, Box, Button, Grid, Group, Input, Stack, TextInput } from "@mantine/core"
import { useState } from "react"
+import { useDispatch, useSelector } from "react-redux"
import ChannelList from "./components/ChannelList"
import Header from "./components/Header"
import MemberList from "./components/MemberList"
import Message from "./components/Message"
+import { chatActions } from "./slices/chat"
+import { RootState } from "./store"
function App() {
const [opened, setOpened] = useState(false);
+ const [message, setMessage] = useState("");
+ const dispatch = useDispatch();
+ const currentChannel = useSelector((state: RootState) => state.chat.currentChannel);
+ const messages = useSelector((state: RootState) => state.chat.messages);
+
+ const chatDisabled = currentChannel === null;
+
+ const sendMessage = () => {
+ dispatch(chatActions.sendMessage({ content: message }));
+ setMessage("");
+ }
return (
}
aside={}
>
-
-
- {[...Array(10)].map((e, i) => (
-
+
+
+ {messages.map((msg, i) => (
+
))}
- ({ borderTop: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]}`})}>
-
-
+ ({ borderTop: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]}`, flexGrow: 0, flexShrink: 0 })}>
+ setMessage(event.target.value)} onKeyUp={(event) => {
+ if (event.key === "Enter") {
+ sendMessage();
+ }
+ }}/>
+
diff --git a/frontend/src/components/ChannelList.tsx b/frontend/src/components/ChannelList.tsx
index ac601f2..afd7319 100644
--- a/frontend/src/components/ChannelList.tsx
+++ b/frontend/src/components/ChannelList.tsx
@@ -1,18 +1,49 @@
-import { ActionIcon, Box, Button, Group, Navbar, NavLink, Stack } from "@mantine/core";
+import { Box, Button, Navbar, NavLink, Stack } from "@mantine/core";
+import { Root } from "react-dom/client";
import { Hash, Settings } from "react-feather";
+import { useDispatch, useSelector } from "react-redux";
+import { chatActions } from "../slices/chat";
+import { RootState } from "../store";
function ChannelList(props: { hidden: boolean }) {
+ const dispatch = useDispatch();
+ const isConnected = useSelector((state: RootState) => state.chat.isConnected);
+ const isConnecting = useSelector((state: RootState) => state.chat.isEstablishingConnection);
+ const channels = useSelector((state: RootState) => state.chat.channels);
+ const currentChannel = useSelector((state: RootState) => state.chat.currentChannel);
+
+ const connectText = isConnected ? 'Disconnect' : 'Connect';
+ const connectClick = () => {
+ if (isConnected) {
+ dispatch(chatActions.disconnect());
+ } else {
+ dispatch(chatActions.connect());
+ }
+ }
+
return (
- {[...Array(4)].map((e, i) => (
- }/>
+ {channels.map((channel) => (
+ }
+ active={channel === currentChannel}
+ onClick={() => dispatch(chatActions.changeChannel({ channel }))}
+ />
))}
-
+
diff --git a/frontend/src/components/MemberList.tsx b/frontend/src/components/MemberList.tsx
index 9241b91..e1bd797 100644
--- a/frontend/src/components/MemberList.tsx
+++ b/frontend/src/components/MemberList.tsx
@@ -1,11 +1,15 @@
import { Aside, MediaQuery, Navbar, NavLink } from "@mantine/core"
+import { useSelector } from "react-redux"
+import { RootState } from "../store"
function MemberList() {
+ const users = useSelector((state: RootState) => state.chat.users);
+
return (
diff --git a/frontend/src/components/Message.tsx b/frontend/src/components/Message.tsx
index 3e68adc..a60a15e 100644
--- a/frontend/src/components/Message.tsx
+++ b/frontend/src/components/Message.tsx
@@ -1,17 +1,22 @@
import { Avatar, Box, Group, Stack, Text } from "@mantine/core"
+import { ChatMessage } from "../middleware/chat"
-function Message() {
+interface MessageProps {
+ message: ChatMessage
+}
+
+function Message(props: MessageProps) {
return (
- LS
+ LS
- Author
+ {props.message.author}
{' '}
- at 4:13pm
+ at {props.message.date}
- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus nemo nisi saepe quasi eaque laudantium autem aliquid eligendi, nihil aut id sunt iusto nulla. Modi ut provident obcaecati quibusdam veritatis?
+ {props.message.content}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 60b9248..7b7ebcd 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -4,7 +4,7 @@ import { MantineProvider } from '@mantine/core';
import App from './App'
import './index.css';
import { Provider } from 'react-redux';
-import store from './store';
+import { store } from './store';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
diff --git a/frontend/src/middleware/chat.tsx b/frontend/src/middleware/chat.tsx
index df6ae29..32772b9 100644
--- a/frontend/src/middleware/chat.tsx
+++ b/frontend/src/middleware/chat.tsx
@@ -1,24 +1,147 @@
import { Middleware } from "@reduxjs/toolkit";
-import WebSocket from 'ws';
-import { chatActions } from "../slices/chat";
+import chat, { chatActions } from "../slices/chat";
-const chatMiddleware: Middleware = (store) => (next) => (action) => {
- if (!chatActions.startConnecting.match(action)) {
- return next(action);
+enum MessageType {
+ ReqServerInfo = "serverInfoRequest",
+ ReqClientSecret = "clientSecret",
+ ReqConnection = "connectionRequest",
+ ReqDisconnect = "disconnect",
+ ReqInfo = "requestInfo",
+ ReqUserMessage = "userMessage",
+ ReqChangeChannel = "changeChannel",
+ ReqHistory = "historyRequest",
+ ReqLeaveChannel = "leaveChannel",
+
+ ResServerData = "serverData",
+ ResClientSecret = "gotSecret",
+ ResConnectionAccepted = "CRAccepted",
+ ResConnectionDenied = "CRDenied",
+ ResOutboundMessage = "outboundMessage",
+ ResConnectionTerminated = "connectionTerminated",
+ ResChannelUpdate = "channelUpdate",
+ ResCommandData = "commandData",
+ ResChannelHistory = "channelHistory",
+ ResErrorOccurred = "errorOccured",
+ ResAdditionalHistory = "additionalHistory"
+}
+
+interface ServerMessage {
+ userid?: string
+ messagetype: MessageType;
+ subtype?: string;
+ data: any;
+ metadata?: string;
+}
+
+export interface ChatMessage {
+ content: string;
+ author: string;
+ color: string;
+ date: number;
+}
+
+const defaultMetadata = ['Unknown', '#ffffff', '0'];
+
+const chatMiddleware: Middleware = (store) => {
+ let socket: WebSocket | null = null;
+
+ return (next) => (action) => {
+ if (chatActions.connect.match(action) && socket === null) {
+ socket = new WebSocket("ws://127.0.0.1:4000?server=127.0.0.1:16000&username=jake&password=password");
+
+ socket.onopen = () => {
+ store.dispatch(chatActions.connected());
+ socket?.send(JSON.stringify({
+ messagetype: MessageType.ReqInfo
+ }));
+ }
+
+ socket.onmessage = (event) => {
+ const data = JSON.parse(event.data);
+ const msg: ServerMessage = {
+ userid: data.userid || '0',
+ messagetype: data.messagetype,
+ subtype: data.subtype,
+ data: "",
+ metadata: "",
+ };
+
+ try {
+ msg.data = JSON.parse(data.data);
+ } catch (SyntaxError) {
+ msg.data = data.data;
+ }
+
+ try {
+ msg.metadata = JSON.parse(data.metadata);
+ } catch (SyntaxError) {
+ msg.metadata = data.metadata;
+ }
+
+ console.log(msg);
+
+ switch (msg.messagetype) {
+ case MessageType.ResServerData:
+ store.dispatch(chatActions.updateChannels({ channels: JSON.parse(msg.data[0]) }));
+ store.dispatch(chatActions.updateMotd({ motd: msg.data[1] }));
+ store.dispatch(chatActions.updateUsers({ users: JSON.parse(msg.data[2]).map((user) => {
+ return user[0];
+ })}));
+ break;
+ case MessageType.ResOutboundMessage:
+ let metadata = msg.metadata || defaultMetadata;
+ store.dispatch(chatActions.recieveMessage({ message: {
+ author: metadata[0],
+ content: msg.data,
+ color: metadata[1],
+ date: 0
+ }}))
+ break;
+ case MessageType.ResCommandData:
+ metadata = msg.metadata || defaultMetadata;
+ store.dispatch(chatActions.recieveMessage({ message: {
+ author: metadata[0],
+ content: msg.data,
+ color: metadata[1],
+ date: 0
+ }}));
+ break;
+ default:
+ store.dispatch(chatActions.recieveMessage({ message: {
+ author: 'Server',
+ content: JSON.stringify(msg),
+ color: '#ff0000',
+ date: 0
+ }}));
+ }
+ }
+
+ socket.onclose = () => {
+ store.dispatch(chatActions.disconnected());
+ }
+
+ socket.onerror = (event) => {
+ console.error('ws error', event);
+ }
+ } else if (chatActions.disconnect.match(action) && socket !== null) {
+ socket.close();
+ socket = null;
+ } else if (chatActions.changeChannel.match(action) && socket !== null) {
+ const changeChannelMessage: ServerMessage = {
+ messagetype: MessageType.ReqChangeChannel,
+ data: action.payload.channel
+ }
+ socket.send(JSON.stringify(changeChannelMessage));
+ } else if (chatActions.sendMessage.match(action) && socket !== null) {
+ const sendMessage: ServerMessage = {
+ messagetype: MessageType.ReqUserMessage,
+ data: action.payload.content
+ }
+ socket.send(JSON.stringify(sendMessage));
+ }
+
+ next(action);
}
-
- const socket = new WebSocket("ws://127.0.0.1:4000?server=195.201.123.169:16000&username=jake");
-
- socket.on('connect', () => {
- console.log('Connected to WebSocket!');
- store.dispatch(chatActions.connectionEstablished());
- });
-
- socket.on('message', (message: any) => {
- store.dispatch(chatActions.recieveMessage({ message }))
- });
-
- next(action);
};
export default chatMiddleware;
diff --git a/frontend/src/slices/chat.tsx b/frontend/src/slices/chat.tsx
index f19169a..f46d762 100644
--- a/frontend/src/slices/chat.tsx
+++ b/frontend/src/slices/chat.tsx
@@ -1,36 +1,78 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+import { ChatMessage } from "../middleware/chat";
export interface ChatState {
- messages: any[];
+ messages: ChatMessage[];
+ users: string[];
+ channels: string[];
+ motd: string | null;
isEstablishingConnection: boolean;
isConnected: boolean;
+ currentChannel: string | null;
}
const initialState: ChatState = {
messages: [],
+ users: [],
+ channels: [],
+ motd: null,
isEstablishingConnection: false,
- isConnected: false
+ isConnected: false,
+ currentChannel: null
};
const chatSlice = createSlice({
name: "chat",
initialState,
reducers: {
- startConnecting: (state) => {
+ connect: (state) => {
+ state.isConnected = false;
state.isEstablishingConnection = true;
},
- connectionEstablished: (state) => {
+ connected: (state) => {
state.isConnected = true;
state.isEstablishingConnection = true;
},
+ disconnect: (state) => {
+ state.isConnected = false;
+ state.isEstablishingConnection = true;
+ },
+ disconnected: (state) => {
+ state.isConnected = false;
+ state.isEstablishingConnection = false;
+ },
recieveMessage: (state, action: PayloadAction<{
- message: any
+ message: ChatMessage
}>) => {
state.messages.push(action.payload.message);
- }
+ },
+ updateChannels: (state, action: PayloadAction<{
+ channels: string[]
+ }>) => {
+ state.channels = action.payload.channels;
+ },
+ updateMotd: (state, action: PayloadAction<{
+ motd: string
+ }>) => {
+ state.motd = action.payload.motd;
+ },
+ updateUsers: (state, action: PayloadAction<{
+ users: string[]
+ }>) => {
+ state.users = action.payload.users;
+ },
+ changeChannel: (state, action: PayloadAction<{
+ channel: string
+ }>) => {
+ state.currentChannel = action.payload.channel;
+ state.messages = [];
+ },
+ sendMessage: (state, action: PayloadAction<{
+ content: string
+ }>) => {}
}
});
export const chatActions = chatSlice.actions;
-export default chatSlice;
+export default chatSlice.reducer;
diff --git a/frontend/src/store.ts b/frontend/src/store.ts
index bada669..752d5e9 100644
--- a/frontend/src/store.ts
+++ b/frontend/src/store.ts
@@ -1,5 +1,15 @@
import { configureStore } from '@reduxjs/toolkit';
+import chatMiddleware from './middleware/chat';
+import chatSlice from './slices/chat';
-export default configureStore({
- reducer: {}
+export const store = configureStore({
+ reducer: {
+ chat: chatSlice
+ },
+ middleware: (getDefaultMiddleware) => {
+ return getDefaultMiddleware().concat([chatMiddleware])
+ }
});
+
+export type RootState = ReturnType
+export type AppDispatch = typeof store.dispatch