Skip to content

Commit

Permalink
Gracefully degrade gamepads in insecure contexts (#72)
Browse files Browse the repository at this point in the history
* Address getGamepads changes

- Added dashboardWarningMessage to Status state
- Display dashboardWarningMessage in OpModeView
- Prevent app from crashing if navigator.getGamepads is not defined
- Display dashboard warning if navigator.getGamepads is not defined

* Unsupported Gamepads now conveyed through icons

- Removed dashboardWarningMethod logic
- Added gamepadsSupported to Status state
- gamepad_not_supported.svg is displayed in place of gamepad.svg when
  gamepads are not supported
- (Questionable) Tooltip logic informing the user that the gamepads are not
  supported.

Co-authored-by: Christian7573 <cag4561@gamil.com>
  • Loading branch information
Christian7573 and Christian7573 authored Jan 11, 2022
1 parent 6d7ef54 commit a1786b8
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 23 deletions.
4 changes: 4 additions & 0 deletions FtcDashboard/dash/src/assets/icons/gamepad_not_supported.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 59 additions & 19 deletions FtcDashboard/dash/src/containers/OpModeView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component, ChangeEvent } from 'react';
import React, { Component, ChangeEvent, createRef, MutableRefObject, PointerEvent } from 'react';
import { connect, ConnectedProps } from 'react-redux';

import styled from 'styled-components';
Expand All @@ -16,10 +16,13 @@ import BaseView, {
} from './BaseView';

import { ReactComponent as GamepadIcon } from '../assets/icons/gamepad.svg';
import { ReactComponent as GamepadNotSupportedIcon } from '../assets/icons/gamepad_not_supported.svg';
import { STOP_OP_MODE_TAG } from '../store/types/opmode';
import ToolTip from '../components/ToolTip';

type OpModeViewState = {
selectedOpMode: string;
shouldShowGamepadUnsupportedTooltip: boolean;
};

const mapStateToProps = ({ status, gamepad }: RootState) => ({
Expand All @@ -44,16 +47,33 @@ const ActionButton = styled.button.attrs<{ className: string }>((props) => ({
}))``;

class OpModeView extends Component<OpModeViewProps, OpModeViewState> {
gamepadUnsupportedTooltipRef: MutableRefObject<HTMLDivElement | null>;
gamepadUnsupportedTooltipTimeout: NodeJS.Timeout | null = null;

constructor(props: OpModeViewProps) {
super(props);

this.state = {
selectedOpMode: '',
shouldShowGamepadUnsupportedTooltip: false,
};
this.gamepadUnsupportedTooltipRef = createRef();

this.onChange = this.onChange.bind(this);
}

gamepadIconsHover(_e: PointerEvent<HTMLDivElement>) {
let myTimeout: NodeJS.Timeout;
this.gamepadUnsupportedTooltipTimeout = setTimeout(() => {
if (this.gamepadUnsupportedTooltipTimeout === myTimeout) this.setState(_prevState => ({ shouldShowGamepadUnsupportedTooltip: true }));
}, 500);
myTimeout = this.gamepadUnsupportedTooltipTimeout;
}
gamepadIconsUnhover() {
this.gamepadUnsupportedTooltipTimeout = null;
this.setState(_prevState => ({ shouldShowGamepadUnsupportedTooltip: false }));
}

static getDerivedStateFromProps(
props: OpModeViewProps,
state: OpModeViewState,
Expand Down Expand Up @@ -143,6 +163,7 @@ class OpModeView extends Component<OpModeViewProps, OpModeViewState> {
opModeList,
warningMessage,
errorMessage,
gamepadsSupported,
} = this.props;

const { gamepad1Connected, gamepad2Connected } = this.props;
Expand All @@ -162,30 +183,49 @@ class OpModeView extends Component<OpModeViewProps, OpModeViewState> {
);
}

const isShowingGamepadUnsupportedTooltip = !gamepadsSupported && this.state.shouldShowGamepadUnsupportedTooltip;

return (
<BaseView isUnlocked={this.props.isUnlocked}>
<div className="flex">
<BaseViewHeading isDraggable={this.props.isDraggable}>
Op Mode
</BaseViewHeading>
<BaseViewIcons>
<BaseViewIcon>
<GamepadIcon
className="w-6 h-6"
style={{
opacity: gamepad1Connected ? 1.0 : 0.3,
}}
/>
</BaseViewIcon>
<BaseViewIcon>
<GamepadIcon
className="w-6 h-6"
style={{
opacity: gamepad2Connected ? 1.0 : 0.3,
}}
/>
</BaseViewIcon>
</BaseViewIcons>
<div onPointerEnter={this.gamepadIconsHover.bind(this)} onPointerLeave={this.gamepadIconsUnhover.bind(this)} ref={this.gamepadUnsupportedTooltipRef}>
<BaseViewIcons>
<BaseViewIcon>
{ gamepadsSupported ?
<GamepadIcon
className="w-6 h-6"
style={{
opacity: gamepad1Connected ? 1.0 : 0.3,
}}
/>
:
<GamepadNotSupportedIcon
className="w-6 h-6"
/>
}
</BaseViewIcon>
<BaseViewIcon>
{ gamepadsSupported ?
<GamepadIcon
className="w-6 h-6"
style={{
opacity: gamepad2Connected ? 1.0 : 0.3,
}}
/>
:
<GamepadNotSupportedIcon
className="w-6 h-6"
/>
}
</BaseViewIcon>
</BaseViewIcons>
<ToolTip isShowing={isShowingGamepadUnsupportedTooltip} hoverRef={this.gamepadUnsupportedTooltipRef} >
Due to changes to JavaScript, gamepads are no longer supported in your browser.
</ToolTip>
</div>
</div>
<BaseViewBody>
<select
Expand Down
15 changes: 12 additions & 3 deletions FtcDashboard/dash/src/store/middleware/gamepadMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
sendGamepadState,
} from '../actions/gamepad';
import GamepadType from '../../enums/GamepadType';
import { GamepadState } from '../types';
import { GamepadState, GAMEPAD_SUPPORTED_STATUS } from '../types';
import { AppThunkDispatch, RootState } from '../reducers';

const scale = (
Expand Down Expand Up @@ -182,18 +182,27 @@ const extractGamepadState = (gamepad: Gamepad) => {
let gamepad1Index = -1;
let gamepad2Index = -1;


const gamepadMiddleware: Middleware<Record<string, unknown>, RootState> = (
store,
) => {
let getGamepads = navigator.getGamepads?.bind(navigator);
if (getGamepads == null) {
getGamepads = function() { return [null, null, null, null]; }
console.log("Gamepads not supported over non-https. See https://developer.mozilla.org/en-US/docs/Web/API/Gamepad");
setTimeout(() => { store.dispatch({ type: GAMEPAD_SUPPORTED_STATUS, gamepadsSupported: false }); }, 1000);
} else {
setTimeout(() => { store.dispatch({ type: GAMEPAD_SUPPORTED_STATUS, gamepadsSupported: true }); }, 1000);
}
function updateGamepads() {
const gamepads = navigator.getGamepads();
const gamepads = getGamepads();
if (gamepads.length === 0) {
setTimeout(updateGamepads, 500);
return;
}

// check for Start-A/Start-B
for (const gamepad of navigator.getGamepads()) {
for (const gamepad of getGamepads()) {
if (gamepad === null || !gamepad.connected) {
continue;
}
Expand Down
10 changes: 9 additions & 1 deletion FtcDashboard/dash/src/store/reducers/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import OpModeStatus from '../../enums/OpModeStatus';
import {
ReceiveOpModeListAction,
ReceiveRobotStatusAction,
GamepadSupportedStatus,
RECEIVE_OP_MODE_LIST,
RECEIVE_ROBOT_STATUS,
GAMEPAD_SUPPORTED_STATUS,
StatusState,
} from '../types';

Expand All @@ -14,11 +16,12 @@ const initialState: StatusState = {
opModeList: [],
warningMessage: '',
errorMessage: '',
gamepadsSupported: true,
};

const statusReducer = (
state = initialState,
action: ReceiveRobotStatusAction | ReceiveOpModeListAction,
action: ReceiveRobotStatusAction | ReceiveOpModeListAction | GamepadSupportedStatus,
) => {
switch (action.type) {
case RECEIVE_ROBOT_STATUS:
Expand All @@ -31,6 +34,11 @@ const statusReducer = (
...state,
opModeList: action.opModeList,
};
case GAMEPAD_SUPPORTED_STATUS:
return {
...state,
gamepadsSupported: action.gamepadsSupported,
};
default:
return state;
}
Expand Down
2 changes: 2 additions & 0 deletions FtcDashboard/dash/src/store/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,14 @@ export {
GET_ROBOT_STATUS,
RECEIVE_ROBOT_STATUS,
RECEIVE_OP_MODE_LIST,
GAMEPAD_SUPPORTED_STATUS,
} from './status';
export type {
StatusState,
GetRobotStatusAction,
ReceiveRobotStatusAction,
ReceiveOpModeListAction,
GamepadSupportedStatus,
} from './status';

export { RECEIVE_TELEMETRY } from './telemetry';
Expand Down
7 changes: 7 additions & 0 deletions FtcDashboard/dash/src/store/types/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Values } from '../../typeHelpers';
export const GET_ROBOT_STATUS = 'GET_ROBOT_STATUS';
export const RECEIVE_ROBOT_STATUS = 'RECEIVE_ROBOT_STATUS';
export const RECEIVE_OP_MODE_LIST = 'RECEIVE_OP_MODE_LIST';
export const GAMEPAD_SUPPORTED_STATUS = 'GAMEPAD_SUPPORTED_STATUS';

export type StatusState = {
available: boolean;
Expand All @@ -12,6 +13,7 @@ export type StatusState = {
opModeList: string[];
warningMessage: string;
errorMessage: string;
gamepadsSupported: boolean;
};

export type GetRobotStatusAction = {
Expand All @@ -27,3 +29,8 @@ export type ReceiveOpModeListAction = {
type: typeof RECEIVE_OP_MODE_LIST;
opModeList: string[];
};

export type GamepadSupportedStatus = {
type: typeof GAMEPAD_SUPPORTED_STATUS;
gamepadsSupported: boolean;
}

0 comments on commit a1786b8

Please sign in to comment.