From 56ed189aeb4ebc8e5dff7a6886e07791ce6569bb Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Wed, 8 May 2019 11:57:21 -0700 Subject: Auto logout after specific time (#6558) * Add i18n strings * Finish Auto timeout * Fix linter * Fix copies * Add unit test to Advanced Tab component * Add back actions and container * Add basic test to ensure container completeness * No zero, fix linters * restrict negative in input --- app/_locales/en/messages.json | 6 +++ package-lock.json | 5 +++ package.json | 1 + ui/app/pages/routes/index.js | 31 +++++++++++++-- .../advanced-tab/advanced-tab.component.js | 45 +++++++++++++++++++++ .../advanced-tab/advanced-tab.container.js | 11 ++++-- .../tests/advanced-tab-component.test.js | 44 +++++++++++++++++++++ .../tests/advanced-tab-container.test.js | 46 ++++++++++++++++++++++ ui/app/store/actions.js | 5 +++ 9 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js create mode 100644 ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 184507cbb..7a5f9297c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -154,6 +154,12 @@ "attributions": { "message": "Attributions" }, + "autoLogoutTimeLimit": { + "message": "Auto-Logout Timer (minutes)" + }, + "autoLogoutTimeLimitDescription": { + "message": "Set the number of idle time in minutes before Metamask automatically log out" + }, "available": { "message": "Available" }, diff --git a/package-lock.json b/package-lock.json index de174b789..e26c37150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32939,6 +32939,11 @@ "react-icon-base": "2.1.0" } }, + "react-idle-timer": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.2.5.tgz", + "integrity": "sha512-8B/OwjG8E/DTx1fHYKTpZ4cnCbL9+LOc5I9t8SYe8tbEkP14KChiYg0xPIuyRpO33wUZHcgmQl93CVePaDhmRA==" + }, "react-input-autosize": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz", diff --git a/package.json b/package.json index 0ab366c17..8d9da397d 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,7 @@ "react-dnd-html5-backend": "^7.4.4", "react-dom": "^15.6.2", "react-hyperscript": "^3.0.0", + "react-idle-timer": "^4.2.5", "react-inspector": "^2.3.0", "react-markdown": "^3.0.0", "react-media": "^1.8.0", diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index e38a6d6ce..7ff741405 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -3,9 +3,10 @@ import PropTypes from 'prop-types' import { connect } from 'react-redux' import { Route, Switch, withRouter, matchPath } from 'react-router-dom' import { compose } from 'recompose' -import actions from '../../store/actions' +import actions, {hideSidebar, hideWarning, lockMetamask} from '../../store/actions' import log from 'loglevel' -import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selectors' +import IdleTimer from 'react-idle-timer' +import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors' // init import FirstTimeFlow from '../first-time-flow' @@ -98,7 +99,9 @@ class Routes extends Component { } renderRoutes () { - return ( + const { autoLogoutTimeLimit, lockMetamask } = this.props + + const routes = ( @@ -116,6 +119,19 @@ class Routes extends Component { ) + + if (autoLogoutTimeLimit > 0) { + return ( + + {routes} + + ) + } + + return routes } onInitializationUnlockPage () { @@ -322,6 +338,7 @@ Routes.propTypes = { networkDropdownOpen: PropTypes.bool, showNetworkDropdown: PropTypes.func, hideNetworkDropdown: PropTypes.func, + lockMetamask: PropTypes.func, history: PropTypes.object, location: PropTypes.object, dispatch: PropTypes.func, @@ -344,6 +361,7 @@ Routes.propTypes = { t: PropTypes.func, providerId: PropTypes.string, providerRequests: PropTypes.array, + autoLogoutTimeLimit: PropTypes.number, } function mapStateToProps (state) { @@ -358,6 +376,7 @@ function mapStateToProps (state) { } = appState const accounts = getMetaMaskAccounts(state) + const { autoLogoutTimeLimit = 0 } = preferencesSelector(state) const { identities, @@ -409,6 +428,7 @@ function mapStateToProps (state) { Qr: state.appState.Qr, welcomeScreenSeen: state.metamask.welcomeScreenSeen, providerId: getNetworkIdentifier(state), + autoLogoutTimeLimit, // state needed to get account dropdown temporarily rendering from app bar identities, @@ -427,6 +447,11 @@ function mapDispatchToProps (dispatch, ownProps) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), + lockMetamask: () => { + dispatch(lockMetamask()) + dispatch(hideWarning()) + dispatch(hideSidebar()) + }, } } diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js index 14b9daae6..8d70cd2df 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js @@ -24,6 +24,8 @@ export default class AdvancedTab extends PureComponent { setAdvancedInlineGasFeatureFlag: PropTypes.func, advancedInlineGas: PropTypes.bool, showFiatInTestnets: PropTypes.bool, + autoLogoutTimeLimit: PropTypes.number, + setAutoLogoutTimeLimit: PropTypes.func.isRequired, setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired, } @@ -355,6 +357,48 @@ export default class AdvancedTab extends PureComponent { ) } + renderAutoLogoutTimeLimit () { + const { t } = this.context + const { + autoLogoutTimeLimit, + setAutoLogoutTimeLimit, + } = this.props + + return ( +
+
+ { t('autoLogoutTimeLimit') } +
+ { t('autoLogoutTimeLimitDescription') } +
+
+
+
+ this.setState({ autoLogoutTimeLimit: Math.max(Number(e.target.value), 0) })} + fullWidth + margin="dense" + min={0} + /> + +
+
+
+ ) + } + renderContent () { const { warning } = this.props @@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent { { this.renderAdvancedGasInputInline() } { this.renderHexDataOptIn() } { this.renderShowConversionInTestnets() } + { this.renderAutoLogoutTimeLimit() } ) } diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js index 69d7e07e6..bcac55f5e 100644 --- a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js +++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js @@ -8,10 +8,11 @@ import { setFeatureFlag, showModal, setShowFiatConversionOnTestnetsPreference, + setAutoLogoutTimeLimit, } from '../../../store/actions' import {preferencesSelector} from '../../../selectors/selectors' -const mapStateToProps = state => { +export const mapStateToProps = state => { const { appState: { warning }, metamask } = state const { featureFlags: { @@ -19,17 +20,18 @@ const mapStateToProps = state => { advancedInlineGas, } = {}, } = metamask - const { showFiatInTestnets } = preferencesSelector(state) + const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state) return { warning, sendHexData, advancedInlineGas, showFiatInTestnets, + autoLogoutTimeLimit, } } -const mapDispatchToProps = dispatch => { +export const mapDispatchToProps = dispatch => { return { setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)), setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)), @@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => { setShowFiatConversionOnTestnetsPreference: value => { return dispatch(setShowFiatConversionOnTestnetsPreference(value)) }, + setAutoLogoutTimeLimit: value => { + return dispatch(setAutoLogoutTimeLimit(value)) + }, } } diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js new file mode 100644 index 000000000..f81329533 --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js @@ -0,0 +1,44 @@ +import React from 'react' +import assert from 'assert' +import sinon from 'sinon' +import { shallow } from 'enzyme' +import AdvancedTab from '../advanced-tab.component' +import TextField from '../../../../components/ui/text-field' + +describe('AdvancedTab Component', () => { + it('should render correctly', () => { + const root = shallow( + , + { + context: { + t: s => `_${s}`, + }, + } + ) + + assert.equal(root.find('.settings-page__content-row').length, 8) + }) + + it('should update autoLogoutTimeLimit', () => { + const setAutoLogoutTimeLimitSpy = sinon.spy() + const root = shallow( + , + { + context: { + t: s => `_${s}`, + }, + } + ) + + const autoTimeout = root.find('.settings-page__content-row').last() + const textField = autoTimeout.find(TextField) + + textField.props().onChange({ target: { value: 1440 } }) + assert.equal(root.state().autoLogoutTimeLimit, 1440) + + autoTimeout.find('button').simulate('click') + assert.equal(setAutoLogoutTimeLimitSpy.args[0][0], 1440) + }) +}) diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js new file mode 100644 index 000000000..62122073d --- /dev/null +++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js @@ -0,0 +1,46 @@ +import assert from 'assert' +import { mapStateToProps, mapDispatchToProps } from '../advanced-tab.container' + +const defaultState = { + appState: { + warning: null, + }, + metamask: { + featureFlags: { + sendHexData: false, + advancedInlineGas: false, + }, + preferences: { + autoLogoutTimeLimit: 0, + showFiatInTestnets: false, + useNativeCurrencyAsPrimaryCurrency: true, + }, + }, +} + +describe('AdvancedTab Container', () => { + it('should map state to props correctly', () => { + const props = mapStateToProps(defaultState) + const expected = { + warning: null, + sendHexData: false, + advancedInlineGas: false, + showFiatInTestnets: false, + autoLogoutTimeLimit: 0, + } + + assert.deepEqual(props, expected) + }) + + it('should map dispatch to props correctly', () => { + const props = mapDispatchToProps(() => 'mockDispatch') + + assert.ok(typeof props.setHexDataFeatureFlag === 'function') + assert.ok(typeof props.setRpcTarget === 'function') + assert.ok(typeof props.displayWarning === 'function') + assert.ok(typeof props.showResetAccountConfirmationModal === 'function') + assert.ok(typeof props.setAdvancedInlineGasFeatureFlag === 'function') + assert.ok(typeof props.setShowFiatConversionOnTestnetsPreference === 'function') + assert.ok(typeof props.setAutoLogoutTimeLimit === 'function') + }) +}) diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index e7b855119..f2a9ed08f 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -316,6 +316,7 @@ var actions = { UPDATE_PREFERENCES: 'UPDATE_PREFERENCES', setUseNativeCurrencyAsPrimaryCurrencyPreference, setShowFiatConversionOnTestnetsPreference, + setAutoLogoutTimeLimit, // Migration of users to new UI setCompletedUiMigration, @@ -2439,6 +2440,10 @@ function setShowFiatConversionOnTestnetsPreference (value) { return setPreference('showFiatInTestnets', value) } +function setAutoLogoutTimeLimit (value) { + return setPreference('autoLogoutTimeLimit', value) +} + function setCompletedOnboarding () { return async dispatch => { dispatch(actions.showLoadingIndication()) -- cgit v1.2.3