1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge branch 'develop' into testing

This commit is contained in:
Thomas 2018-05-17 00:01:36 -07:00
commit 770379c3da
26 changed files with 371 additions and 328 deletions

View File

@ -13,20 +13,6 @@ const RINKEBY_DISPLAY_NAME = 'Rinkeby'
const KOVAN_DISPLAY_NAME = 'Kovan'
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
const MAINNET_RPC_URL = 'https://mainnet.infura.io/metamask'
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
const LOCALHOST_RPC_URL = 'http://localhost:8545'
const MAINNET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
const DEFAULT_NETWORK = 'rinkeby'
const OLD_UI_NETWORK_TYPE = 'network'
const BETA_UI_NETWORK_TYPE = 'networkBeta'
module.exports = {
ROPSTEN,
@ -41,16 +27,4 @@ module.exports = {
RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME,
MAINNET_RPC_URL,
ROPSTEN_RPC_URL,
KOVAN_RPC_URL,
RINKEBY_RPC_URL,
LOCALHOST_RPC_URL,
MAINNET_RPC_URL_BETA,
ROPSTEN_RPC_URL_BETA,
KOVAN_RPC_URL_BETA,
RINKEBY_RPC_URL_BETA,
DEFAULT_NETWORK,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
}

View File

@ -14,52 +14,40 @@ const {
RINKEBY,
KOVAN,
MAINNET,
OLD_UI_NETWORK_TYPE,
DEFAULT_NETWORK,
LOCALHOST,
} = require('./enums')
const { getNetworkEndpoints } = require('./util')
const LOCALHOST_RPC_URL = 'http://localhost:8545'
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
const testMode = (METAMASK_DEBUG || env === 'test')
const defaultProviderConfig = {
type: testMode ? RINKEBY : MAINNET,
}
module.exports = class NetworkController extends EventEmitter {
constructor (config) {
constructor (opts = {}) {
super()
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
this._networkEndpoints = getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
// parse options
const providerConfig = opts.provider || defaultProviderConfig
// create stores
this.providerStore = new ObservableStore(providerConfig)
this.networkStore = new ObservableStore('loading')
this.providerStore = new ObservableStore(config.provider)
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
// create event emitter proxy
this._proxy = createEventEmitterProxy()
this.on('networkDidChange', this.lookupNetwork)
}
async setNetworkEndpoints (version) {
if (version === this._networkEndpointVersion) {
return
}
this._networkEndpointVersion = version
this._networkEndpoints = getNetworkEndpoints(version)
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
const { type } = this.getProviderConfig()
return this.setProviderType(type, true)
}
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
const { type, rpcTarget } = this.providerStore.getState()
// map rpcTarget to rpcUrl
const opts = {
type,
rpcUrl: rpcTarget,
}
this._configureProvider(opts)
this._configureProvider({ type, rpcTarget })
this._proxy.on('block', this._logBlock.bind(this))
this._proxy.on('error', this.verifyNetwork.bind(this))
this.ethQuery = new EthQuery(this._proxy)
@ -96,45 +84,27 @@ module.exports = class NetworkController extends EventEmitter {
})
}
setRpcTarget (rpcUrl) {
this.providerStore.updateState({
setRpcTarget (rpcTarget) {
const providerConfig = {
type: 'rpc',
rpcTarget: rpcUrl,
})
this._switchNetwork({ rpcUrl })
}
getCurrentRpcAddress () {
const provider = this.getProviderConfig()
if (!provider) return null
return this.getRpcAddressForType(provider.type)
}
async setProviderType (type, forceUpdate = false) {
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
// skip if type already matches
if (type === this.getProviderConfig().type && !forceUpdate) {
return
rpcTarget,
}
this.providerStore.updateState(providerConfig)
this._switchNetwork(providerConfig)
}
const rpcTarget = this.getRpcAddressForType(type)
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
this.providerStore.updateState({ type, rpcTarget })
this._switchNetwork({ type })
async setProviderType (type) {
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
const providerConfig = { type }
this.providerStore.updateState(providerConfig)
this._switchNetwork(providerConfig)
}
getProviderConfig () {
return this.providerStore.getState()
}
getRpcAddressForType (type, provider = this.getProviderConfig()) {
if (this._networkEndpoints[type]) {
return this._networkEndpoints[type]
}
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
}
//
// Private
//
@ -146,32 +116,27 @@ module.exports = class NetworkController extends EventEmitter {
}
_configureProvider (opts) {
// type-based rpc endpoints
const { type } = opts
if (type) {
// type-based infura rpc endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
opts.rpcUrl = this.getRpcAddressForType(type)
if (isInfura) {
this._configureInfuraProvider(opts)
// other type-based rpc endpoints
} else {
this._configureStandardProvider(opts)
}
const { type, rpcTarget } = opts
// infura type-based endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
if (isInfura) {
this._configureInfuraProvider(opts)
// other type-based rpc endpoints
} else if (type === LOCALHOST) {
this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL })
// url-based rpc endpoints
} else if (type === 'rpc'){
this._configureStandardProvider({ rpcUrl: rpcTarget })
} else {
this._configureStandardProvider(opts)
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
}
}
_configureInfuraProvider (opts) {
log.info('_configureInfuraProvider', opts)
const infuraProvider = createInfuraProvider({
network: opts.type,
})
_configureInfuraProvider ({ type }) {
log.info('_configureInfuraProvider', type)
const infuraProvider = createInfuraProvider({ network: type })
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
const providerParams = extend(this._baseProviderParams, {
rpcUrl: opts.rpcUrl,
engineParams: {
pollingInterval: 8000,
blockTrackerProvider: infuraProvider,

View File

@ -3,7 +3,6 @@ const {
RINKEBY,
KOVAN,
MAINNET,
LOCALHOST,
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
@ -11,17 +10,6 @@ const {
RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME,
MAINNET_RPC_URL,
ROPSTEN_RPC_URL,
KOVAN_RPC_URL,
RINKEBY_RPC_URL,
LOCALHOST_RPC_URL,
MAINNET_RPC_URL_BETA,
ROPSTEN_RPC_URL_BETA,
KOVAN_RPC_URL_BETA,
RINKEBY_RPC_URL_BETA,
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
} = require('./enums')
const networkToNameMap = {
@ -34,32 +22,8 @@ const networkToNameMap = {
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
}
const networkEndpointsMap = {
[OLD_UI_NETWORK_TYPE]: {
[LOCALHOST]: LOCALHOST_RPC_URL,
[MAINNET]: MAINNET_RPC_URL,
[ROPSTEN]: ROPSTEN_RPC_URL,
[KOVAN]: KOVAN_RPC_URL,
[RINKEBY]: RINKEBY_RPC_URL,
},
[BETA_UI_NETWORK_TYPE]: {
[LOCALHOST]: LOCALHOST_RPC_URL,
[MAINNET]: MAINNET_RPC_URL_BETA,
[ROPSTEN]: ROPSTEN_RPC_URL_BETA,
[KOVAN]: KOVAN_RPC_URL_BETA,
[RINKEBY]: RINKEBY_RPC_URL_BETA,
},
}
const getNetworkDisplayName = key => networkToNameMap[key]
const getNetworkEndpoints = (networkType = OLD_UI_NETWORK_TYPE) => {
return {
...networkEndpointsMap[networkType],
}
}
module.exports = {
getNetworkDisplayName,
getNetworkEndpoints,
}

View File

@ -25,26 +25,31 @@ function migrateFromSnapshotsToDiffs (longHistory) {
}
/**
generates an array of history objects sense the previous state.
The object has the keys opp(the operation preformed),
path(the key and if a nested object then each key will be seperated with a `/`)
value
with the first entry having the note
Generates an array of history objects sense the previous state.
The object has the keys
op (the operation performed),
path (the key and if a nested object then each key will be seperated with a `/`)
value
with the first entry having the note and a timestamp when the change took place
@param previousState {object} - the previous state of the object
@param newState {object} - the update object
@param note {string} - a optional note for the state change
@reurns {array}
@returns {array}
*/
function generateHistoryEntry (previousState, newState, note) {
const entry = jsonDiffer.compare(previousState, newState)
// Add a note to the first op, since it breaks if we append it to the entry
if (note && entry[0]) entry[0].note = note
if (entry[0]) {
if (note) entry[0].note = note
entry[0].timestamp = Date.now()
}
return entry
}
/**
Recovers previous txMeta state obj
@return {object}
@returns {object}
*/
function replayHistory (_shortHistory) {
const shortHistory = clone(_shortHistory)

View File

@ -158,7 +158,7 @@ class TransactionStateManager extends EventEmitter {
/**
updates the txMeta in the list and adds a history entry
@param txMeta {Object} - the txMeta to update
@param [note] {string} - a not about the update for history
@param [note] {string} - a note about the update for history
*/
updateTx (txMeta, note) {
// validate txParams

View File

@ -1,7 +1,3 @@
// test and development environment variables
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
/**
* @typedef {Object} FirstTimeState
@ -14,11 +10,6 @@ const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
*/
const initialState = {
config: {},
NetworkController: {
provider: {
type: (METAMASK_DEBUG || env === 'test') ? DEFAULT_NETWORK : MAINNET,
},
},
}
module.exports = initialState

View File

@ -25,7 +25,7 @@ function setupRaven(opts) {
const report = opts.data
try {
// handle error-like non-error exceptions
nonErrorException(report)
rewriteErrorLikeExceptions(report)
// simplify certain complex error messages (e.g. Ethjs)
simplifyErrorMessages(report)
// modify report urls
@ -42,27 +42,35 @@ function setupRaven(opts) {
return Raven
}
function nonErrorException(report) {
// handle errors that lost their error-ness in serialization
if (report.message.includes('Non-Error exception captured with keys: message')) {
if (!(report.extra && report.extra.__serialized__)) return
report.message = `Non-Error Exception: ${report.extra.__serialized__.message}`
}
function rewriteErrorLikeExceptions(report) {
// handle errors that lost their error-ness in serialization (e.g. dnode)
rewriteErrorMessages(report, (errorMessage) => {
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage
if (!(report.extra && report.extra.__serialized__ && report.extra.__serialized__.message)) return errorMessage
return `Non-Error Exception: ${report.extra.__serialized__.message}`
})
}
function simplifyErrorMessages(report) {
rewriteErrorMessages(report, (errorMessage) => {
// simplify ethjs error messages
errorMessage = extractEthjsErrorMessage(errorMessage)
// simplify 'Transaction Failed: known transaction'
if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
// cut the hash from the error message
errorMessage = 'Transaction Failed: known transaction'
}
return errorMessage
})
}
function rewriteErrorMessages(report, rewriteFn) {
// rewrite top level message
report.message = rewriteFn(report.message)
// rewrite each exception message
if (report.exception && report.exception.values) {
report.exception.values.forEach(item => {
let errorMessage = item.value
// simplify ethjs error messages
errorMessage = extractEthjsErrorMessage(errorMessage)
// simplify 'Transaction Failed: known transaction'
if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
// cut the hash from the error message
errorMessage = 'Transaction Failed: known transaction'
}
// finalize
item.value = errorMessage
item.value = rewriteFn(item.value)
})
}
}

View File

@ -355,7 +355,6 @@ module.exports = class MetamaskController extends EventEmitter {
submitPassword: nodeify(keyringController.submitPassword, keyringController),
// network management
setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
setProviderType: nodeify(networkController.setProviderType, networkController),
setCustomRpc: nodeify(this.setCustomRpc, this),

View File

@ -0,0 +1,96 @@
# Send screen QA checklist:
This checklist can be to guide QA of the send screen. It can also be used to guide e2e tests for the send screen.
Once all of these are QA verified on master, resolutions to any bugs related to the send screen should include and update to this list.
Additional features or functionality on the send screen should include an update to this list.
## Send Eth mode
- [ ] **Header** _It should:_
- [ ] have title "Send ETH"
- [ ] have sub title "Only send ETH to an Ethereum address."
- [ ] return user to main screen when top right X is clicked
- [ ] **From row** _It should:_
- [ ] show the currently selected account by default
- [ ] show a dropdown with all of the users accounts
- [ ] contain the following info for each account: identicon, account name, balance in ETH, balance in current currency
- [ ] change the account selected in the dropdown (but not the app-wide selected account) when one in the dropdown is clicked
- [ ] close the dropdown, without changing the dropdown selected account, when the dropdown is open and then a click happens outside it
- [ ] **To row** _It should:_
- [ ] Show a placeholder with the text 'Recipient Address' by default
- [ ] Show, when clicked, a dropdown list of all 'to accounts': the users accounts, plus any other accounts they have previously sent to
- [ ] Show account address, and account name if it exists, of each item in the dropdown list
- [ ] Show a dropdown list of all to accounts (see above) whose address matches an address currently being typed in
- [ ] Set the input text to the address of an account clicked in the dropdown list, and also hide the dropdown
- [ ] Hide the dropdown without changing what is in the input if the user clicks outside the dropdown list while it is open
- [ ] Select the text in the input (i.e. the address) if an address is displayed and then clicked
- [ ] Show a 'required' error if the dropdown is opened but no account is selected
- [ ] Show an 'invalid address' error if text is entered in the input that cannot be a valid hex address or ens address
- [ ] Support ens names. (enter dinodan.eth on mainnet) After entering the plain text address, the hex address should appear in the input with a green checkmark beside
- [ ] Should show a 'no such address' error if a non-existent ens address is entered
- [ ] **Amount row** _It should:_
- [ ] allow user to enter any rational number >= 0
- [ ] allow user to copy and paste into the field
- [ ] show an insufficient funds error if an amount > balance - gas fee
- [ ] display 'ETH' after the number amount. The position of 'ETH' should change as the length of the input amount text changes
- [ ] display the value of the amount of ETH in the current currency, formatted in that currency
- [ ] show a 'max' but if amount < balance - gas fee
- [ ] show no max button or error if amount === balance - gas fee
- [ ] set the amount to balance - gas fee if the 'max' button is clicked
- [ ] **Gas Fee Display row** _It should:_
- [ ] Default to the fee given by the estimated gas price
- [ ] display the fee in ETH and the current currency
- [ ] update when changes are made using the customize gas modal
- [ ] **Cancel button** _It should:_
- [ ] Take the user back to the main screen
- [ ] **submit button** _It should:_
- [ ] be disabled if no recipient address is provided or if any field is in error
- [ ] sign a transaction with the info in the above form, and display the details of that transaction on the confirm screen
## Send token mode
- [ ] **Header** _It should:_
- [ ] have title "Send Tokens"
- [ ] have sub title "Only send [token symbol] to an Ethereum address."
- [ ] return user to main screen when top right X is clicked
- [ ] **From row** _It should:_
- [ ] Behave the same as 'Send ETH mode' (see above)
- [ ] **To row** _It should:_
- [ ] Behave the same as 'Send ETH mode' (see above)
- [ ] **Amount row** _It should:_
- [ ] allow user to enter any rational number >= 0
- [ ] allow user to copy and paste into the field
- [ ] show an 'insufficient tokens' error if an amount > token balance
- [ ] show an 'insufficient funds' error if an gas fee > eth balance
- [ ] display [token symbol] after the number amount. The position of [token symbol] should change as the length of the input amount text changes
- [ ] display the value of the amount of tokens in the current currency, formatted in that currency
- [ ] show a 'max' but if amount < token balance
- [ ] show no max button or error if amount === token balance
- [ ] set the amount to token balance if the 'max' button is clicked
- [ ] **Gas Fee Display row** _It should:_
- [ ] Behave the same as 'Send ETH mode' (see above)
- [ ] **Cancel button** _It should:_
- [ ] Take the user back to the main screen
- [ ] **submit button** _It should:_
- [ ] be disabled if no recipient address is provided or if any field is in error
- [ ] sign a token transaction with the info in the above form, and display the details of that transaction on the confirm screen
## Edit send Eth mode
- [ ] Say 'Editing transaction' in the header
- [ ] display a button to go back to the confirmation screen without applying update
- [ ] say 'update transaction' on the submit button
- [ ] update the existing transaction, instead of signing a new one, when clicking the submit button
- [ ] Otherwise, behave the same as 'Send ETH mode' (see above)
## Edit send token mode
- [ ] Behave the same as 'Edit send Eth mode' (see above)
## Specific cases to test
- [ ] Send eth to a hex address
- [ ] Send eth to an ENS address
- [ ] Donate to the faucet at https://faucet.metamask.io/ and edit the transaction before confirming
- [ ] Send a token that is available on the 'Add Token' screen search to a hex address
- [ ] Create a custom token at https://tokenfactory.surge.sh/ and send it to a hex address
- [ ] Send a token to an ENS address
- [ ] Create a token transaction using https://tokenfactory.surge.sh/#/, and edit the transaction before confirming
- [ ] Send each of MKR, EOS and ICON using myetherwallet, and edit the transaction before confirming

View File

@ -35,7 +35,6 @@ const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
module.exports = connect(mapStateToProps)(App)
@ -409,7 +408,6 @@ App.prototype.renderDropdown = function () {
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
onClick: () => {
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
},
}, 'Try Beta!'),
])
@ -472,7 +470,6 @@ App.prototype.renderPrimary = function () {
onClick: () => {
global.platform.openExtensionInBrowser()
props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
.then(() => props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
},
style: {
fontSize: '0.8em',

View File

@ -3,17 +3,15 @@ const nock = require('nock')
const NetworkController = require('../../app/scripts/controllers/network')
const {
getNetworkDisplayName,
getNetworkEndpoints,
} = require('../../app/scripts/controllers/network/util')
const { createTestProviderTools } = require('../stub/provider')
const providerResultStub = {}
const provider = createTestProviderTools({ scaffold: providerResultStub }).provider
describe('# Network Controller', function () {
let networkController
const noop = () => {}
const networkControllerProviderInit = {
const networkControllerProviderConfig = {
getAccounts: noop,
}
@ -24,11 +22,9 @@ describe('# Network Controller', function () {
.post('/metamask')
.reply(200)
networkController = new NetworkController({
provider,
})
networkController = new NetworkController()
networkController.initializeProvider(networkControllerProviderInit, provider)
networkController.initializeProvider(networkControllerProviderConfig)
})
afterEach(function () {
@ -38,7 +34,7 @@ describe('# Network Controller', function () {
describe('network', function () {
describe('#provider', function () {
it('provider should be updatable without reassignment', function () {
networkController.initializeProvider(networkControllerProviderInit, provider)
networkController.initializeProvider(networkControllerProviderConfig)
const proxy = networkController._proxy
proxy.setTarget({ test: true, on: () => {} })
assert.ok(proxy.test)
@ -59,12 +55,6 @@ describe('# Network Controller', function () {
})
})
describe('#getRpcAddressForType', function () {
it('should return the right rpc address', function () {
const rpcTarget = networkController.getRpcAddressForType('mainnet')
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
})
})
describe('#setProviderType', function () {
it('should update provider.type', function () {
networkController.setProviderType('mainnet')
@ -76,16 +66,11 @@ describe('# Network Controller', function () {
const loading = networkController.isNetworkLoading()
assert.ok(loading, 'network is loading')
})
it('should set the right rpcTarget', function () {
networkController.setProviderType('mainnet')
const rpcTarget = networkController.getProviderConfig().rpcTarget
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
})
})
})
})
describe('# Network utils', () => {
describe('Network utils', () => {
it('getNetworkDisplayName should return the correct network name', () => {
const tests = [
{
@ -114,9 +99,4 @@ describe('# Network utils', () => {
tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected))
})
it('getNetworkEndpoints should return the correct endpoints', () => {
assert.equal(getNetworkEndpoints('networkBeta').ropsten, 'https://ropsten.infura.io/metamask2')
assert.equal(getNetworkEndpoints('network').rinkeby, 'https://rinkeby.infura.io/metamask')
})
})

View File

@ -1,26 +1,129 @@
const assert = require('assert')
const clone = require('clone')
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
const testVault = require('../data/v17-long-history.json')
describe('deepCloneFromTxMeta', function () {
it('should clone deep', function () {
const input = {
foo: {
bar: {
bam: 'baz'
describe ('Transaction state history helper', function () {
describe('#snapshotFromTxMeta', function () {
it('should clone deep', function () {
const input = {
foo: {
bar: {
bam: 'baz'
}
}
}
}
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
assert('foo' in output, 'has a foo key')
assert('bar' in output.foo, 'has a bar key')
assert('bam' in output.foo.bar, 'has a bar key')
assert.equal(output.foo.bar.bam, 'baz', 'has a baz value')
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
assert('foo' in output, 'has a foo key')
assert('bar' in output.foo, 'has a bar key')
assert('bam' in output.foo.bar, 'has a bar key')
assert.equal(output.foo.bar.bam, 'baz', 'has a baz value')
})
it('should remove the history key', function () {
const input = { foo: 'bar', history: 'remembered' }
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
assert(typeof output.history, 'undefined', 'should remove history')
})
})
it('should remove the history key', function () {
const input = { foo: 'bar', history: 'remembered' }
const output = txStateHistoryHelper.snapshotFromTxMeta(input)
assert(typeof output.history, 'undefined', 'should remove history')
describe('#migrateFromSnapshotsToDiffs', function () {
it('migrates history to diffs and can recover original values', function () {
testVault.data.TransactionController.transactions.forEach((tx, index) => {
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
newHistory.forEach((newEntry, index) => {
if (index === 0) {
assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
} else {
assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
}
const oldEntry = tx.history[index]
const historySubset = newHistory.slice(0, index + 1)
const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
})
})
})
})
})
describe('#replayHistory', function () {
it('replaying history does not mutate the original obj', function () {
const initialState = { test: true, message: 'hello', value: 1 }
const diff1 = [{
"op": "replace",
"path": "/message",
"value": "haay",
}]
const diff2 = [{
"op": "replace",
"path": "/value",
"value": 2,
}]
const history = [initialState, diff1, diff2]
const beforeStateSnapshot = JSON.stringify(initialState)
const latestState = txStateHistoryHelper.replayHistory(history)
const afterStateSnapshot = JSON.stringify(initialState)
assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
})
})
describe('#generateHistoryEntry', function () {
function generateHistoryEntryTest(note) {
const prevState = {
someValue: 'value 1',
foo: {
bar: {
bam: 'baz'
}
}
}
const nextState = {
newPropRoot: 'new property - root',
someValue: 'value 2',
foo: {
newPropFirstLevel: 'new property - first level',
bar: {
bam: 'baz'
}
}
}
const before = new Date().getTime()
const result = txStateHistoryHelper.generateHistoryEntry(prevState, nextState, note)
const after = new Date().getTime()
assert.ok(Array.isArray(result))
assert.equal(result.length, 3)
const expectedEntry1 = { op: 'add', path: '/foo/newPropFirstLevel', value: 'new property - first level' }
assert.equal(result[0].op, expectedEntry1.op)
assert.equal(result[0].path, expectedEntry1.path)
assert.equal(result[0].value, expectedEntry1.value)
assert.equal(result[0].value, expectedEntry1.value)
if (note)
assert.equal(result[0].note, note)
assert.ok(result[0].timestamp >= before && result[0].timestamp <= after)
const expectedEntry2 = { op: 'replace', path: '/someValue', value: 'value 2' }
assert.deepEqual(result[1], expectedEntry2)
const expectedEntry3 = { op: 'add', path: '/newPropRoot', value: 'new property - root' }
assert.deepEqual(result[2], expectedEntry3)
}
it('should generate history entries', function () {
generateHistoryEntryTest()
})
it('should add note to first entry', function () {
generateHistoryEntryTest('custom note')
})
})
})

View File

@ -1,46 +0,0 @@
const assert = require('assert')
const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper')
const testVault = require('../data/v17-long-history.json')
describe('tx-state-history-helper', function () {
it('migrates history to diffs and can recover original values', function () {
testVault.data.TransactionController.transactions.forEach((tx, index) => {
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
newHistory.forEach((newEntry, index) => {
if (index === 0) {
assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj')
} else {
assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj')
}
const oldEntry = tx.history[index]
const historySubset = newHistory.slice(0, index + 1)
const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset)
assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs')
})
})
})
it('replaying history does not mutate the original obj', function () {
const initialState = { test: true, message: 'hello', value: 1 }
const diff1 = [{
"op": "replace",
"path": "/message",
"value": "haay",
}]
const diff2 = [{
"op": "replace",
"path": "/value",
"value": 2,
}]
const history = [initialState, diff1, diff2]
const beforeStateSnapshot = JSON.stringify(initialState)
const latestState = txStateHistoryHelper.replayHistory(history)
const afterStateSnapshot = JSON.stringify(initialState)
assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
})
})

View File

@ -176,14 +176,21 @@ describe('TransactionStateManager', function () {
assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state')
// modify value and updateTx
updatedTx.txParams.gasPrice = desiredGasPrice
const before = new Date().getTime()
txStateManager.updateTx(updatedTx)
const after = new Date().getTime()
// check updated value
const result = txStateManager.getTx('1')
assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated')
// validate history was updated
assert.equal(result.history.length, 2, 'two history items (initial + diff)')
assert.equal(result.history[1].length, 1, 'two history state items (initial + diff)')
const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice }
assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)')
assert.deepEqual(result.history[1][0].op, expectedEntry.op, 'two history items (initial + diff) operation')
assert.deepEqual(result.history[1][0].path, expectedEntry.path, 'two history items (initial + diff) path')
assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value')
assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after)
})
})

View File

@ -271,7 +271,6 @@ var actions = {
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
// Network
setNetworkEndpoints,
updateNetworkEndpointType,
UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE',
@ -1924,23 +1923,6 @@ function setLocaleMessages (localeMessages) {
}
}
function setNetworkEndpoints (networkEndpointType) {
return dispatch => {
log.debug('background.setNetworkEndpoints')
return new Promise((resolve, reject) => {
background.setNetworkEndpoints(networkEndpointType, err => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updateNetworkEndpointType(networkEndpointType))
resolve(networkEndpointType)
})
})
}
}
function updateNetworkEndpointType (networkEndpointType) {
return {
type: actions.UPDATE_NETWORK_ENDPOINT_TYPE,

View File

@ -1,7 +1,7 @@
const { Component } = require('react')
const PropTypes = require('prop-types')
const connect = require('react-redux').connect
const { Route, Switch, withRouter, matchPath } = require('react-router-dom')
const { Route, Switch, withRouter } = require('react-router-dom')
const { compose } = require('recompose')
const h = require('react-hyperscript')
const actions = require('./actions')
@ -83,15 +83,6 @@ class App extends Component {
)
}
renderAppHeader () {
const { location } = this.props
const isInitializing = matchPath(location.pathname, {
path: INITIALIZE_ROUTE, exact: false,
})
return isInitializing ? null : h(AppHeader)
}
render () {
const {
isLoading,
@ -128,7 +119,7 @@ class App extends Component {
// global modal
h(Modal, {}, []),
this.renderAppHeader(),
h(AppHeader),
// sidebar
this.renderSidebar(),

View File

@ -1,9 +1,13 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { matchPath } from 'react-router-dom'
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../../app/scripts/lib/enums')
const { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
const {
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP,
} = require('../../../../app/scripts/lib/enums')
const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
const Identicon = require('../identicon')
const NetworkIndicator = require('../network')
@ -36,13 +40,23 @@ class AppHeader extends Component {
: hideNetworkDropdown()
}
isConfirming () {
const { location } = this.props
return Boolean(matchPath(location.pathname, {
path: CONFIRM_TRANSACTION_ROUTE, exact: false,
}))
}
renderAccountMenu () {
const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props
return isUnlocked && (
<div
className="account-menu__icon"
onClick={toggleAccountMenu}
className={classnames('account-menu__icon', {
'account-menu__icon--disabled': this.isConfirming(),
})}
onClick={() => this.isConfirming() || toggleAccountMenu()}
>
<Identicon
address={selectedAddress}
@ -52,6 +66,26 @@ class AppHeader extends Component {
)
}
hideAppHeader () {
const { location } = this.props
const isInitializing = Boolean(matchPath(location.pathname, {
path: INITIALIZE_ROUTE, exact: false,
}))
if (isInitializing) {
return true
}
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
return true
}
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP && this.isConfirming()) {
return true
}
}
render () {
const {
network,
@ -61,7 +95,7 @@ class AppHeader extends Component {
isUnlocked,
} = this.props
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
if (this.hideAppHeader()) {
return null
}

View File

@ -12,7 +12,6 @@ const SimpleDropdown = require('../../dropdowns/simple-dropdown')
const ToggleButton = require('react-toggle-button')
const { REVEAL_SEED_ROUTE } = require('../../../routes')
const locales = require('../../../../../app/_locales/index.json')
const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/controllers/network/enums')
const getInfuraCurrencyOptions = () => {
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
@ -349,7 +348,6 @@ const mapDispatchToProps = dispatch => {
updateCurrentLocale: key => dispatch(actions.updateCurrentLocale(key)),
setFeatureFlagToBeta: () => {
return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
.then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
},
showResetAccountConfirmationModal: () => {
return dispatch(actions.showModal({ name: 'CONFIRM_RESET_ACCOUNT' }))

View File

@ -175,7 +175,6 @@ UnlockPage.propTypes = {
isUnlocked: PropTypes.bool,
t: PropTypes.func,
useOldInterface: PropTypes.func,
setNetworkEndpoints: PropTypes.func,
}
export default UnlockPage

View File

@ -6,7 +6,6 @@ const {
tryUnlockMetamask,
forgotPassword,
markPasswordForgotten,
setNetworkEndpoints,
} = require('../../../actions')
import UnlockPage from './unlock-page.component'
@ -23,7 +22,6 @@ const mapDispatchToProps = dispatch => {
forgotPassword: () => dispatch(forgotPassword()),
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
markPasswordForgotten: () => dispatch(markPasswordForgotten()),
setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)),
}
}

View File

@ -28,6 +28,10 @@ const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
} = require('../../../../app/scripts/lib/enums')
ConfirmSendEther.contextTypes = {
t: PropTypes.func,
@ -293,6 +297,14 @@ ConfirmSendEther.prototype.editTransaction = function (txMeta) {
history.push(SEND_ROUTE)
}
ConfirmSendEther.prototype.renderNetworkDisplay = function () {
const windowType = window.METAMASK_UI_TYPE
return (windowType === ENVIRONMENT_TYPE_NOTIFICATION || windowType === ENVIRONMENT_TYPE_POPUP)
? h(NetworkDisplay)
: null
}
ConfirmSendEther.prototype.render = function () {
const {
currentCurrency,
@ -358,7 +370,7 @@ ConfirmSendEther.prototype.render = function () {
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
},
}, 'Edit'),
window.METAMASK_UI_TYPE === 'notification' && h(NetworkDisplay),
this.renderNetworkDisplay(),
]),
h('.page-container__title', title),
h('.page-container__subtitle', subtitle),

View File

@ -23,6 +23,10 @@
&__icon {
margin-left: 20px;
cursor: pointer;
&--disabled {
cursor: initial;
}
}
&__header {

View File

@ -82,10 +82,6 @@
display: flex;
flex-flow: row nowrap;
align-items: center;
.identicon {
cursor: pointer;
}
}
}

View File

@ -10,7 +10,6 @@ const getCaretCoordinates = require('textarea-caret')
const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes')
const { getEnvironmentType } = require('../../../app/scripts/lib/util')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../app/scripts/lib/enums')
const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/controllers/network/enums')
class InitializeMenuScreen extends Component {
constructor (props) {
@ -190,7 +189,6 @@ class InitializeMenuScreen extends Component {
showOldUI () {
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
.then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
}
}

View File

@ -6,8 +6,7 @@ const { HashRouter } = require('react-router-dom')
const App = require('./app')
const OldApp = require('../../old-ui/app/app')
const { autoAddToBetaUI } = require('./selectors')
const { setFeatureFlag, setNetworkEndpoints } = require('./actions')
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
const { setFeatureFlag } = require('./actions')
const I18nProvider = require('./i18n-provider')
function mapStateToProps (state) {
@ -24,11 +23,9 @@ function mapDispatchToProps (dispatch) {
return {
setFeatureFlagWithModal: () => {
return dispatch(setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
.then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
},
setFeatureFlagWithoutModal: () => {
return dispatch(setFeatureFlag('betaUI', true))
.then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
},
}
}

View File

@ -5,11 +5,6 @@ const actions = require('./app/actions')
const configureStore = require('./app/store')
const txHelper = require('./lib/tx-helper')
const { fetchLocale } = require('./i18n-helper')
const {
OLD_UI_NETWORK_TYPE,
BETA_UI_NETWORK_TYPE,
} = require('../app/scripts/controllers/network/enums')
const log = require('loglevel')
module.exports = launchMetamaskUi
@ -55,10 +50,6 @@ async function startApp (metamaskState, accountManager, opts) {
networkVersion: opts.networkVersion,
})
const useBetaUi = metamaskState.featureFlags.betaUI
const networkEndpointType = useBetaUi ? BETA_UI_NETWORK_TYPE : OLD_UI_NETWORK_TYPE
store.dispatch(actions.setNetworkEndpoints(networkEndpointType))
// if unconfirmed txs, start on txConf page
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
const numberOfUnapprivedTx = unapprovedTxsAll.length