fix(striker-ui): auto-reconnect VNC upon failure

main
Tsu-ba-me 1 year ago committed by digimer
parent a1a00e375b
commit e63fef101f
  1. 118
      striker-ui/components/Display/FullSize.tsx
  2. 28
      striker-ui/components/Display/VncDisplay.tsx
  3. 9
      striker-ui/types/VncDisplay.d.ts

@ -16,14 +16,12 @@ import { useState, useEffect, FC, useMemo, useRef, useCallback } from 'react';
import { TEXT } from '../../lib/consts/DEFAULT_THEME'; import { TEXT } from '../../lib/consts/DEFAULT_THEME';
import ContainedButton from '../ContainedButton';
import IconButton from '../IconButton'; import IconButton from '../IconButton';
import keyCombinations from './keyCombinations'; import keyCombinations from './keyCombinations';
import { Panel, PanelHeader } from '../Panels'; import { Panel, PanelHeader } from '../Panels';
import Spinner from '../Spinner'; import Spinner from '../Spinner';
import { HeaderText } from '../Text'; import { HeaderText } from '../Text';
import useIsFirstRender from '../../hooks/useIsFirstRender'; import useIsFirstRender from '../../hooks/useIsFirstRender';
import useProtectedState from '../../hooks/useProtectedState';
const PREFIX = 'FullSize'; const PREFIX = 'FullSize';
@ -68,6 +66,7 @@ type FullSizeOptionalProps = {
}; };
type FullSizeProps = FullSizeOptionalProps & { type FullSizeProps = FullSizeOptionalProps & {
vncReconnectTimerStart: number;
serverUUID: string; serverUUID: string;
serverName: string | string[] | undefined; serverName: string | string[] | undefined;
}; };
@ -78,6 +77,8 @@ const FULL_SIZE_DEFAULT_PROPS: Required<
Pick<FullSizeOptionalProps, 'onClickCloseButton'> = { Pick<FullSizeOptionalProps, 'onClickCloseButton'> = {
onClickCloseButton: undefined, onClickCloseButton: undefined,
}; };
// Unit: seconds
const DEFAULT_VNC_RECONNECT_TIMER_START = 5;
const buildServerVncUrl = (hostname: string, serverUuid: string) => const buildServerVncUrl = (hostname: string, serverUuid: string) =>
`ws://${hostname}/ws/server/vnc/${serverUuid}`; `ws://${hostname}/ws/server/vnc/${serverUuid}`;
@ -86,16 +87,19 @@ const FullSize: FC<FullSizeProps> = ({
onClickCloseButton, onClickCloseButton,
serverUUID, serverUUID,
serverName, serverName,
vncReconnectTimerStart = DEFAULT_VNC_RECONNECT_TIMER_START,
}): JSX.Element => { }): JSX.Element => {
const isFirstRender = useIsFirstRender(); const isFirstRender = useIsFirstRender();
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const [rfbConnectArgs, setRfbConnectArgs] = useState<
const [rfbConnectArgs, setRfbConnectArgs] = useProtectedState< Partial<RfbConnectArgs> | undefined
RfbConnectArgs | undefined
>(undefined); >(undefined);
const [vncConnecting, setVncConnecting] = useProtectedState<boolean>(false); const [vncConnecting, setVncConnecting] = useState<boolean>(false);
const [vncError, setVncError] = useProtectedState<boolean>(false); const [vncError, setVncError] = useState<boolean>(false);
const [vncReconnectTimer, setVncReconnectTimer] = useState<number>(
vncReconnectTimerStart,
);
const rfb = useRef<typeof RFB | null>(null); const rfb = useRef<typeof RFB | null>(null);
const rfbScreen = useRef<HTMLDivElement | null>(null); const rfbScreen = useRef<HTMLDivElement | null>(null);
@ -124,46 +128,18 @@ const FullSize: FC<FullSizeProps> = ({
} }
}; };
// 'connect' event emits when a connection successfully completes.
const rfbConnectEventHandler = useCallback(() => {
setVncConnecting(false);
}, [setVncConnecting]);
// 'disconnect' event emits when a connection fails,
// OR when a user closes the existing connection.
const rfbDisconnectEventHandler = useCallback(
({ detail: { clean } }) => {
if (!clean) {
setVncConnecting(false);
setVncError(true);
}
},
[setVncConnecting, setVncError],
);
const connectServerVnc = useCallback(() => { const connectServerVnc = useCallback(() => {
setVncConnecting(true); setVncConnecting(true);
setVncError(false); setVncError(false);
setRfbConnectArgs({ setRfbConnectArgs({
onConnect: rfbConnectEventHandler,
onDisconnect: rfbDisconnectEventHandler,
rfb,
rfbScreen,
url: buildServerVncUrl(window.location.hostname, serverUUID), url: buildServerVncUrl(window.location.hostname, serverUUID),
}); });
}, [ }, [serverUUID]);
rfbConnectEventHandler,
rfbDisconnectEventHandler,
serverUUID,
setRfbConnectArgs,
setVncConnecting,
setVncError,
]);
const disconnectServerVnc = useCallback(() => { const disconnectServerVnc = useCallback(() => {
setRfbConnectArgs(undefined); setRfbConnectArgs(undefined);
}, [setRfbConnectArgs]); }, []);
const reconnectServerVnc = useCallback(() => { const reconnectServerVnc = useCallback(() => {
if (!rfb?.current) return; if (!rfb?.current) return;
@ -174,6 +150,39 @@ const FullSize: FC<FullSizeProps> = ({
connectServerVnc(); connectServerVnc();
}, [connectServerVnc]); }, [connectServerVnc]);
const updateVncReconnectTimer = useCallback((): void => {
const intervalId = setInterval((): void => {
setVncReconnectTimer((previous) => {
const current = previous - 1;
if (current < 1) {
clearInterval(intervalId);
}
return current;
});
}, 1000);
}, []);
// 'connect' event emits when a connection successfully completes.
const rfbConnectEventHandler = useCallback(() => {
setVncConnecting(false);
}, []);
// 'disconnect' event emits when a connection fails,
// OR when a user closes the existing connection.
const rfbDisconnectEventHandler = useCallback(
({ detail: { clean } }) => {
if (!clean) {
setVncConnecting(false);
setVncError(true);
updateVncReconnectTimer();
}
},
[updateVncReconnectTimer],
);
const showScreen = useMemo( const showScreen = useMemo(
() => !vncConnecting && !vncError, () => !vncConnecting && !vncError,
[vncConnecting, vncError], [vncConnecting, vncError],
@ -232,6 +241,14 @@ const FullSize: FC<FullSizeProps> = ({
[keyboardMenuElement, showScreen, vncDisconnectElement], [keyboardMenuElement, showScreen, vncDisconnectElement],
); );
useEffect(() => {
if (vncReconnectTimer === 0) {
setVncReconnectTimer(vncReconnectTimerStart);
reconnectServerVnc();
}
}, [reconnectServerVnc, vncReconnectTimer, vncReconnectTimerStart]);
useEffect(() => { useEffect(() => {
if (isFirstRender) { if (isFirstRender) {
connectServerVnc(); connectServerVnc();
@ -250,8 +267,10 @@ const FullSize: FC<FullSizeProps> = ({
className={classes.displayBox} className={classes.displayBox}
> >
<VncDisplay <VncDisplay
onConnect={rfbConnectEventHandler}
onDisconnect={rfbDisconnectEventHandler}
rfb={rfb} rfb={rfb}
rfbConnectPartialArgs={rfbConnectArgs} rfbConnectArgs={rfbConnectArgs}
rfbScreen={rfbScreen} rfbScreen={rfbScreen}
/> />
</Box> </Box>
@ -259,25 +278,20 @@ const FullSize: FC<FullSizeProps> = ({
<Box display="flex" className={classes.spinnerBox}> <Box display="flex" className={classes.spinnerBox}>
{vncConnecting && ( {vncConnecting && (
<> <>
<HeaderText>Connecting to {serverName}...</HeaderText> <HeaderText textAlign="center">
Connecting to {serverName}.
</HeaderText>
<Spinner /> <Spinner />
</> </>
)} )}
{vncError && ( {vncError && (
<> <>
<Box style={{ paddingBottom: '2em' }}> <HeaderText textAlign="center">
<HeaderText textAlign="center"> There was a problem connecting to the server.
There was a problem connecting to the server, please try </HeaderText>
again <HeaderText textAlign="center" mt="1em">
</HeaderText> Retrying in {vncReconnectTimer}.
</Box> </HeaderText>
<ContainedButton
onClick={() => {
reconnectServerVnc();
}}
>
Reconnect
</ContainedButton>
</> </>
)} )}
</Box> </Box>

@ -55,15 +55,35 @@ const rfbDisconnect: RfbDisconnectFunction = (rfb) => {
}; };
const VncDisplay = (props: VncDisplayProps): JSX.Element => { const VncDisplay = (props: VncDisplayProps): JSX.Element => {
const { rfb, rfbConnectPartialArgs, rfbScreen } = props; const {
onConnect,
onDisconnect,
rfb,
rfbConnectArgs,
rfbScreen,
url: initUrl,
} = props;
useEffect(() => { useEffect(() => {
if (rfbConnectPartialArgs) { if (rfbConnectArgs) {
rfbConnect({ rfb, rfbScreen, ...rfbConnectPartialArgs }); const { url = initUrl } = rfbConnectArgs;
if (!url) return;
const args: RfbConnectArgs = {
onConnect,
onDisconnect,
rfb,
rfbScreen,
url,
...rfbConnectArgs,
};
rfbConnect(args);
} else { } else {
rfbDisconnect(rfb); rfbDisconnect(rfb);
} }
}, [rfb, rfbConnectPartialArgs, rfbScreen]); }, [initUrl, onConnect, onDisconnect, rfb, rfbConnectArgs, rfbScreen]);
useEffect( useEffect(
() => () => { () => () => {

@ -26,8 +26,7 @@ type RfbConnectFunction = (args: RfbConnectArgs) => void;
type RfbDisconnectFunction = (rfb: RfbRef) => void; type RfbDisconnectFunction = (rfb: RfbRef) => void;
type VncDisplayProps = { type VncDisplayProps = Pick<RfbConnectArgs, 'rfb' | 'rfbScreen'> &
rfb: RfbRef; Partial<RfbConnectArgs> & {
rfbConnectPartialArgs?: Omit<RfbConnectArgs, 'rfb' | 'rfbScreen'>; rfbConnectArgs?: Partial<RfbConnectArgs>;
rfbScreen: RfbScreenRef; };
};

Loading…
Cancel
Save