diff --git a/bridge/config.yml b/bridge/config.yml index aa4ba1c..de51fee 100644 --- a/bridge/config.yml +++ b/bridge/config.yml @@ -1,2 +1,2 @@ allowedServers: - - "127.0.0.1:16000" + - "195.201.123.169:16000" diff --git a/bridge/go.mod b/bridge/go.mod index 66322d0..710c045 100644 --- a/bridge/go.mod +++ b/bridge/go.mod @@ -3,7 +3,7 @@ module git.vh7.uk/jakew/echo-web/bridge go 1.18 require ( - git.vh7.uk/jakew/echo-go v0.0.0-20220803185705-357282c0e444 + git.vh7.uk/jakew/echo-go v0.0.0-20220805101505-158189ae7d58 github.com/gorilla/websocket v1.5.0 github.com/rs/zerolog v1.27.0 github.com/samber/lo v1.27.0 diff --git a/bridge/go.sum b/bridge/go.sum index 0081e39..a79757f 100644 --- a/bridge/go.sum +++ b/bridge/go.sum @@ -1,5 +1,7 @@ git.vh7.uk/jakew/echo-go v0.0.0-20220803185705-357282c0e444 h1:/GiYRoIHnYqoF9bAHwYXlLCy4praom2KlwBed8pW3ZU= git.vh7.uk/jakew/echo-go v0.0.0-20220803185705-357282c0e444/go.mod h1:Q4YqOodoX+qYvfM0gSf78cCk0o7dkg1GLNYUFgB6qhU= +git.vh7.uk/jakew/echo-go v0.0.0-20220805101505-158189ae7d58 h1:gMa91PLcG4jRunbntD/YSt/7U3eSyAHUNxIdWu4ELjU= +git.vh7.uk/jakew/echo-go v0.0.0-20220805101505-158189ae7d58/go.mod h1:Q4YqOodoX+qYvfM0gSf78cCk0o7dkg1GLNYUFgB6qhU= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/bridge/main.go b/bridge/main.go index 93bbeaa..ad2b1af 100644 --- a/bridge/main.go +++ b/bridge/main.go @@ -1,15 +1,17 @@ package main import ( + "encoding/json" + "io/ioutil" + "net/http" + "os" + echo "git.vh7.uk/jakew/echo-go" "github.com/gorilla/websocket" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/samber/lo" "gopkg.in/yaml.v3" - "io/ioutil" - "net/http" - "os" ) type Config struct { @@ -26,6 +28,27 @@ var upgrader = websocket.Upgrader{ }, } +func (h *websocketHandler) receiver(conn *websocket.Conn, client *echo.Client) { + defer conn.Close() + + for { + msgs, err := client.Receive() + if err != nil { + log.Error().Err(err).Msg("failed to receive from server") + return + } + + for _, msg := range msgs { + log.Info().Interface("msg", msg).Msg("received message") + bytes, err := json.Marshal(&msg) + if err != nil { + log.Error().Err(err).Msg("failed to marshal message") + } + conn.WriteMessage(websocket.TextMessage, bytes) + } + } +} + func (h *websocketHandler) socketHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { @@ -40,7 +63,7 @@ func (h *websocketHandler) socketHandler(w http.ResponseWriter, r *http.Request) username := r.URL.Query().Get("username") password := r.URL.Query().Get("password") - if !lo.Contains[string](h.config.AllowedServers, server) { + if !lo.Contains(h.config.AllowedServers, server) { _ = conn.WriteMessage(websocket.TextMessage, []byte("{\"error\": \"Server not allowed\"}")) return } @@ -54,34 +77,37 @@ func (h *websocketHandler) socketHandler(w http.ResponseWriter, r *http.Request) } defer client.Disconnect() - err = client.HandshakeLoop(password) + err = client.HandshakeLoop("4.0.0", password) if err != nil { log.Error().Err(err).Msg("failed to handshake with server") _ = conn.WriteMessage(websocket.TextMessage, []byte("{\"error\": \"Failed to connect\"}")) return } + go h.receiver(conn, client) + for { messageType, message, err := conn.ReadMessage() if err != nil { log.Warn().Err(err).Msg("failed to receive websocket message") - break + return } if messageType == websocket.CloseMessage { + log.Debug().Msg("websocket closed") break } else if messageType == websocket.TextMessage { - err = conn.WriteMessage(messageType, message) + var echoMessage echo.RawMessage + err = json.Unmarshal(message, &echoMessage) if err != nil { - log.Warn().Err(err).Msg("failed to send websocket message") + log.Warn().Err(err).Msg("failed to unmarshal message") break } + client.Send(echoMessage.MessageType, echoMessage.Data, echoMessage.SubType, []string{}) } else { log.Warn().Int("type", messageType).Bytes("message", message).Msg("message type not implemented") } } - - log.Info().Str("address", conn.RemoteAddr().String()).Msg("websocket connection closed") } func main() { diff --git a/frontend/package.json b/frontend/package.json index 41d1706..d4a3d52 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,14 +15,18 @@ "@mantine/hooks": "^5.0.2", "@mantine/modals": "^5.0.2", "@mantine/notifications": "^5.0.2", + "@reduxjs/toolkit": "^1.8.3", "dayjs": "^1.11.4", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-feather": "^2.0.10" + "react-feather": "^2.0.10", + "react-redux": "^8.0.2", + "ws": "^8.8.1" }, "devDependencies": { "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", + "@types/ws": "^8.5.3", "@vitejs/plugin-react": "^2.0.0", "typescript": "^4.6.4", "vite": "^3.0.0" diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index fc0bce9..60b9248 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -3,11 +3,15 @@ import ReactDOM from 'react-dom/client' import { MantineProvider } from '@mantine/core'; import App from './App' import './index.css'; +import { Provider } from 'react-redux'; +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 new file mode 100644 index 0000000..df6ae29 --- /dev/null +++ b/frontend/src/middleware/chat.tsx @@ -0,0 +1,24 @@ +import { Middleware } from "@reduxjs/toolkit"; +import WebSocket from 'ws'; +import { chatActions } from "../slices/chat"; + +const chatMiddleware: Middleware = (store) => (next) => (action) => { + if (!chatActions.startConnecting.match(action)) { + return 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 new file mode 100644 index 0000000..f19169a --- /dev/null +++ b/frontend/src/slices/chat.tsx @@ -0,0 +1,36 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface ChatState { + messages: any[]; + isEstablishingConnection: boolean; + isConnected: boolean; +} + +const initialState: ChatState = { + messages: [], + isEstablishingConnection: false, + isConnected: false +}; + +const chatSlice = createSlice({ + name: "chat", + initialState, + reducers: { + startConnecting: (state) => { + state.isEstablishingConnection = true; + }, + connectionEstablished: (state) => { + state.isConnected = true; + state.isEstablishingConnection = true; + }, + recieveMessage: (state, action: PayloadAction<{ + message: any + }>) => { + state.messages.push(action.payload.message); + } + } +}); + +export const chatActions = chatSlice.actions; + +export default chatSlice; diff --git a/frontend/src/store.ts b/frontend/src/store.ts new file mode 100644 index 0000000..bada669 --- /dev/null +++ b/frontend/src/store.ts @@ -0,0 +1,5 @@ +import { configureStore } from '@reduxjs/toolkit'; + +export default configureStore({ + reducer: {} +}); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9afe5ac..98148c6 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -206,7 +206,7 @@ "@babel/plugin-syntax-jsx" "^7.18.6" "@babel/types" "^7.18.10" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== @@ -545,6 +545,29 @@ dependencies: "@babel/runtime" "^7.13.10" +"@reduxjs/toolkit@^1.8.3": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.8.3.tgz#9c6a9c497bde43a67618d37a4175a00ae12efeb2" + integrity sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg== + dependencies: + immer "^9.0.7" + redux "^4.1.2" + redux-thunk "^2.4.1" + reselect "^4.1.5" + +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/node@*": + version "18.6.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.4.tgz#fd26723a8a3f8f46729812a7f9b4fc2d1608ed39" + integrity sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -576,6 +599,18 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + +"@types/ws@^8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@vitejs/plugin-react@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-2.0.0.tgz#12decd097773a00620e44b780b1d2c00df101449" @@ -897,13 +932,18 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: react-is "^16.7.0" +immer@^9.0.7: + version "9.0.15" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.15.tgz#0b9169e5b1d22137aba7d43f8a81a495dd1b62dc" + integrity sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ== + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -1053,6 +1093,23 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +react-redux@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.2.tgz#bc2a304bb21e79c6808e3e47c50fe1caf62f7aad" + integrity sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA== + dependencies: + "@babel/runtime" "^7.12.1" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/use-sync-external-store" "^0.0.3" + hoist-non-react-statics "^3.3.2" + react-is "^18.0.0" + use-sync-external-store "^1.0.0" + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -1084,11 +1141,28 @@ react@^18.2.0: dependencies: loose-envify "^1.1.0" +redux-thunk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== + +redux@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" + integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== + dependencies: + "@babel/runtime" "^7.9.2" + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +reselect@^4.1.5: + version "4.1.6" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.6.tgz#19ca2d3d0b35373a74dc1c98692cdaffb6602656" + integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -1199,6 +1273,11 @@ use-latest@^1.2.1: dependencies: use-isomorphic-layout-effect "^1.1.1" +use-sync-external-store@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + vite@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.4.tgz#c61688d6b97573e96cf5ac25f2d68597b5ce68e8" @@ -1211,6 +1290,11 @@ vite@^3.0.0: optionalDependencies: fsevents "~2.3.2" +ws@^8.8.1: + version "8.8.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0" + integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA== + yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"