Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add web3 shim usage notification #10039

Merged
merged 11 commits into from
Dec 10, 2020
15 changes: 14 additions & 1 deletion app/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@
"message": "Browsing a website with an unconnected account selected"
},
"alertSettingsUnconnectedAccountDescription": {
"message": "This alert is shown in the popup when you are browsing a connected Web3 site, but the currently selected account is not connected."
"message": "This alert is shown in the popup when you are browsing a connected web3 site, but the currently selected account is not connected."
},
"alertSettingsWeb3ShimUsage": {
"message": "When a website tries to use the removed window.web3 API"
},
"alertSettingsWeb3ShimUsageDescription": {
"message": "This alert is shown in the popup when you are browsing a site that tries to use the removed window.web3 API, and may be broken as a result."
},
"alerts": {
"message": "Alerts"
Expand Down Expand Up @@ -233,6 +239,9 @@
"bytes": {
"message": "Bytes"
},
"canToggleInSettings": {
"message": "You can re-enable this notification in Settings -> Alerts."
},
"cancel": {
"message": "Cancel"
},
Expand Down Expand Up @@ -2095,6 +2104,10 @@
"walletSeed": {
"message": "Seed phrase"
},
"web3ShimUsageNotification": {
"message": "We noticed that the current website tried to use the removed window.web3 API. If the site appears to be broken, please click $1 for more information.",
"description": "$1 is a clickable link."
},
"welcome": {
"message": "Welcome to MetaMask"
},
Expand Down
1 change: 0 additions & 1 deletion app/images/icons/connect.svg

This file was deleted.

68 changes: 56 additions & 12 deletions app/scripts/controllers/alert.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import ObservableStore from 'obs-store'
import {
TOGGLEABLE_ALERT_TYPES,
WEB3_SHIM_USAGE_ALERT_STATES,
} from '../../../shared/constants/alerts'

/**
* @typedef {Object} AlertControllerInitState
Expand All @@ -14,38 +18,34 @@ import ObservableStore from 'obs-store'
* @property {AlertControllerInitState} initState - The initial controller state
*/

export const ALERT_TYPES = {
unconnectedAccount: 'unconnectedAccount',
// enumerated here but has no background state
invalidCustomNetwork: 'invalidCustomNetwork',
}

const defaultState = {
alertEnabledness: Object.keys(ALERT_TYPES).reduce(
alertEnabledness: TOGGLEABLE_ALERT_TYPES.reduce(
(alertEnabledness, alertType) => {
alertEnabledness[alertType] = true
return alertEnabledness
},
{},
),
unconnectedAccountAlertShownOrigins: {},
web3ShimUsageOrigins: {},
}

/**
* Controller responsible for maintaining
* alert related state
* Controller responsible for maintaining alert-related state.
*/
export default class AlertController {
/**
* @constructor
* @param {AlertControllerOptions} [opts] - Controller configuration parameters
*/
constructor(opts = {}) {
const { initState, preferencesStore } = opts
const { initState = {}, preferencesStore } = opts
const state = {
...defaultState,
...initState,
unconnectedAccountAlertShownOrigins: {},
alertEnabledness: {
...defaultState.alertEnabledness,
...initState.alertEnabledness,
},
}

this.store = new ObservableStore(state)
Expand Down Expand Up @@ -83,4 +83,48 @@ export default class AlertController {
unconnectedAccountAlertShownOrigins[origin] = true
this.store.updateState({ unconnectedAccountAlertShownOrigins })
}

/**
* Gets the web3 shim usage state for the given origin.
*
* @param {string} origin - The origin to get the web3 shim usage state for.
* @returns {undefined | 1 | 2} The web3 shim usage state for the given
* origin, or undefined.
*/
getWeb3ShimUsageState(origin) {
return this.store.getState().web3ShimUsageOrigins[origin]
}

/**
* Sets the web3 shim usage state for the given origin to RECORDED.
*
* @param {string} origin - The origin the that used the web3 shim.
*/
setWeb3ShimUsageRecorded(origin) {
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.RECORDED)
}

/**
* Sets the web3 shim usage state for the given origin to DISMISSED.
*
* @param {string} origin - The origin that the web3 shim notification was
* dismissed for.
*/
setWeb3ShimUsageAlertDismissed(origin) {
this._setWeb3ShimUsageState(origin, WEB3_SHIM_USAGE_ALERT_STATES.DISMISSED)
}

/**
* @private
* @param {string} origin - The origin to set the state for.
* @param {number} value - The state value to set.
*/
_setWeb3ShimUsageState(origin, value) {
let { web3ShimUsageOrigins } = this.store.getState()
web3ShimUsageOrigins = {
...web3ShimUsageOrigins,
}
web3ShimUsageOrigins[origin] = value
this.store.updateState({ web3ShimUsageOrigins })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ const logWeb3ShimUsage = {
}
export default logWeb3ShimUsage

const recordedWeb3ShimUsage = {}

/**
* @typedef {Object} LogWeb3ShimUsageOptions
* @property {Function} sendMetrics - A function that registers a metrics event.
* @property {Function} getWeb3ShimUsageState - A function that gets web3 shim
* usage state for the given origin.
* @property {Function} setWeb3ShimUsageRecorded - A function that records web3 shim
* usage for a particular origin.
*/

/**
Expand All @@ -27,10 +29,16 @@ const recordedWeb3ShimUsage = {}
* @param {Function} end - The json-rpc-engine 'end' callback.
* @param {LogWeb3ShimUsageOptions} options
*/
function logWeb3ShimUsageHandler(req, res, _next, end, { sendMetrics }) {
function logWeb3ShimUsageHandler(
req,
res,
_next,
end,
{ sendMetrics, getWeb3ShimUsageState, setWeb3ShimUsageRecorded },
) {
const { origin } = req
if (!recordedWeb3ShimUsage[origin]) {
recordedWeb3ShimUsage[origin] = true
if (getWeb3ShimUsageState(origin) === undefined) {
setWeb3ShimUsageRecorded(origin)

sendMetrics({
event: `Website Accessed window.web3 Shim`,
Expand Down
20 changes: 15 additions & 5 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,16 +514,16 @@ export default class MetamaskController extends EventEmitter {
*/
getApi() {
const {
alertController,
keyringController,
metaMetricsController,
networkController,
onboardingController,
alertController,
permissionsController,
preferencesController,
swapsController,
threeBoxController,
txController,
swapsController,
metaMetricsController,
} = this

return {
Expand Down Expand Up @@ -706,8 +706,12 @@ export default class MetamaskController extends EventEmitter {
alertController,
),
setUnconnectedAccountAlertShown: nodeify(
this.alertController.setUnconnectedAccountAlertShown,
this.alertController,
alertController.setUnconnectedAccountAlertShown,
alertController,
),
setWeb3ShimUsageAlertDismissed: nodeify(
alertController.setWeb3ShimUsageAlertDismissed,
alertController,
),

// 3Box
Expand Down Expand Up @@ -1979,6 +1983,12 @@ export default class MetamaskController extends EventEmitter {
handleWatchAssetRequest: this.preferencesController.requestWatchAsset.bind(
this.preferencesController,
),
getWeb3ShimUsageState: this.alertController.getWeb3ShimUsageState.bind(
this.alertController,
),
setWeb3ShimUsageRecorded: this.alertController.setWeb3ShimUsageRecorded.bind(
this.alertController,
),
}),
)
// filter and subscription polyfills
Expand Down
18 changes: 18 additions & 0 deletions shared/constants/alerts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const ALERT_TYPES = {
unconnectedAccount: 'unconnectedAccount',
web3ShimUsage: 'web3ShimUsage',
invalidCustomNetwork: 'invalidCustomNetwork',
}

/**
* Alerts that can be enabled or disabled by the user.
*/
export const TOGGLEABLE_ALERT_TYPES = [
ALERT_TYPES.unconnectedAccount,
ALERT_TYPES.web3ShimUsage,
]

export const WEB3_SHIM_USAGE_ALERT_STATES = {
RECORDED: 1,
DISMISSED: 2,
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import React from 'react'
import React, { useState } from 'react'
import classnames from 'classnames'
import PropTypes from 'prop-types'
import Button from '../../ui/button'
import Checkbox from '../../ui/check-box'
import Tooltip from '../../ui/tooltip'

const HomeNotification = ({
acceptText,
checkboxText,
checkboxTooltipText,
classNames = [],
descriptionText,
ignoreText,
infoText,
onAccept,
onIgnore,
}) => {
const [checkboxState, setCheckBoxState] = useState(false)

const checkboxElement = checkboxText && (
<Checkbox
id="homeNotification_checkbox"
checked={checkboxState}
className="home-notification__checkbox"
onClick={() => setCheckBoxState((checked) => !checked)}
/>
)

return (
<div className={classnames('home-notification', ...classNames)}>
<div className="home-notification__content">
Expand Down Expand Up @@ -43,18 +57,43 @@ const HomeNotification = ({
<Button
type="secondary"
className="home-notification__ignore-button"
onClick={onIgnore}
// Some onIgnore handlers use the checkboxState to determine whether
// to disable the notification
onClick={() => onIgnore(checkboxState)}
>
{ignoreText}
</Button>
) : null}
{checkboxText ? (
<div className="home-notification__checkbox-wrapper">
{checkboxTooltipText ? (
<Tooltip
position="top"
title={checkboxTooltipText}
wrapperClassName="home-notification__checkbox-label-tooltip"
>
{checkboxElement}
</Tooltip>
) : (
checkboxElement
)}
<label
className="home-notification__checkbox-label"
htmlFor="homeNotification_checkbox"
>
{checkboxText}
</label>
</div>
) : null}
</div>
</div>
)
}

HomeNotification.propTypes = {
acceptText: PropTypes.node,
checkboxText: PropTypes.node,
checkboxTooltipText: PropTypes.node,
classNames: PropTypes.array,
descriptionText: PropTypes.node.isRequired,
ignoreText: PropTypes.node,
Expand Down
36 changes: 36 additions & 0 deletions ui/app/components/app/home-notification/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,46 @@
color: $white;
}

&__text-link {
@include H7;

color: $primary-blue;
cursor: pointer;
}

.fa-info-circle {
color: #6a737d;
}

& &__checkbox-wrapper {
display: flex;
flex-direction: row;
align-items: center;

@media screen and (max-width: 575px) {
width: 160px;
}
}

& &__checkbox {
height: 13px;
width: 13px;
font-size: 16px;
cursor: pointer;
}

& &__checkbox-label {
@include H7;

color: $white;
margin-left: 10px;
margin-top: 1px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
cursor: pointer;
}

& &__ignore-button {
border-color: #6a737d;
box-sizing: border-box;
Expand Down
2 changes: 1 addition & 1 deletion ui/app/ducks/alerts/invalid-custom-network.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createSlice } from '@reduxjs/toolkit'

import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert'
import { ALERT_TYPES } from '../../../../shared/constants/alerts'
import { ALERT_STATE } from './enums'

// Constants
Expand Down
4 changes: 2 additions & 2 deletions ui/app/ducks/alerts/unconnected-account.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSlice } from '@reduxjs/toolkit'
import { captureException } from '@sentry/browser'

import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert'
import { ALERT_TYPES } from '../../../../shared/constants/alerts'
import * as actionConstants from '../../store/actionConstants'
import {
addPermittedAccount,
Expand Down Expand Up @@ -101,7 +101,7 @@ export const dismissAndDisableAlert = () => {
return async (dispatch) => {
try {
await dispatch(disableAlertRequested())
await dispatch(setAlertEnabledness(name, false))
await setAlertEnabledness(name, false)
await dispatch(disableAlertSucceeded())
} catch (error) {
console.error(error)
Expand Down
Loading