1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

Add web3 shim usage notification (#10039)

* Add web3 shim usage alert background state and logic
* Cleanup alert background state, constants
* Implement web3 shim usage notification and settings
* nodeify alert controller background hooks
* Remove svg icon, again
* Tweak alert controller initialization
* Add support article URL
* Un-thunk alert UI "actions"
* Delete connect.svg file (unused)
This commit is contained in:
Erik Marks 2020-12-10 15:40:29 -08:00 committed by GitHub
parent db004d4486
commit 54e9c53b27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 288 additions and 46 deletions

View File

@ -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"
@ -233,6 +239,9 @@
"bytes": {
"message": "Bytes"
},
"canToggleInSettings": {
"message": "You can re-enable this notification in Settings -> Alerts."
},
"cancel": {
"message": "Cancel"
},
@ -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"
},

View File

@ -1 +0,0 @@
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fff"><path d="m8.00002 9.57037c.93765 0 1.69776-.76011 1.69776-1.69777 0-.93765-.76011-1.69776-1.69776-1.69776-.93766 0-1.69777.76011-1.69777 1.69776 0 .93766.76011 1.69777 1.69777 1.69777z"/><path d="m11.0582 11.6586c-.1862 0-.3725-.071-.5145-.2131-.2842-.2841-.2842-.7448 0-1.029.6795-.67946 1.0538-1.58294 1.0538-2.54391 0-.96098-.3743-1.86446-1.0538-2.54394-.2842-.28417-.2842-.74484 0-1.02901.2841-.2841.7449-.2841 1.029 0 .9543.95434 1.48 2.22329 1.48 3.57295 0 1.34965-.5257 2.61861-1.48 3.57291-.1421.1421-.3283.2131-.5145.2131z"/><path d="m4.94175 11.6586c-.18622 0-.37246-.071-.51451-.2131-.95434-.9543-1.47997-2.22326-1.47997-3.57291 0-1.34966.52563-2.61861 1.47997-3.57295.28411-.2841.74491-.2841 1.02902 0 .28417.28417.28417.74484 0 1.02901-.67954.67948-1.05376 1.58296-1.05376 2.54394 0 .96097.37422 1.86445 1.05376 2.54391.28417.2842.28417.7449 0 1.029-.14206.1421-.32828.2131-.51451.2131z"/><path d="m13.1451 13.7453c-.1862 0-.3724-.0711-.5145-.2131-.2842-.2842-.2842-.7449 0-1.0291 2.5533-2.55325 2.5533-6.70772 0-9.26101-.2842-.28417-.2842-.74484 0-1.02901.2841-.28411.7449-.28411 1.029 0 3.1207 3.12066 3.1207 8.19842 0 11.31912-.142.142-.3283.2131-.5145.2131z"/><path d="m2.855 13.7453c-.18622 0-.37245-.0711-.5145-.2131-3.120666-3.1207-3.120666-8.19846 0-11.31912.28411-.28411.74491-.28411 1.02901 0 .28417.28417.28417.74484 0 1.02901-2.553289 2.55329-2.553289 6.70776 0 9.26101.28417.2842.28417.7449 0 1.0291-.14206.142-.32828.2131-.51451.2131z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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
@ -14,14 +18,8 @@ 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
@ -29,11 +27,11 @@ const defaultState = {
{},
),
unconnectedAccountAlertShownOrigins: {},
web3ShimUsageOrigins: {},
}
/**
* Controller responsible for maintaining
* alert related state
* Controller responsible for maintaining alert-related state.
*/
export default class AlertController {
/**
@ -41,11 +39,13 @@ export default class AlertController {
* @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)
@ -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 })
}
}

View File

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

View File

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

View File

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

View File

@ -1,11 +1,14 @@
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,
@ -13,6 +16,17 @@ const HomeNotification = ({
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">
@ -43,11 +57,34 @@ 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>
)
@ -55,6 +92,8 @@ const HomeNotification = ({
HomeNotification.propTypes = {
acceptText: PropTypes.node,
checkboxText: PropTypes.node,
checkboxTooltipText: PropTypes.node,
classNames: PropTypes.array,
descriptionText: PropTypes.node.isRequired,
ignoreText: PropTypes.node,

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { combineReducers } from 'redux'
import { ALERT_TYPES } from '../../../app/scripts/controllers/alert'
import { ALERT_TYPES } from '../../../shared/constants/alerts'
import metamaskReducer from './metamask/metamask'
import localeMessagesReducer from './locale/locale'
import sendReducer from './send/send.duck'

View File

@ -1,5 +1,5 @@
import * as actionConstants from '../../store/actionConstants'
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert'
import { ALERT_TYPES } from '../../../../shared/constants/alerts'
export default function reduceMetamask(state = {}, action) {
const metamaskState = {
@ -375,12 +375,12 @@ export const getCurrentLocale = (state) => state.metamask.currentLocale
export const getAlertEnabledness = (state) => state.metamask.alertEnabledness
export const getInvalidCustomNetworkAlertEnabledness = (state) =>
getAlertEnabledness(state)[ALERT_TYPES.invalidCustomNetwork]
export const getUnconnectedAccountAlertEnabledness = (state) =>
getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount]
export const getWeb3ShimUsageAlertEnabledness = (state) =>
getAlertEnabledness(state)[ALERT_TYPES.web3ShimUsage]
export const getUnconnectedAccountAlertShown = (state) =>
state.metamask.unconnectedAccountAlertShownOrigins

View File

@ -31,6 +31,8 @@ import {
const LEARN_MORE_URL =
'https://metamask.zendesk.com/hc/en-us/articles/360045129011-Intro-to-MetaMask-v8-extension'
const LEGACY_WEB3_URL =
'https://metamask.zendesk.com/hc/en-us/articles/360053147012'
export default class Home extends PureComponent {
static contextTypes = {
@ -42,7 +44,7 @@ export default class Home extends PureComponent {
forgottenPassword: PropTypes.bool,
suggestedTokens: PropTypes.object,
unconfirmedTransactionsCount: PropTypes.number,
shouldShowSeedPhraseReminder: PropTypes.bool,
shouldShowSeedPhraseReminder: PropTypes.bool.isRequired,
isPopup: PropTypes.bool,
isNotification: PropTypes.bool.isRequired,
threeBoxSynced: PropTypes.bool,
@ -66,6 +68,10 @@ export default class Home extends PureComponent {
swapsFetchParams: PropTypes.object,
swapsEnabled: PropTypes.bool,
isMainnet: PropTypes.bool,
shouldShowWeb3ShimUsageNotification: PropTypes.bool.isRequired,
setWeb3ShimUsageAlertDismissed: PropTypes.func.isRequired,
originOfCurrentTab: PropTypes.string,
disableWeb3ShimUsageAlert: PropTypes.func.isRequired,
}
state = {
@ -161,10 +167,39 @@ export default class Home extends PureComponent {
setShowRestorePromptToFalse,
showRestorePrompt,
threeBoxLastUpdated,
shouldShowWeb3ShimUsageNotification,
setWeb3ShimUsageAlertDismissed,
originOfCurrentTab,
disableWeb3ShimUsageAlert,
} = this.props
return (
<MultipleNotifications>
{shouldShowWeb3ShimUsageNotification ? (
<HomeNotification
descriptionText={t('web3ShimUsageNotification', [
<span
key="web3ShimUsageNotificationLink"
className="home-notification__text-link"
onClick={() =>
global.platform.openTab({ url: LEGACY_WEB3_URL })
}
>
{t('here')}
</span>,
])}
ignoreText={t('dismiss')}
onIgnore={(disable) => {
setWeb3ShimUsageAlertDismissed(originOfCurrentTab)
if (disable) {
disableWeb3ShimUsageAlert()
}
}}
checkboxText={t('dontShowThisAgain')}
checkboxTooltipText={t('canToggleInSettings')}
key="home-web3ShimUsageNotification"
/>
) : null}
{shouldShowSeedPhraseReminder ? (
<HomeNotification
descriptionText={t('backupApprovalNotice')}

View File

@ -2,11 +2,14 @@ import { compose } from 'redux'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import {
unconfirmedTransactionsCountSelector,
activeTabHasPermissions,
getCurrentEthBalance,
getFirstPermissionRequest,
getTotalUnapprovedCount,
getIsMainnet,
getOriginOfCurrentTab,
getTotalUnapprovedCount,
getWeb3ShimUsageStateForOrigin,
unconfirmedTransactionsCountSelector,
} from '../../selectors'
import {
@ -17,8 +20,11 @@ import {
setConnectedStatusPopoverHasBeenShown,
setDefaultHomeActiveTabName,
setSwapsWelcomeMessageHasBeenShown,
setWeb3ShimUsageAlertDismissed,
setAlertEnabledness,
} from '../../store/actions'
import { setThreeBoxLastUpdated } from '../../ducks/app/app'
import { getWeb3ShimUsageAlertEnabledness } from '../../ducks/metamask/metamask'
import {
getSwapsWelcomeMessageSeenStatus,
getSwapsFeatureLiveness,
@ -28,6 +34,10 @@ import {
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP,
} from '../../../../app/scripts/lib/enums'
import {
ALERT_TYPES,
WEB3_SHIM_USAGE_ALERT_STATES,
} from '../../../../shared/constants/alerts'
import Home from './home.component'
const mapStateToProps = (state) => {
@ -58,6 +68,14 @@ const mapStateToProps = (state) => {
? firstPermissionsRequest.metadata.id
: null
const originOfCurrentTab = getOriginOfCurrentTab(state)
const shouldShowWeb3ShimUsageNotification =
isPopup &&
getWeb3ShimUsageAlertEnabledness(state) &&
activeTabHasPermissions(state) &&
getWeb3ShimUsageStateForOrigin(state, originOfCurrentTab) ===
WEB3_SHIM_USAGE_ALERT_STATES.RECORDED
return {
forgottenPassword,
suggestedTokens,
@ -81,6 +99,8 @@ const mapStateToProps = (state) => {
swapsFetchParams: swapsState.fetchParams,
showAwaitingSwapScreen: swapsState.routeState === 'awaiting',
isMainnet: getIsMainnet(state),
originOfCurrentTab,
shouldShowWeb3ShimUsageNotification,
}
}
@ -103,6 +123,10 @@ const mapDispatchToProps = (dispatch) => ({
onTabClick: (name) => dispatch(setDefaultHomeActiveTabName(name)),
setSwapsWelcomeMessageHasBeenShown: () =>
dispatch(setSwapsWelcomeMessageHasBeenShown()),
setWeb3ShimUsageAlertDismissed: (origin) =>
setWeb3ShimUsageAlertDismissed(origin),
disableWeb3ShimUsageAlert: () =>
setAlertEnabledness(ALERT_TYPES.web3ShimUsage, false),
})
export default compose(

View File

@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useDispatch, useSelector } from 'react-redux'
import { useSelector } from 'react-redux'
import { ALERT_TYPES } from '../../../../../app/scripts/controllers/alert'
import { ALERT_TYPES } from '../../../../../shared/constants/alerts'
import Tooltip from '../../../components/ui/tooltip'
import ToggleButton from '../../../components/ui/toggle-button'
import { setAlertEnabledness } from '../../../store/actions'
@ -11,7 +11,6 @@ import { useI18nContext } from '../../../hooks/useI18nContext'
const AlertSettingsEntry = ({ alertId, description, title }) => {
const t = useI18nContext()
const dispatch = useDispatch()
const isEnabled = useSelector((state) => getAlertEnabledness(state)[alertId])
return (
@ -27,7 +26,7 @@ const AlertSettingsEntry = ({ alertId, description, title }) => {
<ToggleButton
offLabel={t('off')}
onLabel={t('on')}
onToggle={() => dispatch(setAlertEnabledness(alertId, !isEnabled))}
onToggle={() => setAlertEnabledness(alertId, !isEnabled)}
value={isEnabled}
/>
</>
@ -48,6 +47,10 @@ const AlertsTab = () => {
title: t('alertSettingsUnconnectedAccount'),
description: t('alertSettingsUnconnectedAccountDescription'),
},
[ALERT_TYPES.web3ShimUsage]: {
title: t('alertSettingsWeb3ShimUsage'),
description: t('alertSettingsWeb3ShimUsageDescription'),
},
}
return (

View File

@ -244,6 +244,13 @@ export function getPermissionsForActiveTab(state) {
})
}
export function activeTabHasPermissions(state) {
const { activeTab, metamask } = state
const { domains = {} } = metamask
return Boolean(domains[activeTab.origin]?.permissions?.length > 0)
}
export function getLastConnectedInfo(state) {
const { permissionsHistory = {} } = state.metamask
return Object.keys(permissionsHistory).reduce((acc, origin) => {

View File

@ -356,3 +356,7 @@ export function getIpfsGateway(state) {
export function getUSDConversionRate(state) {
return state.metamask.usdConversionRate
}
export function getWeb3ShimUsageStateForOrigin(state, origin) {
return state.metamask.web3ShimUsageOrigins[origin]
}

View File

@ -2481,16 +2481,18 @@ export function setSwapsWelcomeMessageHasBeenShown() {
}
}
export function setAlertEnabledness(alertId, enabledness) {
return async () => {
await promisifiedBackground.setAlertEnabledness(alertId, enabledness)
}
export async function setAlertEnabledness(alertId, enabledness) {
await promisifiedBackground.setAlertEnabledness(alertId, enabledness)
}
export async function setUnconnectedAccountAlertShown(origin) {
await promisifiedBackground.setUnconnectedAccountAlertShown(origin)
}
export async function setWeb3ShimUsageAlertDismissed(origin) {
await promisifiedBackground.setWeb3ShimUsageAlertDismissed(origin)
}
export function loadingMethodDataStarted() {
return {
type: actionConstants.LOADING_METHOD_DATA_STARTED,

View File

@ -4,7 +4,7 @@ import { clone } from 'lodash'
import React from 'react'
import { render } from 'react-dom'
import { getEnvironmentType } from '../app/scripts/lib/util'
import { ALERT_TYPES } from '../app/scripts/controllers/alert'
import { ALERT_TYPES } from '../shared/constants/alerts'
import { SENTRY_STATE } from '../app/scripts/lib/setupSentry'
import { ENVIRONMENT_TYPE_POPUP } from '../app/scripts/lib/enums'
import Root from './app/pages'