mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add switch to connected account alert (#8532)
Add alert suggesting that the user switch to a connected account. This alert is displayed when the popup is opened over an active tab that is connected to some account, but not the current selected account. The user can choose to switch to a connected account, or dismiss the alert. This alert is only shown once per account switch. So if the user repeatedly opens the popup on a dapp without switching accounts, it'll only be shown the first time. The alert also won't be shown if the user has just dismissed an "Unconnected account" alert on this same dapp and account, as that would be redundant. The alert has a "Don't show me this again" checkbox that allows the user to disable the alert. It can be re-enabled again on the Alerts settings page.
This commit is contained in:
parent
6868688a03
commit
53ec42d95f
@ -154,6 +154,12 @@
|
|||||||
"alertsSettingsDescription": {
|
"alertsSettingsDescription": {
|
||||||
"message": "Enable or disable each alert"
|
"message": "Enable or disable each alert"
|
||||||
},
|
},
|
||||||
|
"alertSettingsSwitchToConnected": {
|
||||||
|
"message": "Opening popup with an unconnected account selected"
|
||||||
|
},
|
||||||
|
"alertSettingsSwitchToConnectedDescription": {
|
||||||
|
"message": "This alert is shown when you open the popup with an unconnected account selected."
|
||||||
|
},
|
||||||
"alertSettingsUnconnectedAccount": {
|
"alertSettingsUnconnectedAccount": {
|
||||||
"message": "Switching to an unconnected account"
|
"message": "Switching to an unconnected account"
|
||||||
},
|
},
|
||||||
@ -953,6 +959,9 @@
|
|||||||
"noAlreadyHaveSeed": {
|
"noAlreadyHaveSeed": {
|
||||||
"message": "No, I already have a seed phrase"
|
"message": "No, I already have a seed phrase"
|
||||||
},
|
},
|
||||||
|
"notConnected": {
|
||||||
|
"message": "Not connected"
|
||||||
|
},
|
||||||
"protectYourKeys": {
|
"protectYourKeys": {
|
||||||
"message": "Protect Your Keys!"
|
"message": "Protect Your Keys!"
|
||||||
},
|
},
|
||||||
@ -1412,6 +1421,16 @@
|
|||||||
"supportCenter": {
|
"supportCenter": {
|
||||||
"message": "Visit our Support Center"
|
"message": "Visit our Support Center"
|
||||||
},
|
},
|
||||||
|
"switchAccounts": {
|
||||||
|
"message": "Switch accounts"
|
||||||
|
},
|
||||||
|
"switchToConnectedAlertMultipleAccountsDescription": {
|
||||||
|
"message": "This account is not connected. Switch to a connected account?"
|
||||||
|
},
|
||||||
|
"switchToConnectedAlertSingleAccountDescription": {
|
||||||
|
"message": "This account is not connected. Switch to a connected account ($1)?",
|
||||||
|
"description": "$1 will be replaced by the name of the connected account"
|
||||||
|
},
|
||||||
"symbol": {
|
"symbol": {
|
||||||
"message": "Symbol"
|
"message": "Symbol"
|
||||||
},
|
},
|
||||||
@ -1549,9 +1568,6 @@
|
|||||||
"unapproved": {
|
"unapproved": {
|
||||||
"message": "Unapproved"
|
"message": "Unapproved"
|
||||||
},
|
},
|
||||||
"unconnectedAccountAlertTitle": {
|
|
||||||
"message": "Not connected"
|
|
||||||
},
|
|
||||||
"unconnectedAccountAlertDescription": {
|
"unconnectedAccountAlertDescription": {
|
||||||
"message": "This account is not connected to this site."
|
"message": "This account is not connected to this site."
|
||||||
},
|
},
|
||||||
|
@ -12,6 +12,7 @@ import ObservableStore from 'obs-store'
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const ALERT_TYPES = {
|
export const ALERT_TYPES = {
|
||||||
|
switchToConnected: 'switchToConnected',
|
||||||
unconnectedAccount: 'unconnectedAccount',
|
unconnectedAccount: 'unconnectedAccount',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ const defaultState = {
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
),
|
),
|
||||||
|
switchToConnectedAlertShown: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,13 +38,27 @@ export default class AlertController {
|
|||||||
* @param {AlertControllerOptions} [opts] - Controller configuration parameters
|
* @param {AlertControllerOptions} [opts] - Controller configuration parameters
|
||||||
*/
|
*/
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
const { initState } = opts
|
const { initState, preferencesStore } = opts
|
||||||
const state = Object.assign(
|
const state = Object.assign(
|
||||||
{},
|
{},
|
||||||
defaultState,
|
defaultState,
|
||||||
initState,
|
initState,
|
||||||
|
{
|
||||||
|
switchToConnectedAlertShown: {},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
this.store = new ObservableStore(state)
|
this.store = new ObservableStore(state)
|
||||||
|
|
||||||
|
const { selectedAddress } = preferencesStore.getState()
|
||||||
|
this.selectedAddress = selectedAddress
|
||||||
|
|
||||||
|
preferencesStore.subscribe(({ selectedAddress }) => {
|
||||||
|
const currentState = this.store.getState()
|
||||||
|
if (currentState.switchToConnectedAlertShown && this.selectedAddress !== selectedAddress) {
|
||||||
|
this.selectedAddress = selectedAddress
|
||||||
|
this.store.updateState({ switchToConnectedAlertShown: {} })
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlertEnabledness (alertId, enabledness) {
|
setAlertEnabledness (alertId, enabledness) {
|
||||||
@ -51,4 +67,15 @@ export default class AlertController {
|
|||||||
alertEnabledness[alertId] = enabledness
|
alertEnabledness[alertId] = enabledness
|
||||||
this.store.updateState({ alertEnabledness })
|
this.store.updateState({ alertEnabledness })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the "switch to connected" alert as shown for the given origin
|
||||||
|
* @param {string} origin - The origin the alert has been shown for
|
||||||
|
*/
|
||||||
|
setSwitchToConnectedAlertShown (origin) {
|
||||||
|
let { switchToConnectedAlertShown } = this.store.getState()
|
||||||
|
switchToConnectedAlertShown = { ...switchToConnectedAlertShown }
|
||||||
|
switchToConnectedAlertShown[origin] = true
|
||||||
|
this.store.updateState({ switchToConnectedAlertShown })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,10 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
this.addressBookController = new AddressBookController(undefined, initState.AddressBookController)
|
this.addressBookController = new AddressBookController(undefined, initState.AddressBookController)
|
||||||
|
|
||||||
this.alertController = new AlertController({ initState: initState.AlertController })
|
this.alertController = new AlertController({
|
||||||
|
initState: initState.AlertController,
|
||||||
|
preferencesStore: this.preferencesController.store,
|
||||||
|
})
|
||||||
|
|
||||||
this.threeBoxController = new ThreeBoxController({
|
this.threeBoxController = new ThreeBoxController({
|
||||||
preferencesController: this.preferencesController,
|
preferencesController: this.preferencesController,
|
||||||
@ -564,6 +567,7 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
|
|
||||||
// alert controller
|
// alert controller
|
||||||
setAlertEnabledness: nodeify(alertController.setAlertEnabledness, alertController),
|
setAlertEnabledness: nodeify(alertController.setAlertEnabledness, alertController),
|
||||||
|
setSwitchToConnectedAlertShown: nodeify(this.alertController.setSwitchToConnectedAlertShown, this.alertController),
|
||||||
|
|
||||||
// 3Box
|
// 3Box
|
||||||
setThreeBoxSyncingPermission: nodeify(threeBoxController.setThreeBoxSyncingPermission, threeBoxController),
|
setThreeBoxSyncingPermission: nodeify(threeBoxController.setThreeBoxSyncingPermission, threeBoxController),
|
||||||
|
@ -522,6 +522,9 @@
|
|||||||
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
|
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
|
||||||
"errors": {}
|
"errors": {}
|
||||||
},
|
},
|
||||||
|
"switchToConnected": {
|
||||||
|
"state": "CLOSED"
|
||||||
|
},
|
||||||
"unconnectedAccount": {
|
"unconnectedAccount": {
|
||||||
"state": "CLOSED"
|
"state": "CLOSED"
|
||||||
}
|
}
|
||||||
|
@ -473,6 +473,9 @@
|
|||||||
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
|
"priceAndTimeEstimatesLastRetrieved": 1541527901281,
|
||||||
"errors": {}
|
"errors": {}
|
||||||
},
|
},
|
||||||
|
"switchToConnected": {
|
||||||
|
"state": "CLOSED"
|
||||||
|
},
|
||||||
"unconnectedAccount": {
|
"unconnectedAccount": {
|
||||||
"state": "CLOSED"
|
"state": "CLOSED"
|
||||||
}
|
}
|
||||||
|
@ -1278,6 +1278,9 @@
|
|||||||
"errors": {}
|
"errors": {}
|
||||||
},
|
},
|
||||||
"confirmTransaction": {},
|
"confirmTransaction": {},
|
||||||
|
"switchToConnected": {
|
||||||
|
"state": "CLOSED"
|
||||||
|
},
|
||||||
"unconnectedAccount": {
|
"unconnectedAccount": {
|
||||||
"state": "CLOSED"
|
"state": "CLOSED"
|
||||||
}
|
}
|
||||||
|
@ -980,7 +980,7 @@ describe('Actions', function () {
|
|||||||
it('#showAccountDetail', async function () {
|
it('#showAccountDetail', async function () {
|
||||||
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
|
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
|
||||||
.callsArgWith(1, null)
|
.callsArgWith(1, null)
|
||||||
const store = mockStore({ metamask: { alertEnabledness: {}, selectedAddress: '0x123' } })
|
const store = mockStore({ activeTab: {}, metamask: { alertEnabledness: {}, selectedAddress: '0x123' } })
|
||||||
|
|
||||||
await store.dispatch(actions.showAccountDetail())
|
await store.dispatch(actions.showAccountDetail())
|
||||||
assert(setSelectedAddressSpy.calledOnce)
|
assert(setSelectedAddressSpy.calledOnce)
|
||||||
@ -989,7 +989,7 @@ describe('Actions', function () {
|
|||||||
it('displays warning if setSelectedAddress throws', async function () {
|
it('displays warning if setSelectedAddress throws', async function () {
|
||||||
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
|
setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress')
|
||||||
.callsArgWith(1, new Error('error'))
|
.callsArgWith(1, new Error('error'))
|
||||||
const store = mockStore({ metamask: { alertEnabledness: {}, selectedAddress: '0x123' } })
|
const store = mockStore({ activeTab: {}, metamask: { alertEnabledness: {}, selectedAddress: '0x123' } })
|
||||||
const expectedActions = [
|
const expectedActions = [
|
||||||
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
{ type: 'SHOW_LOADING_INDICATION', value: undefined },
|
||||||
{ type: 'HIDE_LOADING_INDICATION' },
|
{ type: 'HIDE_LOADING_INDICATION' },
|
||||||
|
@ -2,15 +2,22 @@ import React from 'react'
|
|||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import UnconnectedAccountAlert from './unconnected-account-alert'
|
import UnconnectedAccountAlert from './unconnected-account-alert'
|
||||||
|
import SwitchToConnectedAlert from './switch-to-connected-alert'
|
||||||
import { alertIsOpen as unconnectedAccountAlertIsOpen } from '../../../ducks/alerts/unconnected-account'
|
import { alertIsOpen as unconnectedAccountAlertIsOpen } from '../../../ducks/alerts/unconnected-account'
|
||||||
|
import { alertIsOpen as switchToConnectedAlertIsOpen } from '../../../ducks/alerts/switch-to-connected'
|
||||||
|
|
||||||
const Alerts = () => {
|
const Alerts = () => {
|
||||||
const _unconnectedAccountAlertIsOpen = useSelector(unconnectedAccountAlertIsOpen)
|
const _unconnectedAccountAlertIsOpen = useSelector(unconnectedAccountAlertIsOpen)
|
||||||
|
const _switchToConnectedAlertIsOpen = useSelector(switchToConnectedAlertIsOpen)
|
||||||
|
|
||||||
if (_unconnectedAccountAlertIsOpen) {
|
if (_unconnectedAccountAlertIsOpen) {
|
||||||
return (
|
return (
|
||||||
<UnconnectedAccountAlert />
|
<UnconnectedAccountAlert />
|
||||||
)
|
)
|
||||||
|
} else if (_switchToConnectedAlertIsOpen) {
|
||||||
|
return (
|
||||||
|
<SwitchToConnectedAlert />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@ -1 +1,3 @@
|
|||||||
@import './unconnected-account-alert/unconnected-account-alert.scss';
|
@import './unconnected-account-alert/unconnected-account-alert.scss';
|
||||||
|
|
||||||
|
@import './switch-to-connected-alert/switch-to-connected-alert.scss';
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './switch-to-connected-alert'
|
@ -0,0 +1,121 @@
|
|||||||
|
import React, { useContext, useState } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ALERT_STATE,
|
||||||
|
switchToAccount,
|
||||||
|
dismissAlert,
|
||||||
|
dismissAndDisableAlert,
|
||||||
|
getAlertState,
|
||||||
|
} from '../../../../ducks/alerts/switch-to-connected'
|
||||||
|
import { getPermittedIdentitiesForCurrentTab } from '../../../../selectors'
|
||||||
|
import { I18nContext } from '../../../../contexts/i18n'
|
||||||
|
import Popover from '../../../ui/popover'
|
||||||
|
import Button from '../../../ui/button'
|
||||||
|
import Dropdown from '../../../ui/dropdown'
|
||||||
|
import Checkbox from '../../../ui/check-box'
|
||||||
|
import Tooltip from '../../../ui/tooltip-v2'
|
||||||
|
|
||||||
|
const {
|
||||||
|
ERROR,
|
||||||
|
LOADING,
|
||||||
|
} = ALERT_STATE
|
||||||
|
|
||||||
|
const SwitchToUnconnectedAccountAlert = () => {
|
||||||
|
const t = useContext(I18nContext)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const alertState = useSelector(getAlertState)
|
||||||
|
const connectedAccounts = useSelector(getPermittedIdentitiesForCurrentTab)
|
||||||
|
const [accountToSwitchTo, setAccountToSwitchTo] = useState(connectedAccounts[0].address)
|
||||||
|
const [dontShowThisAgain, setDontShowThisAgain] = useState(false)
|
||||||
|
|
||||||
|
const onClose = async () => {
|
||||||
|
return dontShowThisAgain
|
||||||
|
? await dispatch(dismissAndDisableAlert())
|
||||||
|
: dispatch(dismissAlert())
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = connectedAccounts.map((account) => {
|
||||||
|
return { name: account.name, value: account.address }
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
contentClassName="switch-to-connected-alert__content"
|
||||||
|
footer={(
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
alertState === ERROR
|
||||||
|
? (
|
||||||
|
<div className="switch-to-connected-alert__error">
|
||||||
|
{ t('failureMessage') }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div className="switch-to-connected-alert__footer-buttons">
|
||||||
|
<Button
|
||||||
|
disabled={alertState === LOADING}
|
||||||
|
onClick={onClose}
|
||||||
|
type="secondary"
|
||||||
|
>
|
||||||
|
{ t('dismiss') }
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={alertState === LOADING || alertState === ERROR || dontShowThisAgain}
|
||||||
|
onClick={() => dispatch(switchToAccount(accountToSwitchTo))}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
{ t('switchAccounts') }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
footerClassName="switch-to-connected-alert__footer"
|
||||||
|
onClose={onClose}
|
||||||
|
subtitle={
|
||||||
|
connectedAccounts.length > 1
|
||||||
|
? t('switchToConnectedAlertMultipleAccountsDescription')
|
||||||
|
: t('switchToConnectedAlertSingleAccountDescription', [connectedAccounts[0].name])
|
||||||
|
}
|
||||||
|
title={t('notConnected')}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
connectedAccounts.length > 1
|
||||||
|
? (
|
||||||
|
<Dropdown
|
||||||
|
className="switch-to-connected-alert__dropdown"
|
||||||
|
title="Switch to account"
|
||||||
|
onChange={(address) => setAccountToSwitchTo(address)}
|
||||||
|
options={options}
|
||||||
|
selectedOption={accountToSwitchTo}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div className="switch-to-connected-alert__checkbox-wrapper">
|
||||||
|
<Checkbox
|
||||||
|
id="switchToConnected_dontShowThisAgain"
|
||||||
|
checked={dontShowThisAgain}
|
||||||
|
className="switch-to-connected-alert__checkbox"
|
||||||
|
onClick={() => setDontShowThisAgain((checked) => !checked)}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="switch-to-connected-alert__checkbox-label"
|
||||||
|
htmlFor="switchToConnected_dontShowThisAgain"
|
||||||
|
>
|
||||||
|
{ t('dontShowThisAgain') }
|
||||||
|
<Tooltip
|
||||||
|
position="top"
|
||||||
|
title={t('unconnectedAccountAlertDisableTooltip')}
|
||||||
|
wrapperClassName="switch-to-connected-alert__checkbox-label-tooltip"
|
||||||
|
>
|
||||||
|
<i className="fa fa-info-circle" />
|
||||||
|
</Tooltip>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SwitchToUnconnectedAccountAlert
|
@ -0,0 +1,66 @@
|
|||||||
|
.switch-to-connected-alert {
|
||||||
|
&__footer {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
:only-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
button:first-child {
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid #D73A49;
|
||||||
|
background: #F8EAE8;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 24px 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dropdown {
|
||||||
|
background-color: white;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox-label {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
color: $Grey-500;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox-label-tooltip {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,7 @@ const SwitchToUnconnectedAccountAlert = () => {
|
|||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
contentClassName="unconnected-account-alert__content"
|
contentClassName="unconnected-account-alert__content"
|
||||||
title={t('unconnectedAccountAlertTitle')}
|
title={t('notConnected')}
|
||||||
subtitle={t('unconnectedAccountAlertDescription')}
|
subtitle={t('unconnectedAccountAlertDescription')}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
footer={(
|
footer={(
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
export { default as switchToConnected } from './switch-to-connected'
|
||||||
export { default as unconnectedAccount } from './unconnected-account'
|
export { default as unconnectedAccount } from './unconnected-account'
|
||||||
|
111
ui/app/ducks/alerts/switch-to-connected.js
Normal file
111
ui/app/ducks/alerts/switch-to-connected.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
import { captureException } from '@sentry/browser'
|
||||||
|
|
||||||
|
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert'
|
||||||
|
import * as actionConstants from '../../store/actionConstants'
|
||||||
|
import { setAlertEnabledness, setSelectedAddress } from '../../store/actions'
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
|
||||||
|
export const ALERT_STATE = {
|
||||||
|
CLOSED: 'CLOSED',
|
||||||
|
ERROR: 'ERROR',
|
||||||
|
LOADING: 'LOADING',
|
||||||
|
OPEN: 'OPEN',
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = ALERT_TYPES.switchToConnected
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
state: ALERT_STATE.CLOSED,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice (reducer plus auto-generated actions and action creators)
|
||||||
|
|
||||||
|
const slice = createSlice({
|
||||||
|
name,
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
disableAlertFailed: (state) => {
|
||||||
|
state.state = ALERT_STATE.ERROR
|
||||||
|
},
|
||||||
|
disableAlertRequested: (state) => {
|
||||||
|
state.state = ALERT_STATE.LOADING
|
||||||
|
},
|
||||||
|
disableAlertSucceeded: (state) => {
|
||||||
|
state.state = ALERT_STATE.CLOSED
|
||||||
|
},
|
||||||
|
dismissAlert: (state) => {
|
||||||
|
state.state = ALERT_STATE.CLOSED
|
||||||
|
},
|
||||||
|
switchAccountFailed: (state) => {
|
||||||
|
state.state = ALERT_STATE.ERROR
|
||||||
|
},
|
||||||
|
switchAccountRequested: (state) => {
|
||||||
|
state.state = ALERT_STATE.LOADING
|
||||||
|
},
|
||||||
|
switchAccountSucceeded: (state) => {
|
||||||
|
state.state = ALERT_STATE.CLOSED
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: {
|
||||||
|
[actionConstants.SELECTED_ADDRESS_CHANGED]: (state) => {
|
||||||
|
// close the alert if the account is switched while it's open
|
||||||
|
if (state.state === ALERT_STATE.OPEN) {
|
||||||
|
state.state = ALERT_STATE.CLOSED
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { actions, reducer } = slice
|
||||||
|
|
||||||
|
export default reducer
|
||||||
|
|
||||||
|
// Selectors
|
||||||
|
|
||||||
|
export const getAlertState = (state) => state[name].state
|
||||||
|
|
||||||
|
export const alertIsOpen = (state) => state[name].state !== ALERT_STATE.CLOSED
|
||||||
|
|
||||||
|
// Actions / action-creators
|
||||||
|
|
||||||
|
const {
|
||||||
|
disableAlertFailed,
|
||||||
|
disableAlertRequested,
|
||||||
|
disableAlertSucceeded,
|
||||||
|
dismissAlert,
|
||||||
|
switchAccountFailed,
|
||||||
|
switchAccountRequested,
|
||||||
|
switchAccountSucceeded,
|
||||||
|
} = actions
|
||||||
|
|
||||||
|
export { dismissAlert }
|
||||||
|
|
||||||
|
export const dismissAndDisableAlert = () => {
|
||||||
|
return async (dispatch) => {
|
||||||
|
try {
|
||||||
|
await dispatch(disableAlertRequested())
|
||||||
|
await dispatch(setAlertEnabledness(name, false))
|
||||||
|
await dispatch(disableAlertSucceeded())
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
captureException(error)
|
||||||
|
await dispatch(disableAlertFailed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const switchToAccount = (address) => {
|
||||||
|
return async (dispatch) => {
|
||||||
|
try {
|
||||||
|
await dispatch(switchAccountRequested())
|
||||||
|
await dispatch(setSelectedAddress(address))
|
||||||
|
await dispatch(switchAccountSucceeded())
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
captureException(error)
|
||||||
|
await dispatch(switchAccountFailed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,11 @@ import sendReducer from './send/send.duck'
|
|||||||
import appStateReducer from './app/app'
|
import appStateReducer from './app/app'
|
||||||
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'
|
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'
|
||||||
import gasReducer from './gas/gas.duck'
|
import gasReducer from './gas/gas.duck'
|
||||||
import { unconnectedAccount } from './alerts'
|
import { switchToConnected, unconnectedAccount } from './alerts'
|
||||||
import { ALERT_TYPES } from '../../../app/scripts/controllers/alert'
|
import { ALERT_TYPES } from '../../../app/scripts/controllers/alert'
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
|
[ALERT_TYPES.switchToConnected]: switchToConnected,
|
||||||
[ALERT_TYPES.unconnectedAccount]: unconnectedAccount,
|
[ALERT_TYPES.unconnectedAccount]: unconnectedAccount,
|
||||||
activeTab: (s) => (s === undefined ? null : s),
|
activeTab: (s) => (s === undefined ? null : s),
|
||||||
metamask: metamaskReducer,
|
metamask: metamaskReducer,
|
||||||
|
@ -371,4 +371,8 @@ export const getCurrentLocale = (state) => state.metamask.currentLocale
|
|||||||
|
|
||||||
export const getAlertEnabledness = (state) => state.metamask.alertEnabledness
|
export const getAlertEnabledness = (state) => state.metamask.alertEnabledness
|
||||||
|
|
||||||
|
export const getSwitchToConnectedAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.switchToConnected]
|
||||||
|
|
||||||
export const getUnconnectedAccountAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount]
|
export const getUnconnectedAccountAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount]
|
||||||
|
|
||||||
|
export const getSwitchToConnectedAlertShown = (state) => state.metamask.switchToConnectedAlertShown
|
||||||
|
@ -184,6 +184,7 @@ export default class Routes extends Component {
|
|||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
|
isUnlocked,
|
||||||
alertMessage,
|
alertMessage,
|
||||||
textDirection,
|
textDirection,
|
||||||
loadingMessage,
|
loadingMessage,
|
||||||
@ -252,7 +253,13 @@ export default class Routes extends Component {
|
|||||||
{ !isLoading && isLoadingNetwork && <LoadingNetwork /> }
|
{ !isLoading && isLoadingNetwork && <LoadingNetwork /> }
|
||||||
{ this.renderRoutes() }
|
{ this.renderRoutes() }
|
||||||
</div>
|
</div>
|
||||||
<Alerts />
|
{
|
||||||
|
isUnlocked
|
||||||
|
? (
|
||||||
|
<Alerts />
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,10 @@ const AlertsTab = () => {
|
|||||||
const t = useContext(I18nContext)
|
const t = useContext(I18nContext)
|
||||||
|
|
||||||
const alertConfig = {
|
const alertConfig = {
|
||||||
|
[ALERT_TYPES.switchToConnected]: {
|
||||||
|
title: t('alertSettingsSwitchToConnected'),
|
||||||
|
description: t('alertSettingsSwitchToConnectedDescription'),
|
||||||
|
},
|
||||||
[ALERT_TYPES.unconnectedAccount]: {
|
[ALERT_TYPES.unconnectedAccount]: {
|
||||||
title: t('alertSettingsUnconnectedAccount'),
|
title: t('alertSettingsUnconnectedAccount'),
|
||||||
description: t('alertSettingsUnconnectedAccountDescription'),
|
description: t('alertSettingsUnconnectedAccountDescription'),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { forOwn } from 'lodash'
|
import { forOwn } from 'lodash'
|
||||||
import { getOriginOfCurrentTab } from './selectors'
|
import { getMetaMaskIdentities, getOriginOfCurrentTab } from './selectors'
|
||||||
import {
|
import {
|
||||||
CAVEAT_NAMES,
|
CAVEAT_NAMES,
|
||||||
} from '../../../app/scripts/controllers/permissions/enums'
|
} from '../../../app/scripts/controllers/permissions/enums'
|
||||||
@ -117,6 +117,12 @@ export function getConnectedDomainsForSelectedAddress (state) {
|
|||||||
return connectedDomains
|
return connectedDomains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPermittedIdentitiesForCurrentTab (state) {
|
||||||
|
const permittedAccounts = getPermittedAccountsForCurrentTab(state)
|
||||||
|
const identities = getMetaMaskIdentities(state)
|
||||||
|
return permittedAccounts.map((address) => identities[address])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object mapping addresses to objects mapping origins to connected
|
* Returns an object mapping addresses to objects mapping origins to connected
|
||||||
* domain info. Domain info objects have the following properties:
|
* domain info. Domain info objects have the following properties:
|
||||||
|
@ -1188,6 +1188,7 @@ export function showAccountDetail (address) {
|
|||||||
|
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const unconnectedAccountAlertIsEnabled = getUnconnectedAccountAlertEnabledness(state)
|
const unconnectedAccountAlertIsEnabled = getUnconnectedAccountAlertEnabledness(state)
|
||||||
|
const activeTabOrigin = state.activeTab.origin
|
||||||
const selectedAddress = getSelectedAddress(state)
|
const selectedAddress = getSelectedAddress(state)
|
||||||
const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(state)
|
const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(state)
|
||||||
const currentTabIsConnectedToPreviousAddress = permittedAccountsForCurrentTab.includes(selectedAddress)
|
const currentTabIsConnectedToPreviousAddress = permittedAccountsForCurrentTab.includes(selectedAddress)
|
||||||
@ -1206,10 +1207,11 @@ export function showAccountDetail (address) {
|
|||||||
type: actionConstants.SHOW_ACCOUNT_DETAIL,
|
type: actionConstants.SHOW_ACCOUNT_DETAIL,
|
||||||
value: address,
|
value: address,
|
||||||
})
|
})
|
||||||
|
dispatch(setSelectedToken())
|
||||||
if (unconnectedAccountAlertIsEnabled && switchingToUnconnectedAddress) {
|
if (unconnectedAccountAlertIsEnabled && switchingToUnconnectedAddress) {
|
||||||
dispatch(switchedToUnconnectedAccount())
|
dispatch(switchedToUnconnectedAccount())
|
||||||
|
await setSwitchToConnectedAlertShown(activeTabOrigin)
|
||||||
}
|
}
|
||||||
dispatch(setSelectedToken())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2162,6 +2164,10 @@ export function setAlertEnabledness (alertId, enabledness) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setSwitchToConnectedAlertShown (origin) {
|
||||||
|
await promisifiedBackground.setSwitchToConnectedAlertShown(origin)
|
||||||
|
}
|
||||||
|
|
||||||
export function loadingMethodDataStarted () {
|
export function loadingMethodDataStarted () {
|
||||||
return {
|
return {
|
||||||
type: actionConstants.LOADING_METHOD_DATA_STARTED,
|
type: actionConstants.LOADING_METHOD_DATA_STARTED,
|
||||||
|
34
ui/index.js
34
ui/index.js
@ -7,8 +7,17 @@ import Root from './app/pages'
|
|||||||
import * as actions from './app/store/actions'
|
import * as actions from './app/store/actions'
|
||||||
import configureStore from './app/store/store'
|
import configureStore from './app/store/store'
|
||||||
import txHelper from './lib/tx-helper'
|
import txHelper from './lib/tx-helper'
|
||||||
|
import { getEnvironmentType } from '../app/scripts/lib/util'
|
||||||
|
import { ALERT_TYPES } from '../app/scripts/controllers/alert'
|
||||||
|
import { ENVIRONMENT_TYPE_POPUP } from '../app/scripts/lib/enums'
|
||||||
import { fetchLocale } from './app/helpers/utils/i18n-helper'
|
import { fetchLocale } from './app/helpers/utils/i18n-helper'
|
||||||
import switchDirection from './app/helpers/utils/switch-direction'
|
import switchDirection from './app/helpers/utils/switch-direction'
|
||||||
|
import { getPermittedAccountsForCurrentTab, getSelectedAddress } from './app/selectors'
|
||||||
|
import { ALERT_STATE } from './app/ducks/alerts/switch-to-connected'
|
||||||
|
import {
|
||||||
|
getSwitchToConnectedAlertEnabledness,
|
||||||
|
getSwitchToConnectedAlertShown,
|
||||||
|
} from './app/ducks/metamask/metamask'
|
||||||
|
|
||||||
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn')
|
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn')
|
||||||
|
|
||||||
@ -43,7 +52,7 @@ async function startApp (metamaskState, backgroundConnection, opts) {
|
|||||||
await switchDirection('rtl')
|
await switchDirection('rtl')
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = configureStore({
|
const draftInitialState = {
|
||||||
activeTab: opts.activeTab,
|
activeTab: opts.activeTab,
|
||||||
|
|
||||||
// metamaskState represents the cross-tab state
|
// metamaskState represents the cross-tab state
|
||||||
@ -56,7 +65,28 @@ async function startApp (metamaskState, backgroundConnection, opts) {
|
|||||||
current: currentLocaleMessages,
|
current: currentLocaleMessages,
|
||||||
en: enLocaleMessages,
|
en: enLocaleMessages,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
||||||
|
const origin = draftInitialState.activeTab.origin
|
||||||
|
const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(draftInitialState)
|
||||||
|
const selectedAddress = getSelectedAddress(draftInitialState)
|
||||||
|
const switchToConnectedAlertShown = getSwitchToConnectedAlertShown(draftInitialState)
|
||||||
|
const switchToConnectedAlertIsEnabled = getSwitchToConnectedAlertEnabledness(draftInitialState)
|
||||||
|
|
||||||
|
if (
|
||||||
|
origin &&
|
||||||
|
switchToConnectedAlertIsEnabled &&
|
||||||
|
!switchToConnectedAlertShown[origin] &&
|
||||||
|
permittedAccountsForCurrentTab.length > 0 &&
|
||||||
|
!permittedAccountsForCurrentTab.includes(selectedAddress)
|
||||||
|
) {
|
||||||
|
draftInitialState[ALERT_TYPES.switchToConnected] = { state: ALERT_STATE.OPEN }
|
||||||
|
actions.setSwitchToConnectedAlertShown(origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = configureStore(draftInitialState)
|
||||||
|
|
||||||
// if unconfirmed txs, start on txConf page
|
// if unconfirmed txs, start on txConf page
|
||||||
const unapprovedTxsAll = txHelper(
|
const unapprovedTxsAll = txHelper(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user