mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Adding browser outdated notification (#17027)
* Adding browser outdated notification * updating dependency * adding unit tests for util function * adding unit tests for selectors, lintfix * Added Tests, refactored notification delay logic * lint:fix * adding test coverage for method parameter * Update app/scripts/controllers/app-state.js adding documentation details Co-authored-by: Mark Stacey <markjstacey@gmail.com> * moving declaration into test * Update app/scripts/controllers/app-state.test.js spacing in test file Co-authored-by: Mark Stacey <markjstacey@gmail.com> * Update jest.config.js removing duplicate entries Co-authored-by: Mark Stacey <markjstacey@gmail.com> * using async submitRequestToBackground method * removing unused import * removing unnecessary link syntax in notification * adding opera and edge and associated tests * handling the undefined case in bowser.satisfies * setOutdatedBrowserWarningLastShown try/catch * lint:fix * Removing try/catch and letting errors bubble up Removing deprecated displayWarning method Co-authored-by: Mark Stacey <markjstacey@gmail.com> * taking out forceMetamaskUpdateState call * excludint app-state test from mocha test suite * Added note: Jest files should match Mocha excluded * syntax error, lint:fix --------- Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
parent
aededb1c71
commit
532a10f9f5
@ -235,6 +235,7 @@ module.exports = {
|
||||
'test/e2e/**/*.spec.js',
|
||||
],
|
||||
excludedFiles: [
|
||||
'app/scripts/controllers/app-state.test.js',
|
||||
'app/scripts/controllers/network/**/*.test.js',
|
||||
'app/scripts/controllers/permissions/**/*.test.js',
|
||||
'app/scripts/lib/**/*.test.js',
|
||||
@ -257,11 +258,12 @@ module.exports = {
|
||||
* Jest tests
|
||||
*
|
||||
* These are files that make use of globals and syntax introduced by the
|
||||
* Jest library.
|
||||
* Jest library. The files in this section should match the Mocha excludedFiles section.
|
||||
*/
|
||||
{
|
||||
files: [
|
||||
'**/__snapshots__/*.snap',
|
||||
'app/scripts/controllers/app-state.test.js',
|
||||
'app/scripts/controllers/network/**/*.test.js',
|
||||
'app/scripts/controllers/network/provider-api-tests/*.js',
|
||||
'app/scripts/controllers/permissions/**/*.test.js',
|
||||
|
@ -5,6 +5,7 @@ module.exports = {
|
||||
'./app/scripts/lib/**/*.test.js',
|
||||
'./app/scripts/migrations/*.test.js',
|
||||
'./app/scripts/platforms/*.test.js',
|
||||
'./app/scripts/controllers/app-state.test.js',
|
||||
'./app/scripts/controllers/network/**/*.test.js',
|
||||
'./app/scripts/controllers/permissions/**/*.test.js',
|
||||
'./app/scripts/constants/error-utils.test.js',
|
||||
|
3
app/_locales/en/messages.json
generated
3
app/_locales/en/messages.json
generated
@ -2602,6 +2602,9 @@
|
||||
"message": "other snaps",
|
||||
"description": "Used in the 'permission_rpc' message."
|
||||
},
|
||||
"outdatedBrowserNotification": {
|
||||
"message": "Your browser is out of date. If you don't update your browser, you won't be able to get security patches and new features from MetaMask."
|
||||
},
|
||||
"padlock": {
|
||||
"message": "Padlock"
|
||||
},
|
||||
|
@ -37,6 +37,8 @@ export default class AppStateController extends EventEmitter {
|
||||
fullScreenGasPollTokens: [],
|
||||
recoveryPhraseReminderHasBeenShown: false,
|
||||
recoveryPhraseReminderLastShown: new Date().getTime(),
|
||||
outdatedBrowserWarningLastShown: new Date().getTime(),
|
||||
collectiblesDetectionNoticeDismissed: false,
|
||||
showTestnetMessageInDropdown: true,
|
||||
showPortfolioTooltip: true,
|
||||
showBetaHeader: isBeta(),
|
||||
@ -161,6 +163,17 @@ export default class AppStateController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the timestamp of the last time the user has seen the outdated browser warning
|
||||
*
|
||||
* @param {number} lastShown - Timestamp (in milliseconds) of when the user was last shown the warning.
|
||||
*/
|
||||
setOutdatedBrowserWarningLastShown(lastShown) {
|
||||
this.store.updateState({
|
||||
outdatedBrowserWarningLastShown: lastShown,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last active time to the current time.
|
||||
*/
|
||||
|
33
app/scripts/controllers/app-state.test.js
Normal file
33
app/scripts/controllers/app-state.test.js
Normal file
@ -0,0 +1,33 @@
|
||||
import AppStateController from './app-state';
|
||||
|
||||
describe('AppStateController', () => {
|
||||
describe('setOutdatedBrowserWarningLastShown', () => {
|
||||
it('should set the last shown time', () => {
|
||||
const appStateController = new AppStateController({
|
||||
addUnlockListener: jest.fn(),
|
||||
isUnlocked: jest.fn(() => true),
|
||||
initState: {},
|
||||
onInactiveTimeout: jest.fn(),
|
||||
showUnlockRequest: jest.fn(),
|
||||
preferencesStore: {
|
||||
subscribe: jest.fn(),
|
||||
getState: jest.fn(() => ({
|
||||
preferences: {
|
||||
autoLockTimeLimit: 0,
|
||||
},
|
||||
})),
|
||||
},
|
||||
qrHardwareStore: {
|
||||
subscribe: jest.fn(),
|
||||
},
|
||||
});
|
||||
const date = new Date();
|
||||
|
||||
appStateController.setOutdatedBrowserWarningLastShown(date);
|
||||
|
||||
expect(
|
||||
appStateController.store.getState().outdatedBrowserWarningLastShown,
|
||||
).toStrictEqual(date);
|
||||
});
|
||||
});
|
||||
});
|
@ -1878,6 +1878,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
appStateController.setRecoveryPhraseReminderLastShown.bind(
|
||||
appStateController,
|
||||
),
|
||||
setOutdatedBrowserWarningLastShown:
|
||||
appStateController.setOutdatedBrowserWarningLastShown.bind(
|
||||
appStateController,
|
||||
),
|
||||
setShowTestnetMessageInDropdown:
|
||||
appStateController.setShowTestnetMessageInDropdown.bind(
|
||||
appStateController,
|
||||
|
@ -35,6 +35,7 @@ module.exports = {
|
||||
setupFilesAfterEnv: ['<rootDir>/test/jest/setup.js'],
|
||||
testMatch: [
|
||||
'<rootDir>/app/scripts/constants/error-utils.test.js',
|
||||
'<rootDir>/app/scripts/controllers/app-state.test.js',
|
||||
'<rootDir>/app/scripts/controllers/network/**/*.test.js',
|
||||
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
|
||||
'<rootDir>/app/scripts/flask/**/*.test.js',
|
||||
|
@ -2106,6 +2106,11 @@
|
||||
"browserify>browser-resolve": true
|
||||
}
|
||||
},
|
||||
"bowser": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"browserify>assert": {
|
||||
"globals": {
|
||||
"Buffer": true
|
||||
|
@ -2430,6 +2430,11 @@
|
||||
"browserify>browser-resolve": true
|
||||
}
|
||||
},
|
||||
"bowser": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"browserify>assert": {
|
||||
"globals": {
|
||||
"Buffer": true
|
||||
|
@ -2106,6 +2106,11 @@
|
||||
"browserify>browser-resolve": true
|
||||
}
|
||||
},
|
||||
"bowser": {
|
||||
"globals": {
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"browserify>assert": {
|
||||
"globals": {
|
||||
"Buffer": true
|
||||
|
@ -269,6 +269,7 @@
|
||||
"base64-js": "^1.5.1",
|
||||
"bignumber.js": "^4.1.0",
|
||||
"bn.js": "^4.11.7",
|
||||
"bowser": "^2.11.0",
|
||||
"classnames": "^2.2.6",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"currency-formatter": "^1.4.2",
|
||||
|
@ -13,3 +13,9 @@ _supportRequestLink =
|
||||
export const SUPPORT_REQUEST_LINK = _supportRequestLink;
|
||||
export const CONTRACT_ADDRESS_LINK = _contractAddressLink;
|
||||
export const PASSWORD_MIN_LENGTH = 8;
|
||||
export const OUTDATED_BROWSER_VERSIONS = {
|
||||
chrome: '<80',
|
||||
edge: '<80',
|
||||
firefox: '<78',
|
||||
opera: '<67',
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import * as ethUtil from 'ethereumjs-util';
|
||||
import { DateTime } from 'luxon';
|
||||
import { getFormattedIpfsUrl } from '@metamask/assets-controllers';
|
||||
import slip44 from '@metamask/slip44';
|
||||
import bowser from 'bowser';
|
||||
import { CHAIN_IDS } from '../../../shared/constants/network';
|
||||
import {
|
||||
toChecksumHexAddress,
|
||||
@ -16,6 +17,7 @@ import {
|
||||
TRUNCATED_ADDRESS_END_CHARS,
|
||||
} from '../../../shared/constants/labels';
|
||||
import { Numeric } from '../../../shared/modules/Numeric';
|
||||
import { OUTDATED_BROWSER_VERSIONS } from '../constants/common';
|
||||
|
||||
// formatData :: ( date: <Unix Timestamp> ) -> String
|
||||
export function formatDate(date, format = "M/d/y 'at' T") {
|
||||
@ -328,6 +330,12 @@ export function getURL(url) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getIsBrowserDeprecated(
|
||||
browser = bowser.getParser(window.navigator.userAgent),
|
||||
) {
|
||||
return browser.satisfies(OUTDATED_BROWSER_VERSIONS) ?? false;
|
||||
}
|
||||
|
||||
export function getURLHost(url) {
|
||||
return getURL(url)?.host || '';
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import Bowser from 'bowser';
|
||||
import { BN } from 'ethereumjs-util';
|
||||
import { addHexPrefixToObjectValues } from '../../../shared/lib/swaps-utils';
|
||||
import { toPrecisionWithoutTrailingZeros } from '../../../shared/lib/transactions-controller-utils';
|
||||
@ -194,6 +195,77 @@ describe('util', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getIsBrowserDeprecated', () => {
|
||||
it('should call Bowser.getParser when no parameter is passed', () => {
|
||||
const spy = jest.spyOn(Bowser, 'getParser');
|
||||
util.getIsBrowserDeprecated();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
it('should return false when given a modern chrome browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.2623.112 Safari/537.36',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
it('should return true when given an outdated chrome browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.2623.112 Safari/537.36',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
it('should return false when given a modern firefox browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
it('should return true when given an outdated firefox browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
it('should return false when given a modern opera browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.3578.98 Safari/537.36 OPR/68.0.3135.47',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
it('should return true when given an outdated opera browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 OPR/58.0.3135.47',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
it('should return false when given a modern edge browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.3578.98 Safari/537.36 Edg/81.0.416.68',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
it('should return true when given an outdated edge browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 Edge/71.0.416.68',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
it('should return false when given an unknown browser', () => {
|
||||
const browser = Bowser.getParser(
|
||||
'Mozilla/5.0 (Nintendo Switch; WebApplet) AppleWebKit/609.4 (KHTML, like Gecko) NF/6.0.2.21.3 NintendoBrowser/5.1.0.22474',
|
||||
);
|
||||
const result = util.getIsBrowserDeprecated(browser);
|
||||
expect(result).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizing values', function () {
|
||||
describe('#getRandomFileName', () => {
|
||||
it('should only return a string containing alphanumeric characters', () => {
|
||||
|
@ -122,6 +122,8 @@ export default class Home extends PureComponent {
|
||||
showRecoveryPhraseReminder: PropTypes.bool.isRequired,
|
||||
setRecoveryPhraseReminderHasBeenShown: PropTypes.func.isRequired,
|
||||
setRecoveryPhraseReminderLastShown: PropTypes.func.isRequired,
|
||||
showOutdatedBrowserWarning: PropTypes.bool.isRequired,
|
||||
setOutdatedBrowserWarningLastShown: PropTypes.func.isRequired,
|
||||
seedPhraseBackedUp: (props) => {
|
||||
if (
|
||||
props.seedPhraseBackedUp !== null &&
|
||||
@ -249,6 +251,11 @@ export default class Home extends PureComponent {
|
||||
setRecoveryPhraseReminderLastShown(new Date().getTime());
|
||||
};
|
||||
|
||||
onOutdatedBrowserWarningClose = () => {
|
||||
const { setOutdatedBrowserWarningLastShown } = this.props;
|
||||
setOutdatedBrowserWarningLastShown(new Date().getTime());
|
||||
};
|
||||
|
||||
renderNotifications() {
|
||||
const { t } = this.context;
|
||||
const {
|
||||
@ -265,6 +272,7 @@ export default class Home extends PureComponent {
|
||||
shouldShowErrors,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
infuraBlocked,
|
||||
showOutdatedBrowserWarning,
|
||||
newNetworkAdded,
|
||||
setNewNetworkAdded,
|
||||
newCollectibleAddedMessage,
|
||||
@ -490,6 +498,14 @@ export default class Home extends PureComponent {
|
||||
key="home-infuraBlockedNotification"
|
||||
/>
|
||||
) : null}
|
||||
{showOutdatedBrowserWarning ? (
|
||||
<HomeNotification
|
||||
descriptionText={t('outdatedBrowserNotification')}
|
||||
acceptText={t('gotIt')}
|
||||
onAccept={this.onOutdatedBrowserWarningClose}
|
||||
key="home-outdatedBrowserNotification"
|
||||
/>
|
||||
) : null}
|
||||
{Object.keys(newCustomNetworkAdded).length !== 0 && (
|
||||
<Popover className="home__new-network-added">
|
||||
<i className="fa fa-check-circle fa-2x home__new-network-added__check-circle" />
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
getShowWhatsNewPopup,
|
||||
getSortedAnnouncementsToShow,
|
||||
getShowRecoveryPhraseReminder,
|
||||
getShowOutdatedBrowserWarning,
|
||||
getNewNetworkAdded,
|
||||
hasUnsignedQRHardwareTransaction,
|
||||
hasUnsignedQRHardwareMessage,
|
||||
@ -36,6 +37,7 @@ import {
|
||||
setAlertEnabledness,
|
||||
setRecoveryPhraseReminderHasBeenShown,
|
||||
setRecoveryPhraseReminderLastShown,
|
||||
setOutdatedBrowserWarningLastShown,
|
||||
setNewNetworkAdded,
|
||||
setNewCollectibleAddedMessage,
|
||||
setRemoveCollectibleMessage,
|
||||
@ -54,6 +56,7 @@ import {
|
||||
import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask';
|
||||
import { getSwapsFeatureIsLive } from '../../ducks/swaps/swaps';
|
||||
import { getEnvironmentType } from '../../../app/scripts/lib/util';
|
||||
import { getIsBrowserDeprecated } from '../../helpers/utils/util';
|
||||
import {
|
||||
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||
ENVIRONMENT_TYPE_POPUP,
|
||||
@ -143,6 +146,8 @@ const mapStateToProps = (state) => {
|
||||
portfolioTooltipWasShownInThisSession:
|
||||
getPortfolioTooltipWasShownInThisSession(state),
|
||||
showRecoveryPhraseReminder: getShowRecoveryPhraseReminder(state),
|
||||
showOutdatedBrowserWarning:
|
||||
getIsBrowserDeprecated() && getShowOutdatedBrowserWarning(state),
|
||||
seedPhraseBackedUp,
|
||||
newNetworkAdded: getNewNetworkAdded(state),
|
||||
isSigningQRHardwareTransaction,
|
||||
@ -172,6 +177,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setRecoveryPhraseReminderHasBeenShown()),
|
||||
setRecoveryPhraseReminderLastShown: (lastShown) =>
|
||||
dispatch(setRecoveryPhraseReminderLastShown(lastShown)),
|
||||
setOutdatedBrowserWarningLastShown: (lastShown) => {
|
||||
dispatch(setOutdatedBrowserWarningLastShown(lastShown));
|
||||
},
|
||||
setNewNetworkAdded: (newNetwork) => {
|
||||
console.log({ newNetwork });
|
||||
dispatch(setNewNetworkAdded(newNetwork));
|
||||
|
@ -1066,6 +1066,15 @@ export function getShowRecoveryPhraseReminder(state) {
|
||||
return currentTime - recoveryPhraseReminderLastShown >= frequency;
|
||||
}
|
||||
|
||||
export function getShowOutdatedBrowserWarning(state) {
|
||||
const { outdatedBrowserWarningLastShown } = state.metamask;
|
||||
if (!outdatedBrowserWarningLastShown) {
|
||||
return true;
|
||||
}
|
||||
const currentTime = new Date().getTime();
|
||||
return currentTime - outdatedBrowserWarningLastShown >= DAY * 2;
|
||||
}
|
||||
|
||||
export function getShowPortfolioTooltip(state) {
|
||||
return state.metamask.showPortfolioTooltip;
|
||||
}
|
||||
|
@ -269,6 +269,26 @@ describe('Selectors', () => {
|
||||
expect(useCurrencyRateCheck).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('#getShowOutdatedBrowserWarning returns false if outdatedBrowserWarningLastShown is less than 2 days ago', () => {
|
||||
mockState.metamask.showOutdatedBrowserWarning = true;
|
||||
const timestamp = new Date();
|
||||
timestamp.setDate(timestamp.getDate() - 1);
|
||||
mockState.metamask.outdatedBrowserWarningLastShown = timestamp.getTime();
|
||||
const showOutdatedBrowserWarning =
|
||||
selectors.getShowOutdatedBrowserWarning(mockState);
|
||||
expect(showOutdatedBrowserWarning).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it('#getShowOutdatedBrowserWarning returns true if outdatedBrowserWarningLastShown is more than 2 days ago', () => {
|
||||
mockState.metamask.showOutdatedBrowserWarning = true;
|
||||
const timestamp = new Date();
|
||||
timestamp.setDate(timestamp.getDate() - 3);
|
||||
mockState.metamask.outdatedBrowserWarningLastShown = timestamp.getTime();
|
||||
const showOutdatedBrowserWarning =
|
||||
selectors.getShowOutdatedBrowserWarning(mockState);
|
||||
expect(showOutdatedBrowserWarning).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('#getTotalUnapprovedSignatureRequestCount', () => {
|
||||
const totalUnapprovedSignatureRequestCount =
|
||||
selectors.getTotalUnapprovedSignatureRequestCount(mockState);
|
||||
|
@ -3274,6 +3274,14 @@ export function setRecoveryPhraseReminderLastShown(lastShown) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setOutdatedBrowserWarningLastShown(lastShown) {
|
||||
return async () => {
|
||||
await submitRequestToBackground('setOutdatedBrowserWarningLastShown', [
|
||||
lastShown,
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
export function loadingMethodDataStarted() {
|
||||
return {
|
||||
type: actionConstants.LOADING_METHOD_DATA_STARTED,
|
||||
|
Loading…
Reference in New Issue
Block a user