From 0ff59e6d1e719aa14bf8d5cd3a1bbedc4859b553 Mon Sep 17 00:00:00 2001 From: Jeremy Letto Date: Sat, 19 Mar 2022 19:22:00 -0500 Subject: [PATCH] test connection UI (#4596) * test connection UI * cleanup Co-authored-by: Jeremy Letto --- webclient/src/api/AuthenticationService.tsx | 4 + .../src/components/KnownHosts/KnownHosts.css | 30 ++++++ .../src/components/KnownHosts/KnownHosts.tsx | 96 +++++++++++++++---- webclient/src/store/server/server.actions.ts | 6 ++ webclient/src/store/server/server.dispatch.ts | 6 ++ webclient/src/store/server/server.types.ts | 2 + webclient/src/types/server.tsx | 3 +- webclient/src/websocket/WebClient.ts | 6 +- .../src/websocket/commands/SessionCommands.ts | 3 + .../persistence/SessionPersistence.ts | 8 ++ .../websocket/services/WebSocketService.ts | 27 ++++++ 11 files changed, 169 insertions(+), 22 deletions(-) diff --git a/webclient/src/api/AuthenticationService.tsx b/webclient/src/api/AuthenticationService.tsx index 74209499..eb221520 100644 --- a/webclient/src/api/AuthenticationService.tsx +++ b/webclient/src/api/AuthenticationService.tsx @@ -6,6 +6,10 @@ export default class AuthenticationService { SessionCommands.connect(options, WebSocketConnectReason.LOGIN); } + static testConnection(options: WebSocketConnectOptions): void { + SessionCommands.connect(options, WebSocketConnectReason.TEST_CONNECTION); + } + static register(options: WebSocketConnectOptions): void { SessionCommands.connect(options, WebSocketConnectReason.REGISTER); } diff --git a/webclient/src/components/KnownHosts/KnownHosts.css b/webclient/src/components/KnownHosts/KnownHosts.css index e1728226..9c1f7c95 100644 --- a/webclient/src/components/KnownHosts/KnownHosts.css +++ b/webclient/src/components/KnownHosts/KnownHosts.css @@ -1,8 +1,23 @@ .KnownHosts { +} + +.KnownHosts-form { width: 100%; position: relative; } +.KnownHosts-item { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.KnownHosts-item__wrapper { + display: flex; + align-items: center; +} + .KnownHosts-item__label { position: relative; } @@ -16,6 +31,16 @@ font-size: .9em; } +.KnownHosts-item__status { + display: none; +} + +.KnownHosts-item__status svg { + display: none; + margin-left: -5px; + margin-right: 5px; +} + .KnownHosts-validation { position: absolute; top: 0; @@ -33,6 +58,11 @@ margin-left: 4px; } +.KnownHosts .MuiSelect-selectMenu .KnownHosts-item__status, +.KnownHosts .MuiSelect-selectMenu .KnownHosts-item__status svg { + display: block; +} + .Mui-selected .KnownHosts-item__label svg { display: block; } diff --git a/webclient/src/components/KnownHosts/KnownHosts.tsx b/webclient/src/components/KnownHosts/KnownHosts.tsx index 3596f6b9..ebd84a3a 100644 --- a/webclient/src/components/KnownHosts/KnownHosts.tsx +++ b/webclient/src/components/KnownHosts/KnownHosts.tsx @@ -4,6 +4,8 @@ import { Select, MenuItem } from '@material-ui/core'; import Button from '@material-ui/core/Button'; import FormControl from '@material-ui/core/FormControl'; import IconButton from '@material-ui/core/IconButton'; +import WifiTetheringIcon from '@material-ui/icons/WifiTethering'; +import PortableWifiOffIcon from '@material-ui/icons/PortableWifiOff'; import InputLabel from '@material-ui/core/InputLabel'; import { makeStyles } from '@material-ui/core/styles'; import Check from '@material-ui/icons/Check'; @@ -11,13 +13,22 @@ import AddIcon from '@material-ui/icons/Add'; import EditRoundedIcon from '@material-ui/icons/Edit'; import ErrorOutlinedIcon from '@material-ui/icons/ErrorOutlined'; +import { AuthenticationService } from 'api'; import { KnownHostDialog } from 'dialogs'; +import { useReduxEffect } from 'hooks'; import { HostDTO } from 'services'; +import { ServerTypes } from 'store'; import { DefaultHosts, Host, getHostPort } from 'types'; import Toast from 'components/Toast/Toast'; import './KnownHosts.css'; +enum TestConnection { + TESTING = 'testing', + FAILED = 'failed', + SUCCESS = 'success', +} + const useStyles = makeStyles(theme => ({ root: { '& .KnownHosts-error': { @@ -26,6 +37,18 @@ const useStyles = makeStyles(theme => ({ '& .KnownHosts-warning': { color: theme.palette.warning.main + }, + + '& .KnownHosts-item': { + [`& .${TestConnection.TESTING}`]: { + color: theme.palette.warning.main + }, + [`& .${TestConnection.FAILED}`]: { + color: theme.palette.error.main + }, + [`& .${TestConnection.SUCCESS}`]: { + color: theme.palette.success.main + } } }, })); @@ -46,9 +69,11 @@ const KnownHosts = (props) => { edit: null, }); - const [showCreateToast, setShowCreateToast] = useState(false) - const [showDeleteToast, setShowDeleteToast] = useState(false) - const [showEditToast, setShowEditToast] = useState(false) + const [testingConnection, setTestingConnection] = useState(null); + + const [showCreateToast, setShowCreateToast] = useState(false); + const [showDeleteToast, setShowDeleteToast] = useState(false); + const [showEditToast, setShowEditToast] = useState(false); const loadKnownHosts = useCallback(async () => { const hosts = await HostDTO.getAll(); @@ -77,6 +102,14 @@ const KnownHosts = (props) => { } }, [hostsState, onChange]); + useReduxEffect(() => { + setTestingConnection(TestConnection.SUCCESS); + }, ServerTypes.TEST_CONNECTION_SUCCESSFUL, []); + + useReduxEffect(() => { + setTestingConnection(TestConnection.FAILED); + }, ServerTypes.TEST_CONNECTION_FAILED, []); + const selectHost = (selectedHost) => { setHostsState(s => ({ ...s, selectedHost })); }; @@ -135,6 +168,8 @@ const KnownHosts = (props) => { }; const updateLastSelectedHost = (hostId): Promise => { + testConnection(); + return HostDTO.getAll().then(hosts => hosts.map(async host => { if (host.id === hostId) { @@ -152,20 +187,27 @@ const KnownHosts = (props) => { ); }; + const testConnection = () => { + setTestingConnection(TestConnection.TESTING); + + const options = { ...getHostPort(hostsState.selectedHost) }; + AuthenticationService.testConnection(options); + } + return ( -
- +
+ { touched && ( -
+
{ (error && -
+
{error}
) || - (warning &&
{warning}
) + (warning &&
{warning}
) }
) } @@ -189,19 +231,33 @@ const KnownHosts = (props) => { { hostsState.hosts.map((host, index) => ( - -
- - {host.name} ({ getHostPort(hostsState.hosts[index]).host }:{getHostPort(hostsState.hosts[index]).port}) -
+ +
+
+
+
+ { + testingConnection === TestConnection.FAILED + ? + : + } +
+
- { host.editable && ( - { - openEditKnownHostDialog(hostsState.hosts[index]); - }}> - - - ) } +
+ + {host.name} ({ getHostPort(hostsState.hosts[index]).host }:{getHostPort(hostsState.hosts[index]).port}) +
+
+ + { host.editable && ( + { + openEditKnownHostDialog(hostsState.hosts[index]); + }}> + + + ) } +
)) } diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index edc1b9ed..45c4d190 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -24,6 +24,12 @@ export const Actions = { connectionFailed: () => ({ type: Types.CONNECTION_FAILED, }), + testConnectionSuccessful: () => ({ + type: Types.TEST_CONNECTION_SUCCESSFUL, + }), + testConnectionFailed: () => ({ + type: Types.TEST_CONNECTION_FAILED, + }), serverMessage: message => ({ type: Types.SERVER_MESSAGE, message diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index 43bbfa0d..364ad859 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -22,6 +22,12 @@ export const Dispatch = { connectionFailed: () => { store.dispatch(Actions.connectionFailed()); }, + testConnectionSuccessful: () => { + store.dispatch(Actions.testConnectionSuccessful()); + }, + testConnectionFailed: () => { + store.dispatch(Actions.testConnectionFailed()); + }, updateBuddyList: buddyList => { store.dispatch(Actions.updateBuddyList(buddyList)); }, diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index 2e8db280..336560d1 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -5,6 +5,8 @@ export const Types = { LOGIN_FAILED: '[Server] Login Failed', CONNECTION_CLOSED: '[Server] Connection Closed', CONNECTION_FAILED: '[Server] Connection Failed', + TEST_CONNECTION_SUCCESSFUL: '[Server] Test Connection Successful', + TEST_CONNECTION_FAILED: '[Server] Test Connection Failed', SERVER_MESSAGE: '[Server] Server Message', UPDATE_BUDDY_LIST: '[Server] Update Buddy List', ADD_TO_BUDDY_LIST: '[Server] Add to Buddy List', diff --git a/webclient/src/types/server.tsx b/webclient/src/types/server.tsx index 2a302bdc..b2ea881a 100644 --- a/webclient/src/types/server.tsx +++ b/webclient/src/types/server.tsx @@ -35,7 +35,8 @@ export enum WebSocketConnectReason { ACTIVATE_ACCOUNT, PASSWORD_RESET_REQUEST, PASSWORD_RESET_CHALLENGE, - PASSWORD_RESET + PASSWORD_RESET, + TEST_CONNECTION, } export class Host { diff --git a/webclient/src/websocket/WebClient.ts b/webclient/src/websocket/WebClient.ts index 8e706ab8..eb9e0291 100644 --- a/webclient/src/websocket/WebClient.ts +++ b/webclient/src/websocket/WebClient.ts @@ -57,7 +57,11 @@ export class WebClient { public connect(options: WebSocketConnectOptions) { this.connectionAttemptMade = true; this.options = options; - this.socket.connect(this.options); + this.socket.connect(options); + } + + public testConnect(options: WebSocketConnectOptions) { + this.socket.testConnect(options); } public disconnect() { diff --git a/webclient/src/websocket/commands/SessionCommands.ts b/webclient/src/websocket/commands/SessionCommands.ts index 3d9ee2ea..cef45e57 100644 --- a/webclient/src/websocket/commands/SessionCommands.ts +++ b/webclient/src/websocket/commands/SessionCommands.ts @@ -25,6 +25,9 @@ export class SessionCommands { case WebSocketConnectReason.PASSWORD_RESET: SessionCommands.updateStatus(StatusEnum.CONNECTING, 'Connecting...'); break; + case WebSocketConnectReason.TEST_CONNECTION: + webClient.testConnect({ ...options }); + return; default: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason); return; diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 8768cabf..05e335b3 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -29,6 +29,14 @@ export class SessionPersistence { ServerDispatch.connectionFailed(); } + static testConnectionSuccessful() { + ServerDispatch.testConnectionSuccessful(); + } + + static testConnectionFailed() { + ServerDispatch.testConnectionFailed(); + } + static updateBuddyList(buddyList) { ServerDispatch.updateBuddyList(buddyList); } diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts index 1cc67ca3..cf777728 100644 --- a/webclient/src/websocket/services/WebSocketService.ts +++ b/webclient/src/websocket/services/WebSocketService.ts @@ -38,6 +38,16 @@ export class WebSocketService { this.socket = this.createWebSocket(`${protocol}://${host}:${port}`); } + public testConnect(options: WebSocketConnectOptions, protocol: string = 'wss'): void { + if (window.location.hostname === 'localhost') { + protocol = 'ws'; + } + + const { host, port } = options; + + this.testWebSocket(`${protocol}://${host}:${port}`); + } + public disconnect(): void { if (this.socket) { this.socket.close(); @@ -92,4 +102,21 @@ export class WebSocketService { return socket; } + + private testWebSocket(url: string): void { + const socket = new WebSocket(url); + socket.binaryType = 'arraybuffer'; + + const connectionTimer = setTimeout(() => socket.close(), this.webClient.clientOptions.keepalive); + + socket.onopen = () => { + clearTimeout(connectionTimer); + SessionPersistence.testConnectionSuccessful(); + socket.close(); + }; + + socket.onerror = () => { + SessionPersistence.testConnectionFailed(); + }; + } }