Change notification windows order

This commit is contained in:
Victor Baranov 2020-04-23 22:30:28 +03:00
parent 5e49fa72b4
commit bd1e5d50a6
23 changed files with 544 additions and 699 deletions

View File

@ -2,6 +2,8 @@
## Current Master
- [#364](https://github.com/poanetwork/nifty-wallet/pull/364) - Fix notifications order in batch requests
## 5.0.3 Fri May 01 2020
- [#373](https://github.com/poanetwork/nifty-wallet/pull/373) - (Feature) Add STAKE token

View File

@ -5,11 +5,11 @@
// this needs to run before anything else
require('./lib/setupFetchDebugging')()
const endOfStream = require('end-of-stream')
const pump = require('pump')
const debounce = require('debounce-stream')
const log = require('loglevel')
const extension = require('extensionizer')
import endOfStream from 'end-of-stream'
import pump from 'pump'
import debounce from 'debounce-stream'
import log from 'loglevel'
import extension from 'extensionizer'
const LocalStorageStore = require('obs-store/lib/localStorage')
const LocalStore = require('./lib/local-store')
const storeTransform = require('obs-store/lib/transform')
@ -19,13 +19,11 @@ const Migrator = require('./lib/migrator/')
const migrations = require('./migrations/')
const PortStream = require('extension-port-stream')
const createStreamSink = require('./lib/createStreamSink')
const NotificationManager = require('./lib/notification-manager.js')
import NotificationManager from './lib/notification-manager.js'
const MetamaskController = require('./metamask-controller')
const rawFirstTimeState = require('./first-time-state')
const setupRaven = require('./lib/setupRaven')
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
@ -51,12 +49,6 @@ global.METAMASK_NOTIFIER = notificationManager
const release = platform.getVersion()
const raven = setupRaven({ release })
// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
// Internet Explorer 6-11
const isIE = !!document.documentMode
// Edge 20+
const isEdge = !isIE && !!window.StyleMedia
let popupIsOpen = false
let notificationIsOpen = false
const openMetamaskTabsIDs = {}
@ -70,9 +62,6 @@ let versionedData
// initialization flow
initialize().catch(log.error)
// setup metamask mesh testing container
setupMetamaskMeshMetrics()
/**
* An object representing a transaction, in whatever state it is in.
@ -177,6 +166,7 @@ async function initialize () {
async function loadStateFromPersistence () {
// migrations
const migrator = new Migrator({ migrations })
migrator.on('error', console.warn)
// read from disk
// first from preferred, async API:
@ -256,7 +246,7 @@ function setupController (initState, initLangCode) {
showUnconfirmedMessage: triggerUi,
unlockAccountMessage: triggerUi,
showUnapprovedTx: triggerUi,
showWatchAssetUi: showWatchAssetUi,
openPopup: openPopup,
// initial state
initState,
// initial locale code
@ -269,7 +259,6 @@ function setupController (initState, initLangCode) {
getOpenMetamaskTabsIds: () => {
return openMetamaskTabsIDs
},
encryptor: isEdge ? new EdgeEncryptor() : undefined,
})
global.metamaskController = controller
@ -460,28 +449,27 @@ function setupController (initState, initLangCode) {
/**
* Opens the browser popup for user confirmation
*/
function triggerUi () {
extension.tabs.query({ active: true }, tabs => {
const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
/**
* https://github.com/poanetwork/metamask-extension/issues/19
* !notificationIsOpen was removed from the check, because notification can be opened, but it can be behind the DApp
* for some reasons. For example, if notification popup was opened, but user moved focus to DApp.
* New transaction, in this case, will not appear in front of DApp.
*/
if (!popupIsOpen && !currentlyActiveMetamaskTab) {
notificationManager.showPopup()
}
})
async function triggerUi () {
const tabs = await platform.getActiveTabs()
const currentlyActiveMetamaskTab = Boolean(tabs.find((tab) => openMetamaskTabsIDs[tab.id]))
/**
* https://github.com/poanetwork/metamask-extension/issues/19
* !notificationIsOpen was removed from the check, because notification can be opened, but it can be behind the DApp
* for some reasons. For example, if notification popup was opened, but user moved focus to DApp.
* New transaction, in this case, will not appear in front of DApp.
*/
if (!popupIsOpen && !currentlyActiveMetamaskTab) {
await notificationManager.showPopup()
}
}
/**
* Opens the browser popup for user confirmation of watchAsset
* then it waits until user interact with the UI
*/
function showWatchAssetUi () {
triggerUi()
return new Promise(
async function openPopup () {
await triggerUi()
await new Promise(
(resolve) => {
const interval = setInterval(() => {
if (!notificationIsOpen) {

View File

@ -47,7 +47,7 @@ class PreferencesController {
this.diagnostics = opts.diagnostics
this.network = opts.network
this.store = new ObservableStore(initState)
this.showWatchAssetUi = opts.showWatchAssetUi
this.openPopup = opts.openPopup
this._subscribeProviderType()
}
// PUBLIC METHODS
@ -591,7 +591,7 @@ class PreferencesController {
}
const tokenOpts = { rawAddress, decimals, symbol, image }
this.addSuggestedERC20Asset(tokenOpts)
return this.showWatchAssetUi().then(() => {
return this.openPopup().then(() => {
const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress))
return tokenAddresses.length > 0
})

View File

@ -1,97 +0,0 @@
const asmcrypto = require('asmcrypto.js')
const Unibabel = require('browserify-unibabel')
/**
* A Microsoft Edge-specific encryption class that exposes
* the interface expected by eth-keykeyring-controller
*/
class EdgeEncryptor {
/**
* Encrypts an arbitrary object to ciphertext
*
* @param {string} password Used to generate a key to encrypt the data
* @param {Object} dataObject Data to encrypt
* @returns {Promise<string>} Promise resolving to an object with ciphertext
*/
encrypt (password, dataObject) {
const salt = this._generateSalt()
return this._keyFromPassword(password, salt)
.then(function (key) {
const data = JSON.stringify(dataObject)
const dataBuffer = Unibabel.utf8ToBuffer(data)
const vector = global.crypto.getRandomValues(new Uint8Array(16))
const resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector)
const buffer = new Uint8Array(resultbuffer)
const vectorStr = Unibabel.bufferToBase64(vector)
const vaultStr = Unibabel.bufferToBase64(buffer)
return JSON.stringify({
data: vaultStr,
iv: vectorStr,
salt: salt,
})
})
}
/**
* Decrypts an arbitrary object from ciphertext
*
* @param {string} password Used to generate a key to decrypt the data
* @param {string} text Ciphertext of an encrypted object
* @returns {Promise<Object>} Promise resolving to copy of decrypted object
*/
decrypt (password, text) {
const payload = JSON.parse(text)
const salt = payload.salt
return this._keyFromPassword(password, salt)
.then(function (key) {
const encryptedData = Unibabel.base64ToBuffer(payload.data)
const vector = Unibabel.base64ToBuffer(payload.iv)
return new Promise((resolve, reject) => {
let result
try {
result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector)
} catch (err) {
return reject(new Error('Incorrect password'))
}
const decryptedData = new Uint8Array(result)
const decryptedStr = Unibabel.bufferToUtf8(decryptedData)
const decryptedObj = JSON.parse(decryptedStr)
resolve(decryptedObj)
})
})
}
/**
* Retrieves a cryptographic key using a password
*
* @private
* @param {string} password Password used to unlock a cryptographic key
* @param {string} salt Random base64 data
* @returns {Promise<Object>} Promise resolving to a derived key
*/
_keyFromPassword (password, salt) {
const passBuffer = Unibabel.utf8ToBuffer(password)
const saltBuffer = Unibabel.base64ToBuffer(salt)
return new Promise((resolve) => {
const key = asmcrypto.PBKDF2_HMAC_SHA256.bytes(passBuffer, saltBuffer, 10000)
resolve(key)
})
}
/**
* Generates random base64 encoded data
*
* @private
* @returns {string} Randomized base64 encoded data
*/
_generateSalt (byteCount = 32) {
const view = new Uint8Array(byteCount)
global.crypto.getRandomValues(view)
const b64encoded = btoa(String.fromCharCode.apply(null, view))
return b64encoded
}
}
module.exports = EdgeEncryptor

View File

@ -1,7 +1,7 @@
const extension = require('extensionizer')
const height = 620
const width = 360
import ExtensionPlatform from '../platforms/extension'
const NOTIFICATION_HEIGHT = 620
const NOTIFICATION_WIDTH = 360
class NotificationManager {
@ -12,47 +12,45 @@ class NotificationManager {
*
*/
constructor () {
this.platform = new ExtensionPlatform()
}
/**
* Either brings an existing MetaMask notification window into focus, or creates a new notification window. New
* notification windows are given a 'popup' type.
*
*/
showPopup () {
this._getPopup((err, popup) => {
if (err) throw err
async showPopup () {
const popup = await this._getPopup()
// Bring focus to chrome popup
if (popup) {
// bring focus to existing chrome popup
extension.windows.update(popup.id, { focused: true })
} else {
const cb = (currentPopup) => {
this._popupId = currentPopup.id
extension.windows.update(currentPopup.id, { focused: true })
}
// create new notification popup
const creation = extension.windows.create({
url: 'notification.html',
type: 'popup',
width,
height,
}, cb)
creation && creation.then && creation.then(cb)
}
})
// Bring focus to chrome popup
if (popup) {
// bring focus to existing chrome popup
await this.platform.focusWindow(popup.id)
} else {
// create new notification popup
const popupWindow = await this.platform.openWindow({
url: 'notification.html',
type: 'popup',
width: NOTIFICATION_WIDTH,
height: NOTIFICATION_HEIGHT,
})
this._popupId = popupWindow.id
}
}
/**
* Closes a MetaMask notification if it window exists.
*
*/
closePopup () {
// closes notification popup
this._getPopup((err, popup) => {
if (err) throw err
if (!popup) return
extension.windows.remove(popup.id, console.error)
})
async closePopup () {
const popup = this._getPopup()
if (!popup) {
return
}
await this.platform.removeWindow(popup.id)
}
/**
@ -60,39 +58,19 @@ class NotificationManager {
* type 'popup')
*
* @private
* @param {Function} cb A node style callback that to whcih the found notification window will be passed.
* @param {Function} cb - A node style callback that to whcih the found notification window will be passed.
*
*/
_getPopup (cb) {
this._getWindows((err, windows) => {
if (err) throw err
cb(null, this._getPopupIn(windows))
})
}
/**
* Returns all open MetaMask windows.
*
* @private
* @param {Function} cb A node style callback that to which the windows will be passed.
*
*/
_getWindows (cb) {
// Ignore in test environment
if (!extension.windows) {
return cb()
}
extension.windows.getAll({}, (windows) => {
cb(null, windows)
})
async _getPopup () {
const windows = await this.platform.getAllWindows()
return this._getPopupIn(windows)
}
/**
* Given an array of windows, returns the 'popup' that has been opened by MetaMask, or null if no such window exists.
*
* @private
* @param {array} windows An array of objects containing data about the open MetaMask extension windows.
* @param {array} windows - An array of objects containing data about the open MetaMask extension windows.
*
*/
_getPopupIn (windows) {
@ -104,4 +82,4 @@ class NotificationManager {
}
module.exports = NotificationManager
export default NotificationManager

View File

@ -1,12 +0,0 @@
module.exports = setupMetamaskMeshMetrics
/**
* Injects an iframe into the current document for testing
*/
function setupMetamaskMeshMetrics () {
const testingContainer = document.createElement('iframe')
testingContainer.src = 'https://metamask.github.io/mesh-testing/'
console.log('Injecting Nifty Wallet Mesh testing client')
document.head.appendChild(testingContainer)
}

View File

@ -6,6 +6,7 @@ const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_BACKGROUND,
PLATFORM_FIREFOX,
PLATFORM_OPERA,
PLATFORM_CHROME,
@ -13,17 +14,6 @@ const {
PLATFORM_BRAVE,
} = require('./enums')
/**
* Generates an example stack trace
*
* @returns {string} A stack trace
*
*/
function getStack () {
const stack = new Error('Stack trace generator - not an error').stack
return stack
}
/**
* Used to determine the window type through which the app is being viewed.
* - 'popup' refers to the extension opened through the browser app icon (in top right corner in chrome and firefox)
@ -34,12 +24,15 @@ function getStack () {
*
*/
const getEnvironmentType = (url = window.location.href) => {
if (url.match(/popup.html(?:#.*)*$/)) {
const parsedUrl = new URL(url)
if (parsedUrl.pathname === '/popup.html') {
return ENVIRONMENT_TYPE_POPUP
} else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
} else if (['/home.html', '/phishing.html'].includes(parsedUrl.pathname)) {
return ENVIRONMENT_TYPE_FULLSCREEN
} else {
} else if (parsedUrl.pathname === '/notification.html') {
return ENVIRONMENT_TYPE_NOTIFICATION
} else {
return ENVIRONMENT_TYPE_BACKGROUND
}
}
@ -175,7 +168,6 @@ module.exports = {
removeListeners,
applyListeners,
getPlatform,
getStack,
getEnvironmentType,
sufficientBalance,
hexToBn,

View File

@ -111,7 +111,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
showWatchAssetUi: opts.showWatchAssetUi,
openPopup: opts.openPopup,
network: this.networkController,
})
@ -1945,7 +1945,7 @@ module.exports = class MetamaskController extends EventEmitter {
.sort((block1, block2) => block1.number - block2.number)[recentBlocks.length - 1]
const gasPrice = recentBlock && recentBlock.minimumGasPrice && recentBlock.minimumGasPrice.toString()
if (gasPrice !== '0x' && gasPrice !== '0x0' && gasPrice !== '') {
return gasPrice
} else {

View File

@ -12,8 +12,64 @@ class ExtensionPlatform {
extension.runtime.reload()
}
openWindow ({ url }) {
extension.tabs.create({ url })
openTab (options) {
return new Promise((resolve, reject) => {
extension.tabs.create(options, (newTab) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(newTab)
})
})
}
openWindow (options) {
return new Promise((resolve, reject) => {
extension.windows.create(options, (newWindow) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(newWindow)
})
})
}
closeWindow (windowId) {
return new Promise((resolve, reject) => {
extension.windows.remove(windowId, () => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve()
})
})
}
focusWindow (windowId) {
return new Promise((resolve, reject) => {
extension.windows.update(windowId, { focused: true }, () => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve()
})
})
}
getLastFocusedWindow () {
return new Promise((resolve, reject) => {
extension.windows.getLastFocused((windowObject) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(windowObject)
})
})
}
closeCurrentWindow () {
@ -22,27 +78,6 @@ class ExtensionPlatform {
})
}
/**
* Closes all notifications windows, when action is confirmed in popup
* or closes notification window itself, when action is confirmed from it
*/
closeNotificationWindow () {
return extension.windows.getCurrent((curWindowsDetails) => {
if (curWindowsDetails.type === 'popup') {
return extension.windows.remove(curWindowsDetails.id)
} else {
extension.windows.getAll((windowsDetails) => {
const windowsDetailsFiltered = windowsDetails.filter((windowDetails) => windowDetails.id !== curWindowsDetails.id)
return windowsDetailsFiltered.forEach((windowDetails) => {
if (windowDetails.type === 'popup') {
extension.windows.remove(windowDetails.id)
}
})
})
}
})
}
getVersion () {
return extension.runtime.getManifest().version
}
@ -57,7 +92,7 @@ class ExtensionPlatform {
if (route) {
extensionURL += `#${route}`
}
this.openWindow({ url: extensionURL })
this.openTab({ url: extensionURL })
if (getEnvironmentType() !== ENVIRONMENT_TYPE_BACKGROUND) {
window.close()
}
@ -86,6 +121,30 @@ class ExtensionPlatform {
}
}
getAllWindows () {
return new Promise((resolve, reject) => {
extension.windows.getAll((windows) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(windows)
})
})
}
getActiveTabs () {
return new Promise((resolve, reject) => {
extension.tabs.query({ active: true }, (tabs) => {
const error = checkForError()
if (error) {
return reject(error)
}
return resolve(tabs)
})
})
}
currentTab () {
return new Promise((resolve, reject) => {
extension.tabs.getCurrent((tab) => {

View File

@ -3,11 +3,8 @@ const OldMetaMaskUiCss = require('../../old-ui/css')
const startPopup = require('./popup-core')
const PortStream = require('extension-port-stream')
const { getEnvironmentType } = require('./lib/util')
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums')
import extension from 'extensionizer'
const ExtensionPlatform = require('./platforms/extension')
const NotificationManager = require('./lib/notification-manager')
const notificationManager = new NotificationManager()
const setupRaven = require('./lib/setupRaven')
const log = require('loglevel')
@ -29,7 +26,6 @@ async function start () {
// identify window type (popup, notification)
const windowType = getEnvironmentType(window.location.href)
global.METAMASK_UI_TYPE = windowType
closePopupIfOpen(windowType)
// setup stream to background
const extensionPort = extension.runtime.connect({ name: windowType })
@ -51,13 +47,6 @@ async function start () {
})
function closePopupIfOpen (windowType) {
if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) {
// should close only chrome popup
notificationManager.closePopup()
}
}
function displayCriticalError (err) {
container.innerHTML = '<div class="critical-error">The Nifty Wallet app failed to load: please open and close Nifty Wallet again to restart.</div>'
container.style.height = '80px'

View File

@ -74,7 +74,7 @@ class ConnectScreen extends Component {
<Button
type="primary"
large={true}
onClick={() => global.platform.openWindow({
onClick={() => global.platform.openTab({
url: 'https://google.com/chrome',
})}
>Download Google Chrome</Button>

View File

@ -5,7 +5,7 @@ import PropTypes from 'prop-types'
import clone from 'clone'
import log from 'loglevel'
const ethUtil = require('ethereumjs-util')
import ethUtil from 'ethereumjs-util'
const BN = ethUtil.BN
const hexToBn = require('../../../app/scripts/lib/hex-to-bn')
const util = require('../util')
@ -47,12 +47,12 @@ class PendingTx extends Component {
isUnlocked: PropTypes.bool,
currentCurrency: PropTypes.string,
conversionRate: PropTypes.number,
unconfTxListLength: PropTypes.number,
provider: PropTypes.object,
index: PropTypes.number,
blockGasLimit: PropTypes.string,
tokensToSend: PropTypes.objectOf(BigNumber),
tokensTransferTo: PropTypes.string,
unapprovedTxs: PropTypes.object,
}
constructor (opts = {}) {
@ -132,12 +132,13 @@ class PendingTx extends Component {
const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0
const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation } = this.getNavigateTxData()
const balanceBn = hexToBn(balance)
const insufficientBalance = balanceBn.lt(maxCost)
const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
const gasLimitSpecified = txMeta.gasLimitSpecified
const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
const showRejectAll = props.unconfTxListLength > 1
const isNotification = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION
@ -158,7 +159,6 @@ class PendingTx extends Component {
}
const isError = txMeta.simulationFails || !isValidAddress || insufficientBalance || (dangerousGasLimit && !gasLimitSpecified)
return (
h('div', {
@ -179,7 +179,7 @@ class PendingTx extends Component {
h('.flex-row.flex-center', {
style: {
maxWidth: '100%',
padding: showRejectAll ? '20px 20px 50px 20px' : '20px 20px 20px 20px',
padding: showNavigation ? '20px 20px 50px 20px' : '20px 20px 20px 20px',
background: 'linear-gradient(rgb(84, 36, 147), rgb(104, 45, 182))',
position: 'relative',
},
@ -197,22 +197,22 @@ class PendingTx extends Component {
h('h3', {
style: {
alignSelf: 'center',
display: props.unconfTxListLength > 1 ? 'block' : 'none',
display: showNavigation ? 'block' : 'none',
fontSize: '14px',
},
}, [
h('i.fa.white-arrow-left.fa-lg.cursor-pointer', {
style: {
display: props.index === 0 ? 'none' : 'inline-block',
display: positionOfCurrentTx === 1 ? 'none' : 'inline-block',
},
onClick: () => props.actions.previousTx(),
onClick: () => props.actions.nextTx(prevTxId),
}),
` ${props.index + 1} of ${props.unconfTxListLength} `,
` ${positionOfCurrentTx} of ${totalTx} `,
h('i.fa.white-arrow-right.fa-lg.cursor-pointer', {
style: {
display: props.index + 1 === props.unconfTxListLength ? 'none' : 'inline-block',
display: positionOfCurrentTx === totalTx ? 'none' : 'inline-block',
},
onClick: () => props.actions.nextTx(),
onClick: () => props.actions.nextTx(nextTxId),
}),
])],
),
@ -519,7 +519,7 @@ class PendingTx extends Component {
onClick: props.cancelTransaction,
}, 'Reject'),
]),
showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
showNavigation ? h('.flex-row.flex-space-around.conf-buttons', {
style: {
display: 'flex',
justifyContent: 'flex-end',
@ -731,6 +731,23 @@ class PendingTx extends Component {
}
}
getNavigateTxData () {
const { unapprovedTxs, network, txData: { id } = {} } = this.props
const currentNetworkUnapprovedTxs = Object.keys(unapprovedTxs)
.filter((key) => unapprovedTxs[key].metamaskNetworkId === network)
.reduce((acc, key) => ({ ...acc, [key]: unapprovedTxs[key] }), {})
const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs)
const currentPosition = enumUnapprovedTxs.indexOf(id ? id.toString() : '')
return {
totalTx: enumUnapprovedTxs.length,
positionOfCurrentTx: currentPosition + 1,
nextTxId: enumUnapprovedTxs[currentPosition + 1],
prevTxId: enumUnapprovedTxs[currentPosition - 1],
showNavigation: enumUnapprovedTxs.length > 1,
}
}
}
function forwardCarrat () {
@ -755,7 +772,7 @@ function mapStateToProps (state) {
unapprovedMsgs: state.metamask.unapprovedMsgs,
unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs,
unapprovedTypedMessages: state.metamask.unapprovedTypedMessages,
index: state.appState.currentView.pendingTxIndex || 0,
index: state.appState.currentView.key || 0,
warning: state.appState.warning,
network: state.metamask.network,
provider: state.metamask.provider,
@ -764,14 +781,14 @@ function mapStateToProps (state) {
currentCurrency: state.metamask.currentCurrency,
blockGasLimit: state.metamask.currentBlockGasLimit,
computedBalances: state.metamask.computedBalances,
pendingTxIndex: state.appState.currentView.pendingTxIndex || 0,
}
}
const mapDispatchToProps = (dispatch) => {
return {
actions: {
previousTx: () => dispatch(actions.previousTx()),
nextTx: () => dispatch(actions.nextTx()),
nextTx: (txId) => dispatch(actions.nextTx(txId)),
displayWarning: (msg) => dispatch(actions.displayWarning(msg)),
goHome: () => dispatch(actions.goHome()),
},

View File

@ -212,7 +212,7 @@ ShiftListItem.prototype.renderInfo = function () {
paddingLeft: '29px',
textAlign: 'left',
},
onClick: () => global.platform.openWindow({ url }),
onClick: () => global.platform.openTab({ url }),
}, [
h('div', {
style: {

View File

@ -1,11 +1,11 @@
const inherits = require('util').inherits
const Component = require('react').Component
import PropTypes from 'prop-types'
import { Component } from 'react'
import { connect } from 'react-redux'
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../ui/app/actions')
const LoadingIndicator = require('./components/loading')
const txHelper = require('../lib/tx-helper')
const log = require('loglevel')
import log from 'loglevel'
const { getCurrentKeyring, ifContractAcc } = require('./util')
const PendingTx = require('./components/pending-tx')
@ -15,118 +15,224 @@ import PendingTypedMsg from './components/pending-typed-msg'
const Loading = require('./components/loading')
const { DAI_CODE, POA_SOKOL_CODE, RSK_TESTNET_CODE, GOERLI_TESTNET_CODE } = require('../../app/scripts/controllers/network/enums')
const { getMetaMaskAccounts } = require('../../ui/app/selectors')
import BigNumber from 'bignumber.js'
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
function mapStateToProps (state) {
const { metamask, appState } = state
const { screenParams, pendingTxIndex } = appState.currentView
return {
identities: metamask.identities,
accounts: getMetaMaskAccounts(state),
keyrings: metamask.keyrings,
selectedAddress: metamask.selectedAddress,
unapprovedTxs: metamask.unapprovedTxs,
unapprovedMsgs: metamask.unapprovedMsgs,
unapprovedPersonalMsgs: metamask.unapprovedPersonalMsgs,
unapprovedTypedMessages: metamask.unapprovedTypedMessages,
index: pendingTxIndex || 0,
warning: appState.warning,
network: metamask.network,
provider: metamask.provider,
conversionRate: metamask.conversionRate,
currentCurrency: metamask.currentCurrency,
blockGasLimit: metamask.currentBlockGasLimit,
computedBalances: metamask.computedBalances,
isToken: (screenParams && screenParams.isToken),
tokenSymbol: (screenParams && screenParams.tokenSymbol),
tokensToSend: (screenParams && screenParams.tokensToSend),
tokensTransferTo: (screenParams && screenParams.tokensTransferTo),
isContractExecutionByUser: (screenParams && screenParams.isContractExecutionByUser),
}
}
inherits(ConfirmTxScreen, Component)
function ConfirmTxScreen () {
Component.call(this)
}
ConfirmTxScreen.prototype.render = function () {
const props = this.props
const { network, unapprovedTxs, currentCurrency, computedBalances,
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, blockGasLimit } = props
let { conversionRate } = props
const isTestnet = parseInt(network) === POA_SOKOL_CODE || parseInt(network) === RSK_TESTNET_CODE || parseInt(network) === GOERLI_TESTNET_CODE
const isDai = parseInt(network) === DAI_CODE
if (isTestnet) {
conversionRate = 0
} else if (isDai) {
conversionRate = 1
class ConfirmTxScreen extends Component {
static propTypes = {
network: PropTypes.string,
identities: PropTypes.objectOf(PropTypes.object),
keyrings: PropTypes.array,
actions: PropTypes.objectOf(PropTypes.func),
isToken: PropTypes.bool,
isContractExecutionByUser: PropTypes.bool,
selectedAddress: PropTypes.string,
warning: PropTypes.string,
unapprovedTxs: PropTypes.object,
unapprovedMsgs: PropTypes.object,
unapprovedPersonalMsgs: PropTypes.object,
unapprovedTypedMessages: PropTypes.object,
pendingTxIndex: PropTypes.number,
blockGasLimit: PropTypes.string,
accounts: PropTypes.object,
currentCurrency: PropTypes.string,
computedBalances: PropTypes.object,
conversionRate: PropTypes.number,
tokenSymbol: PropTypes.string,
tokensToSend: PropTypes.objectOf(BigNumber),
tokensTransferTo: PropTypes.string,
}
const unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
const ind = props.index || 0
const txData = unconfTxList[ind] || {}
const txParams = txData.params || {}
render () {
const props = this.props
const { network, unapprovedTxs, currentCurrency, computedBalances,
unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, blockGasLimit } = props
let { conversionRate } = props
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })
const isTestnet = parseInt(network) === POA_SOKOL_CODE || parseInt(network) === RSK_TESTNET_CODE || parseInt(network) === GOERLI_TESTNET_CODE
const isDai = parseInt(network) === DAI_CODE
if (isTestnet) {
conversionRate = 0
} else if (isDai) {
conversionRate = 1
}
const unconfTxListLength = unconfTxList.length
const unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network)
const ind = props.pendingTxIndex || 0
const txData = unconfTxList[ind] || {}
const txParams = txData.params || {}
return (
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })
h('.flex-column.flex-grow', {
style: {
width: '100%',
},
}, [
const unconfTxListLength = unconfTxList.length
h(LoadingIndicator, {
isLoading: this.state ? !this.state.bypassLoadingScreen : txData.loadingDefaults,
loadingMessage: 'Estimating transaction cost…',
canBypass: true,
bypass: () => {
this.setState({bypassLoadingScreen: true})
return (
h('.flex-column.flex-grow', {
style: {
width: '100%',
},
}),
}, [
// subtitle and nav
h(LoadingIndicator, {
isLoading: this.state ? !this.state.bypassLoadingScreen : txData.loadingDefaults,
loadingMessage: 'Estimating transaction cost…',
canBypass: true,
bypass: () => {
this.setState({bypassLoadingScreen: true})
},
}),
warningIfExists(props.warning),
// subtitle and nav
currentTxView({
// Properties
txData: txData,
key: txData.id,
selectedAddress: props.selectedAddress,
accounts: props.accounts,
identities: props.identities,
conversionRate,
currentCurrency,
blockGasLimit,
unconfTxListLength,
computedBalances,
network,
isToken: props.isToken,
tokenSymbol: props.tokenSymbol,
tokensToSend: props.tokensToSend,
tokensTransferTo: props.tokensTransferTo,
// Actions
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress, props.isContractExecutionByUser),
sendTransaction: this.sendTransaction.bind(this),
cancelTransaction: this.cancelTransaction.bind(this, txData),
cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
signTypedMessage: this.signTypedMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
}),
])
)
warningIfExists(props.warning),
currentTxView({
// Properties
txData: txData,
key: txData.id,
selectedAddress: props.selectedAddress,
accounts: props.accounts,
identities: props.identities,
conversionRate,
currentCurrency,
blockGasLimit,
unconfTxListLength,
computedBalances,
network,
isToken: props.isToken,
tokenSymbol: props.tokenSymbol,
tokensToSend: props.tokensToSend,
tokensTransferTo: props.tokensTransferTo,
// Actions
buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress, props.isContractExecutionByUser),
sendTransaction: this.sendTransaction.bind(this),
cancelTransaction: this.cancelTransaction.bind(this, txData),
cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
signTypedMessage: this.signTypedMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
}),
])
)
}
buyEth (address, isContractExecutionByUser, event) {
event.preventDefault()
this.props.actions.buyEthView(address, isContractExecutionByUser)
}
sendTransaction (txData, event) {
this.stopPropagation(event)
this.props.actions.updateAndApproveTx(txData)
this._checkIfContractExecutionAndUnlockContract(txData)
}
cancelTransaction (txData, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.actions.cancelTx(txData)
this._checkIfContractExecutionAndUnlockContract(txData)
}
cancelAllTransactions (unconfTxList, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.actions.cancelTxs(unconfTxList)
this._checkIfMultipleContractExecutionAndUnlockContract(unconfTxList)
}
signMessage (msgData, event) {
log.info('conf-tx.js: signing message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.actions.signMsg(params)
}
stopPropagation (event) {
if (event.stopPropagation) {
event.stopPropagation()
}
}
signPersonalMessage (msgData, event) {
log.info('conf-tx.js: signing personal message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.actions.signPersonalMsg(params)
}
signTypedMessage (msgData, event) {
log.info('conf-tx.js: signing typed message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.actions.signTypedMsg(params)
}
cancelMessage (msgData, event) {
log.info('canceling message')
this.stopPropagation(event)
this.props.actions.cancelMsg(msgData)
}
cancelPersonalMessage (msgData, event) {
log.info('canceling personal message')
this.stopPropagation(event)
this.props.actions.cancelPersonalMsg(msgData)
}
cancelTypedMessage (msgData, event) {
log.info('canceling typed message')
this.stopPropagation(event)
this.props.actions.cancelTypedMsg(msgData)
}
_checkIfMultipleContractExecutionAndUnlockContract (unconfTxList) {
const areTxsToOneContractFromTheList = unconfTxList.slice(0).reduce((res, txData, ind, unconfTxList) => {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList && Object.keys(res).length === 0) {
res = { status: true, to }
} else if (res.status && res.to !== to) {
res = { status: false }
unconfTxList.splice(1)
}
} else {
res = { status: false }
unconfTxList.splice(1)
}
return res
}, {})
if (areTxsToOneContractFromTheList.status) {
this._unlockContract(areTxsToOneContractFromTheList.to)
}
}
_checkIfContractExecutionAndUnlockContract (txData) {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList) {
this._unlockContract(to)
}
}
}
_unlockContract (to) {
const currentKeyring = getCurrentKeyring(to, this.props.network, this.props.keyrings, this.props.identities)
if (ifContractAcc(currentKeyring)) {
this.props.actions.showAccountDetail(to)
}
}
}
function currentTxView (opts) {
@ -153,119 +259,6 @@ function currentTxView (opts) {
}
}
ConfirmTxScreen.prototype.buyEth = function (address, isContractExecutionByUser, event) {
event.preventDefault()
this.props.dispatch(actions.buyEthView(address, isContractExecutionByUser))
}
ConfirmTxScreen.prototype.sendTransaction = function (txData, event) {
this.stopPropagation(event)
this.props.dispatch(actions.updateAndApproveTx(txData))
this._checkIfContractExecutionAndUnlockContract(txData)
}
ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.dispatch(actions.cancelTx(txData))
this._checkIfContractExecutionAndUnlockContract(txData)
}
ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) {
this.stopPropagation(event)
event.preventDefault()
this.props.dispatch(actions.cancelAllTx(unconfTxList))
this._checkIfMultipleContractExecutionAndUnlockContract(unconfTxList)
}
ConfirmTxScreen.prototype.signMessage = function (msgData, event) {
log.info('conf-tx.js: signing message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signMsg(params))
}
ConfirmTxScreen.prototype.stopPropagation = function (event) {
if (event.stopPropagation) {
event.stopPropagation()
}
}
ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) {
log.info('conf-tx.js: signing personal message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signPersonalMsg(params))
}
ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) {
log.info('conf-tx.js: signing typed message')
const params = msgData.msgParams
params.metamaskId = msgData.id
this.stopPropagation(event)
this.props.dispatch(actions.signTypedMsg(params))
}
ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) {
log.info('canceling message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelMsg(msgData))
}
ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) {
log.info('canceling personal message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelPersonalMsg(msgData))
}
ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) {
log.info('canceling typed message')
this.stopPropagation(event)
this.props.dispatch(actions.cancelTypedMsg(msgData))
}
ConfirmTxScreen.prototype._checkIfMultipleContractExecutionAndUnlockContract = function (unconfTxList) {
const areTxsToOneContractFromTheList = unconfTxList.slice(0).reduce((res, txData, ind, unconfTxList) => {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList && Object.keys(res).length === 0) {
res = { status: true, to }
} else if (res.status && res.to !== to) {
res = { status: false }
unconfTxList.splice(1)
}
} else {
res = { status: false }
unconfTxList.splice(1)
}
return res
}, {})
if (areTxsToOneContractFromTheList.status) {
this._unlockContract(areTxsToOneContractFromTheList.to)
}
}
ConfirmTxScreen.prototype._checkIfContractExecutionAndUnlockContract = function (txData) {
if (txData.txParams.data && this.props.isContractExecutionByUser) {
const to = txData && txData.txParams && txData.txParams.to
const targetContractIsInTheList = Object.keys(this.props.accounts).some((acc) => acc === to)
if (targetContractIsInTheList) {
this._unlockContract(to)
}
}
}
ConfirmTxScreen.prototype._unlockContract = function (to) {
const currentKeyring = getCurrentKeyring(to, this.props.network, this.props.keyrings, this.props.identities)
if (ifContractAcc(currentKeyring)) {
this.props.dispatch(actions.showAccountDetail(to))
}
}
function warningIfExists (warning) {
if (warning &&
// Do not display user rejections on this screen:
@ -277,3 +270,51 @@ function warningIfExists (warning) {
}, warning)
}
}
function mapStateToProps (state) {
const { metamask, appState } = state
const { screenParams, pendingTxIndex } = appState.currentView
return {
identities: metamask.identities,
accounts: getMetaMaskAccounts(state),
keyrings: metamask.keyrings,
selectedAddress: metamask.selectedAddress,
unapprovedTxs: metamask.unapprovedTxs,
unapprovedMsgs: metamask.unapprovedMsgs,
unapprovedPersonalMsgs: metamask.unapprovedPersonalMsgs,
unapprovedTypedMessages: metamask.unapprovedTypedMessages,
pendingTxIndex: pendingTxIndex || 0,
warning: appState.warning,
network: metamask.network,
provider: metamask.provider,
conversionRate: metamask.conversionRate,
currentCurrency: metamask.currentCurrency,
blockGasLimit: metamask.currentBlockGasLimit,
computedBalances: metamask.computedBalances,
isToken: (screenParams && screenParams.isToken),
tokenSymbol: (screenParams && screenParams.tokenSymbol),
tokensToSend: (screenParams && screenParams.tokensToSend),
tokensTransferTo: (screenParams && screenParams.tokensTransferTo),
isContractExecutionByUser: (screenParams && screenParams.isContractExecutionByUser),
}
}
function mapDispatchToProps (dispatch) {
return {
actions: {
buyEthView: (address, isContractExecutionByUser) => dispatch(actions.buyEthView(address, isContractExecutionByUser)),
updateAndApproveTx: (txData) => dispatch(actions.updateAndApproveTx(txData)),
cancelTx: (txData) => dispatch(actions.cancelTx(txData)),
cancelTxs: (unconfTxList) => dispatch(actions.cancelTxs(unconfTxList)),
signMsg: (params) => dispatch(actions.signMsg(params)),
signPersonalMsg: (params) => dispatch(actions.signPersonalMsg(params)),
signTypedMsg: (params) => dispatch(actions.signTypedMsg(params)),
cancelMsg: (msgData) => dispatch(actions.cancelMsg(msgData)),
cancelPersonalMsg: (msgData) => dispatch(actions.cancelPersonalMsg(msgData)),
cancelTypedMsg: (msgData) => dispatch(actions.cancelTypedMsg(msgData)),
showAccountDetail: (to) => dispatch(actions.showAccountDetail(to)),
},
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConfirmTxScreen)

View File

@ -298,7 +298,7 @@ module.exports = {
accountName: By.className('font-medium color-forest'),
edit: By.className('edit-text'),
iconCopy: By.className('clipboard cursor-pointer white'),
transactionList: By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'),
transactionList: By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div.flex-column > div > div:nth-child(1)'),
buttons: {
send: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)'),
buy: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(3)'),
@ -309,7 +309,7 @@ module.exports = {
},
network: By.className('network-name'),
sent: {
menu: By.className('wallet-view__tab-history'),
menu: By.css('#wallet-view__tab-history'),
tokens: By.className('activeForm right'),
},
// balance: By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'),

View File

@ -60,6 +60,8 @@ const importGanacheSeedPhrase = async (f, account2, password) => {
})
it('finds the transaction in the transactions list', async () => {
const sentTab = await f.waitUntilShowUp(screens.main.sent.menu)
await sentTab.click()
const transactionAmount = await f.waitUntilShowUp(screens.main.transactionList)
assert.equal(await transactionAmount.getText(), '10.0')
})

View File

@ -443,7 +443,7 @@ describe('preferences controller', function () {
req.params.options = { address, symbol, decimals, image }
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
preferencesController.showWatchAssetUi = async () => {}
preferencesController.openPopup = async () => {}
await preferencesController._handleWatchAssetERC20(req.params.options)
const suggested = preferencesController.getSuggestedTokens()
@ -463,7 +463,7 @@ describe('preferences controller', function () {
req.params.options = { address, symbol, decimals, image }
sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true)
preferencesController.showWatchAssetUi = async () => {
preferencesController.openPopup = async () => {
await preferencesController.addToken(address, symbol, decimals, image)
}

View File

@ -1,101 +0,0 @@
const assert = require('assert')
const EdgeEncryptor = require('../../../app/scripts/edge-encryptor')
var password = 'passw0rd1'
var data = 'some random data'
global.crypto = global.crypto || {
getRandomValues: function (array) {
for (let i = 0; i < array.length; i++) {
array[i] = Math.random() * 100
}
return array
},
}
describe('EdgeEncryptor', function () {
const edgeEncryptor = new EdgeEncryptor()
describe('encrypt', function () {
it('should encrypt the data.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
assert.notEqual(data, encryptedData)
assert.notEqual(encryptedData.length, 0)
done()
}).catch(function (err) {
done(err)
})
})
it('should return proper format.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
const encryptedObject = JSON.parse(encryptedData)
assert.ok(encryptedObject.data, 'there is no data')
assert.ok(encryptedObject.iv && encryptedObject.iv.length !== 0, 'there is no iv')
assert.ok(encryptedObject.salt && encryptedObject.salt.length !== 0, 'there is no salt')
done()
}).catch(function (err) {
done(err)
})
})
it('should not return the same twice.', function (done) {
const encryptPromises = []
encryptPromises.push(edgeEncryptor.encrypt(password, data))
encryptPromises.push(edgeEncryptor.encrypt(password, data))
Promise.all(encryptPromises).then((encryptedData) => {
assert.equal(encryptedData.length, 2)
assert.notEqual(encryptedData[0], encryptedData[1])
assert.notEqual(encryptedData[0].length, 0)
assert.notEqual(encryptedData[1].length, 0)
done()
})
})
})
describe('decrypt', function () {
it('should be able to decrypt the encrypted data.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
edgeEncryptor.decrypt(password, encryptedData)
.then(function (decryptedData) {
assert.equal(decryptedData, data)
done()
})
.catch(function (err) {
done(err)
})
})
.catch(function (err) {
done(err)
})
})
it('cannot decrypt the encrypted data with wrong password.', function (done) {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
edgeEncryptor.decrypt('wrong password', encryptedData)
.then(function (decryptedData) {
assert.fail('could decrypt with wrong password')
done()
})
.catch(function (err) {
assert.ok(err instanceof Error)
assert.equal(err.message, 'Incorrect password')
done()
})
})
.catch(function (err) {
done(err)
})
})
})
})

View File

@ -19,6 +19,7 @@ const { POA,
CLASSIC } = require('../../app/scripts/controllers/network/enums')
const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util')
const WebcamUtils = require('../lib/webcam-utils')
import { getEnvironmentType } from '../../app/scripts/lib/util'
const actions = {
_setBackgroundConnection: _setBackgroundConnection,
@ -30,6 +31,9 @@ const actions = {
MODAL_CLOSE: 'UI_MODAL_CLOSE',
showModal: showModal,
hideModal: hideModal,
CLOSE_NOTIFICATION_WINDOW: 'CLOSE_NOTIFICATION_WINDOW',
// sidebar state
SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN',
SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE',
@ -173,7 +177,6 @@ const actions = {
COMPLETED_TX: 'COMPLETED_TX',
TRANSACTION_ERROR: 'TRANSACTION_ERROR',
NEXT_TX: 'NEXT_TX',
PREVIOUS_TX: 'PREV_TX',
EDIT_TX: 'EDIT_TX',
signMsg: signMsg,
cancelMsg: cancelMsg,
@ -186,14 +189,12 @@ const actions = {
signTokenTx: signTokenTx,
updateTransaction,
updateAndApproveTx,
cancelTx: cancelTx,
cancelTx,
cancelTxs,
completedTx: completedTx,
txError: txError,
nextTx: nextTx,
editTx,
previousTx: previousTx,
cancelAllTx: cancelAllTx,
viewPendingTx: viewPendingTx,
VIEW_PENDING_TX: 'VIEW_PENDING_TX',
updateTransactionParams,
@ -370,6 +371,8 @@ const actions = {
getRequestAccountTabIds,
setOpenMetamaskTabsIDs,
getOpenMetamaskTabsIds,
closeCurrentNotificationWindow,
closeNotificationWindow,
}
module.exports = actions
@ -918,9 +921,8 @@ function setCurrentCurrency (currencyCode) {
function signMsg (msgData) {
log.debug('action - signMsg')
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.signMessage`)
background.signMessage(msgData, (err, newState) => {
@ -935,10 +937,7 @@ function signMsg (msgData) {
}
dispatch(actions.completedTx(msgData.metamaskId))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -948,7 +947,7 @@ function signMsg (msgData) {
function signPersonalMsg (msgData) {
log.debug('action - signPersonalMsg')
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -966,9 +965,7 @@ function signPersonalMsg (msgData) {
dispatch(actions.completedTx(msgData.metamaskId))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -978,7 +975,7 @@ function signPersonalMsg (msgData) {
function signTypedMsg (msgData) {
log.debug('action - signTypedMsg')
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -996,9 +993,7 @@ function signTypedMsg (msgData) {
dispatch(actions.completedTx(msgData.metamaskId))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1205,7 +1200,7 @@ function clearSend () {
function sendTx (txData) {
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
return (dispatch, getState) => {
return (dispatch) => {
log.debug(`actions calling background.approveTransaction`)
background.approveTransaction(txData.id, (err) => {
if (err) {
@ -1214,10 +1209,7 @@ function sendTx (txData) {
return log.error(err)
}
dispatch(actions.completedTx(txData.id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
})
}
}
@ -1267,10 +1259,9 @@ function updateTransaction (txData) {
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch, getState) => {
return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx`)
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.updateAndApproveTransaction(txData, err => {
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
@ -1293,11 +1284,7 @@ function updateAndApproveTx (txData) {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
dispatch(actions.hideLoadingIndication())
dispatch(actions.setCurrentAccountTab('history'))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return txData
})
@ -1331,7 +1318,7 @@ function txError (err) {
}
function cancelMsg (msgData) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -1346,9 +1333,7 @@ function cancelMsg (msgData) {
dispatch(actions.completedTx(msgData.id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1357,7 +1342,7 @@ function cancelMsg (msgData) {
}
function cancelPersonalMsg (msgData) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -1372,9 +1357,7 @@ function cancelPersonalMsg (msgData) {
dispatch(actions.completedTx(id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1383,7 +1366,7 @@ function cancelPersonalMsg (msgData) {
}
function cancelTypedMsg (msgData) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
@ -1398,9 +1381,7 @@ function cancelTypedMsg (msgData) {
dispatch(actions.completedTx(id))
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return resolve(msgData)
})
@ -1409,12 +1390,11 @@ function cancelTypedMsg (msgData) {
}
function cancelTx (txData) {
return (dispatch, getState) => {
return (dispatch) => {
log.debug(`background.cancelTransaction`)
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.cancelTransaction(txData.id, err => {
background.cancelTransaction(txData.id, (err) => {
if (err) {
return reject(err)
}
@ -1423,15 +1403,12 @@ function cancelTx (txData) {
})
})
.then(() => updateMetamaskStateFromBackground())
.then(newState => dispatch(actions.updateMetamaskState(newState)))
.then((newState) => dispatch(actions.updateMetamaskState(newState)))
.then(() => {
dispatch(actions.clearSend())
dispatch(actions.completedTx(txData.id))
dispatch(actions.hideLoadingIndication())
if (!hasUnconfirmedTransactions(getState())) {
return global.platform.closeNotificationWindow()
}
dispatch(actions.closeCurrentNotificationWindow())
return txData
})
@ -1444,9 +1421,9 @@ function cancelTx (txData) {
* @return {function(*): Promise<void>}
*/
function cancelTxs (txDataList) {
return async (dispatch, getState) => {
return async (dispatch) => {
dispatch(actions.showLoadingIndication())
const txIds = txDataList.map(({id}) => id)
const txIds = txDataList.map(({ id }) => id)
const cancellations = txIds.map((id) => new Promise((resolve, reject) => {
background.cancelTransaction(id, (err) => {
if (err) {
@ -1468,30 +1445,12 @@ function cancelTxs (txDataList) {
dispatch(actions.hideLoadingIndication())
if (global.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION) {
return global.platform.closeCurrentWindow()
}
}
}
/**
* @deprecated
* @param {Array<object>} txsData
* @return {Function}
*/
function cancelAllTx (txsData) {
return (dispatch) => {
txsData.forEach((txData, i) => {
background.cancelTransaction(txData.id, () => {
dispatch(actions.completedTx(txData.id))
if (i === txsData.length - 1) {
dispatch(actions.goHome())
global.platform.closeNotificationWindow()
}
})
})
}
}
//
// initialize screen
//
@ -1762,9 +1721,10 @@ function showConfTxPage (screenParams) {
}
}
function nextTx () {
function nextTx (txId) {
return {
type: actions.NEXT_TX,
value: txId,
}
}
@ -1775,12 +1735,6 @@ function viewPendingTx (txId) {
}
}
function previousTx () {
return {
type: actions.PREVIOUS_TX,
}
}
function editTx (txId) {
return {
type: actions.EDIT_TX,
@ -2131,6 +2085,23 @@ function hideModal (payload) {
}
}
function closeCurrentNotificationWindow () {
return (dispatch, getState) => {
if (getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION &&
!hasUnconfirmedTransactions(getState())) {
global.platform.closeCurrentWindow()
dispatch(actions.closeNotificationWindow())
}
}
}
function closeNotificationWindow () {
return {
type: actions.CLOSE_NOTIFICATION_WINDOW,
}
}
function showSidebar ({ transitionName, type }) {
return {
type: actions.SIDEBAR_OPEN,
@ -2335,7 +2306,7 @@ function showSendContractPage ({methodSelected, methodABI, inputValues}) {
function buyEth (opts) {
return (dispatch) => {
const url = getBuyEthUrl(opts)
global.platform.openWindow({ url })
global.platform.openTab({ url })
dispatch({
type: actions.BUY_ETH,
})

View File

@ -68,7 +68,7 @@ TokenMenuDropdown.prototype.render = function () {
onClick: (e) => {
e.stopPropagation()
const url = ethNetProps.explorerLinks.getExplorerAccountLinkFor(this.props.token.address, this.props.network)
global.platform.openWindow({ url })
global.platform.openTab({ url })
this.props.onClose()
},
text: this.context.t('viewOnEtherscan'),

View File

@ -79,7 +79,8 @@ function reduceApp (state, action) {
customHdPaths: customHdPaths,
}, state.appState)
let curPendingTxIndex = appState.currentView.pendingTxIndex || 0
const curPendingTxIndex = appState.currentView.pendingTxIndex || 0
const curPendingTxId = appState.currentView.pendingTxId || 0
switch (action.type) {
// dropdown methods
@ -507,6 +508,7 @@ function reduceApp (state, action) {
currentView: {
name: 'confTx',
pendingTxIndex: action.id ? indexForPending(state, action.id) : 0,
pendingTxId: action.id,
screenParams: action.value,
},
transForward: action.transForward,
@ -559,11 +561,14 @@ function reduceApp (state, action) {
}
case actions.NEXT_TX:
const increment = (action.value - curPendingTxId)
return extend(appState, {
transForward: true,
currentView: {
name: 'confTx',
pendingTxIndex: ++curPendingTxIndex,
pendingTxIndex: curPendingTxIndex + increment,
pendingTxId: action.value,
index: curPendingTxIndex + increment,
warning: null,
},
})
@ -575,16 +580,7 @@ function reduceApp (state, action) {
currentView: {
name: 'confTx',
pendingTxIndex,
warning: null,
},
})
case actions.PREVIOUS_TX:
return extend(appState, {
transForward: false,
currentView: {
name: 'confTx',
pendingTxIndex: --curPendingTxIndex,
pendingTxId: action.value,
warning: null,
},
})

View File

@ -6,6 +6,8 @@ import { roundExponential } from '../helpers/confirm-transaction/util'
const unapprovedTxsSelector = state => state.metamask.unapprovedTxs
const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
const unapprovedPersonalMsgsSelector = state => state.metamask.unapprovedPersonalMsgs
const unapprovedDecryptMsgsSelector = (state) => state.metamask.unapprovedDecryptMsgs
const unapprovedEncryptionPublicKeyMsgsSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgs
const unapprovedTypedMessagesSelector = state => state.metamask.unapprovedTypedMessages
const networkSelector = state => state.metamask.network
@ -13,18 +15,24 @@ export const unconfirmedTransactionsListSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
unapprovedTypedMessages = {},
network,
) => txHelper(
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedDecryptMsgs,
unapprovedEncryptionPublicKeyMsgs,
unapprovedTypedMessages,
network,
) || [],
@ -34,12 +42,16 @@ export const unconfirmedTransactionsHashSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
unapprovedTypedMessages = {},
network,
) => {
@ -58,6 +70,8 @@ export const unconfirmedTransactionsHashSelector = createSelector(
...filteredUnapprovedTxs,
...unapprovedMsgs,
...unapprovedPersonalMsgs,
...unapprovedDecryptMsgs,
...unapprovedEncryptionPublicKeyMsgs,
...unapprovedTypedMessages,
}
},
@ -65,18 +79,24 @@ export const unconfirmedTransactionsHashSelector = createSelector(
const unapprovedMsgCountSelector = state => state.metamask.unapprovedMsgCount
const unapprovedPersonalMsgCountSelector = state => state.metamask.unapprovedPersonalMsgCount
const unapprovedDecryptMsgCountSelector = (state) => state.metamask.unapprovedDecryptMsgCount
const unapprovedEncryptionPublicKeyMsgCountSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgCount
const unapprovedTypedMessagesCountSelector = state => state.metamask.unapprovedTypedMessagesCount
export const unconfirmedTransactionsCountSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgCountSelector,
unapprovedPersonalMsgCountSelector,
unapprovedDecryptMsgCountSelector,
unapprovedEncryptionPublicKeyMsgCountSelector,
unapprovedTypedMessagesCountSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedDecryptMsgCount = 0,
unapprovedEncryptionPublicKeyMsgCount = 0,
unapprovedTypedMessagesCount = 0,
network,
) => {
@ -86,7 +106,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
})
return filteredUnapprovedTxIds.length + unapprovedTypedMessagesCount + unapprovedMsgCount +
unapprovedPersonalMsgCount
unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount
},
)
@ -132,15 +152,16 @@ export const tokenAmountAndToAddressSelector = createSelector(
let tokenAmount = 0
if (params && params.length) {
const toParam = params.find(param => param.name === TOKEN_PARAM_TO)
const valueParam = params.find(param => param.name === TOKEN_PARAM_VALUE)
const toParam = params.find((param) => param.name === TOKEN_PARAM_TO)
const valueParam = params.find((param) => param.name === TOKEN_PARAM_VALUE)
toAddress = toParam ? toParam.value : params[0].value
const value = valueParam ? Number(valueParam.value) : Number(params[1].value)
tokenAmount = roundExponential(value)
if (tokenDecimals) {
tokenAmount = calcTokenAmount(value, tokenDecimals)
tokenAmount = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(tokenAmount)
}
return {
@ -158,11 +179,11 @@ export const approveTokenAmountAndToAddressSelector = createSelector(
let tokenAmount = 0
if (params && params.length) {
toAddress = params.find(param => param.name === TOKEN_PARAM_SPENDER).value
const value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
toAddress = params.find((param) => param.name === TOKEN_PARAM_SPENDER).value
const value = Number(params.find((param) => param.name === TOKEN_PARAM_VALUE).value)
if (tokenDecimals) {
tokenAmount = calcTokenAmount(value, tokenDecimals)
tokenAmount = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(tokenAmount)
@ -183,11 +204,11 @@ export const sendTokenTokenAmountAndToAddressSelector = createSelector(
let tokenAmount = 0
if (params && params.length) {
toAddress = params.find(param => param.name === TOKEN_PARAM_TO).value
let value = Number(params.find(param => param.name === TOKEN_PARAM_VALUE).value)
toAddress = params.find((param) => param.name === TOKEN_PARAM_TO).value
let value = Number(params.find((param) => param.name === TOKEN_PARAM_VALUE).value)
if (tokenDecimals) {
value = calcTokenAmount(value, tokenDecimals)
value = calcTokenAmount(value, tokenDecimals).toNumber()
}
tokenAmount = roundExponential(value)

View File

@ -54,9 +54,8 @@ async function startApp (metamaskState, accountManager, opts) {
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
const numberOfUnapprivedTx = unapprovedTxsAll.length
if (numberOfUnapprivedTx > 0) {
store.dispatch(actions.showConfTxPage({
id: unapprovedTxsAll[numberOfUnapprivedTx - 1].id,
id: unapprovedTxsAll[0].id,
}))
}