diff --git a/package.json b/package.json index 3dacba4bb..509b7825c 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "classnames": "^2.2.6", "date-fns": "^1.29.0", "easy-peasy": "^2.5.0", + "events": "^3.0.0", "feather-icons": "^4.10.0", "formik": "^1.5.7", "jquery": "^3.3.1", @@ -35,6 +36,7 @@ "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", "@types/classnames": "^2.2.8", + "@types/events": "^3.0.0", "@types/feather-icons": "^4.7.0", "@types/lodash": "^4.14.119", "@types/query-string": "^6.3.0", diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 0fd1f0114..7f74a64e8 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -1,13 +1,13 @@ import React, { useEffect } from 'react'; import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationState } from '@/state/types'; -import Sockette from 'sockette'; +import { Websocket } from '@/plugins/Websocket'; export default () => { const server = useStoreState((state: State) => state.server.data); const instance = useStoreState((state: State) => state.server.socket.instance); - const setInstance = useStoreActions((actions: Actions) => actions.server.socket.setInstance); - const setConnectionState = useStoreActions((actions: Actions) => actions.server.socket.setConnectionState); + const setServerStatus = useStoreActions((actions: Actions) => actions.server.setServerStatus); + const { setInstance, setConnectionState } = useStoreActions((actions: Actions) => actions.server.socket); useEffect(() => { // If there is already an instance or there is no server, just exit out of this process @@ -16,19 +16,20 @@ export default () => { return; } - console.log('need to connect to instance'); - const socket = new Sockette(`wss://wings.pterodactyl.test:8080/api/servers/${server.uuid}/ws`, { - protocols: 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA', - // onmessage: (ev) => console.log(ev), - onopen: () => setConnectionState(true), - onclose: () => setConnectionState(false), - onerror: () => setConnectionState(false), - }); + console.log('Connecting!'); - console.log('Setting instance!'); + const socket = new Websocket( + `wss://wings.pterodactyl.test:8080/api/servers/${server.uuid}/ws`, + 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA' + ); + + socket.on('SOCKET_OPEN', () => setConnectionState(true)); + socket.on('SOCKET_CLOSE', () => setConnectionState(false)); + socket.on('SOCKET_ERROR', () => setConnectionState(false)); + socket.on('status', (status) => setServerStatus(status)); setInstance(socket); - }, [server]); + }, [ server ]); return null; }; diff --git a/resources/scripts/plugins/Websocket.ts b/resources/scripts/plugins/Websocket.ts new file mode 100644 index 000000000..d558856d3 --- /dev/null +++ b/resources/scripts/plugins/Websocket.ts @@ -0,0 +1,53 @@ +import Sockette from 'sockette'; +import { EventEmitter } from 'events'; + +export const SOCKET_EVENTS = [ + 'SOCKET_OPEN', + 'SOCKET_RECONNECT', + 'SOCKET_CLOSE', + 'SOCKET_ERROR', +]; + +export class Websocket extends EventEmitter { + socket: Sockette; + + constructor (url: string, protocol: string) { + super(); + + this.socket = new Sockette(url, { + protocols: protocol, + onmessage: e => { + try { + let { event, args } = JSON.parse(e.data); + this.emit(event, ...args); + } catch (ex) { + console.warn('Failed to parse incoming websocket message.', ex); + } + }, + onopen: () => this.emit('SOCKET_OPEN'), + onreconnect: () => this.emit('SOCKET_RECONNECT'), + onclose: () => this.emit('SOCKET_CLOSE'), + onerror: () => this.emit('SOCKET_ERROR'), + }); + } + + close (code?: number, reason?: string) { + this.socket.close(code, reason); + } + + open () { + this.socket.open(); + } + + json (data: any) { + this.socket.json(data); + } + + reconnect () { + this.socket.reconnect(); + } + + send (data: any) { + this.socket.send(data); + } +} diff --git a/resources/scripts/state/models/server.ts b/resources/scripts/state/models/server.ts index 1c6539eac..86ae301b3 100644 --- a/resources/scripts/state/models/server.ts +++ b/resources/scripts/state/models/server.ts @@ -2,16 +2,21 @@ import getServer, { Server } from '@/api/server/getServer'; import { action, Action, thunk, Thunk } from 'easy-peasy'; import socket, { SocketState } from './socket'; +export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'online'; + export interface ServerState { data?: Server; + status: ServerStatus; socket: SocketState; getServer: Thunk>; setServer: Action; + setServerStatus: Action; clearServerState: Action; } const server: ServerState = { socket, + status: 'offline', getServer: thunk(async (actions, payload) => { const server = await getServer(payload); actions.setServer(server); @@ -19,10 +24,14 @@ const server: ServerState = { setServer: action((state, payload) => { state.data = payload; }), + setServerStatus: action((state, payload) => { + state.status = payload; + }), clearServerState: action(state => { state.data = undefined; if (state.socket.instance) { + state.socket.instance.removeAllListeners(); state.socket.instance.close(); } diff --git a/resources/scripts/state/models/socket.ts b/resources/scripts/state/models/socket.ts index fb5082acb..10922feda 100644 --- a/resources/scripts/state/models/socket.ts +++ b/resources/scripts/state/models/socket.ts @@ -1,10 +1,10 @@ import { Action, action } from 'easy-peasy'; -import Sockette from 'sockette'; +import { Websocket } from '@/plugins/Websocket'; export interface SocketState { - instance: Sockette | null; + instance: Websocket | null; connected: boolean; - setInstance: Action; + setInstance: Action; setConnectionState: Action; } diff --git a/yarn.lock b/yarn.lock index 102a9feff..6609f8f34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -766,6 +766,10 @@ version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" +"@types/events@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + "@types/feather-icons@^4.7.0": version "4.7.0" resolved "https://registry.yarnpkg.com/@types/feather-icons/-/feather-icons-4.7.0.tgz#ec66bc046bcd1513835f87541ecef54b50c57ec9" @@ -3059,6 +3063,10 @@ events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" +events@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" + eventsource@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"