1
0
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:
vthomas13 2023-02-02 13:56:41 -05:00 committed by GitHub
parent aededb1c71
commit 532a10f9f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 222 additions and 1 deletions

View File

@ -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',

View File

@ -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',

View File

@ -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"
},

View File

@ -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.
*/

View 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);
});
});
});

View File

@ -1878,6 +1878,10 @@ export default class MetamaskController extends EventEmitter {
appStateController.setRecoveryPhraseReminderLastShown.bind(
appStateController,
),
setOutdatedBrowserWarningLastShown:
appStateController.setOutdatedBrowserWarningLastShown.bind(
appStateController,
),
setShowTestnetMessageInDropdown:
appStateController.setShowTestnetMessageInDropdown.bind(
appStateController,

View File

@ -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',

View File

@ -2106,6 +2106,11 @@
"browserify>browser-resolve": true
}
},
"bowser": {
"globals": {
"define": true
}
},
"browserify>assert": {
"globals": {
"Buffer": true

View File

@ -2430,6 +2430,11 @@
"browserify>browser-resolve": true
}
},
"bowser": {
"globals": {
"define": true
}
},
"browserify>assert": {
"globals": {
"Buffer": true

View File

@ -2106,6 +2106,11 @@
"browserify>browser-resolve": true
}
},
"bowser": {
"globals": {
"define": true
}
},
"browserify>assert": {
"globals": {
"Buffer": true

View File

@ -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",

View File

@ -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',
};

View File

@ -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 || '';
}

View File

@ -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', () => {

View File

@ -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" />

View File

@ -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));

View File

@ -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;
}

View File

@ -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);

View File

@ -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,

View File

@ -24147,6 +24147,7 @@ __metadata:
bify-module-groups: ^2.0.0
bignumber.js: ^4.1.0
bn.js: ^4.11.7
bowser: ^2.11.0
brfs: ^2.0.2
browser-util-inspect: ^0.2.0
browserify: ^16.5.1