aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2016-06-25 08:11:55 +0800
committerGitHub <noreply@github.com>2016-06-25 08:11:55 +0800
commit52518a5efc2f37881c2e88c8cb0649ad5bc41dd4 (patch)
tree54734b126f94cedf1a19c9e406cb6f0a6091476b
parent251cb84afb91cfbae14f8e68314b57c1e25a4af3 (diff)
parent60fec24c003605047f3ab7c011a1ee98432b369b (diff)
downloadtangerine-wallet-browser-52518a5efc2f37881c2e88c8cb0649ad5bc41dd4.tar.gz
tangerine-wallet-browser-52518a5efc2f37881c2e88c8cb0649ad5bc41dd4.tar.zst
tangerine-wallet-browser-52518a5efc2f37881c2e88c8cb0649ad5bc41dd4.zip
Merge pull request #323 from MetaMask/RefactorBackground
Refactored background.js controller apart from chrome + transport
-rw-r--r--app/scripts/background.js343
-rw-r--r--app/scripts/lib/config-manager-singleton.js3
-rw-r--r--app/scripts/lib/config-manager.js56
-rw-r--r--app/scripts/lib/id-management.js6
-rw-r--r--app/scripts/lib/idStore.js28
-rw-r--r--app/scripts/metamask-controller.js257
-rw-r--r--test/lib/mock-config-manager.js57
-rw-r--r--test/unit/config-manager-test.js7
-rw-r--r--test/unit/idStore-test.js4
9 files changed, 448 insertions, 313 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 6934e9d3e..97e3269ba 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -1,19 +1,51 @@
const urlUtil = require('url')
+const extend = require('xtend')
const Dnode = require('dnode')
const eos = require('end-of-stream')
-const extend = require('xtend')
-const EthStore = require('eth-store')
-const MetaMaskProvider = require('web3-provider-engine/zero.js')
const PortStream = require('./lib/port-stream.js')
-const IdentityStore = require('./lib/idStore')
const createUnlockRequestNotification = require('./lib/notifications.js').createUnlockRequestNotification
const createTxNotification = require('./lib/notifications.js').createTxNotification
const createMsgNotification = require('./lib/notifications.js').createMsgNotification
-const configManager = require('./lib/config-manager-singleton')
const messageManager = require('./lib/message-manager')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
-const HostStore = require('./lib/remote-store.js').HostStore
-const Web3 = require('web3')
+const MetamaskController = require('./metamask-controller')
+
+const STORAGE_KEY = 'metamask-config'
+
+const controller = new MetamaskController({
+ // User confirmation callbacks:
+ showUnconfirmedMessage,
+ unlockAccountMessage,
+ showUnconfirmedTx,
+ // Persistence Methods:
+ setData,
+ loadData,
+})
+const idStore = controller.idStore
+
+function unlockAccountMessage () {
+ createUnlockRequestNotification({
+ title: 'Account Unlock Request',
+ })
+}
+
+function showUnconfirmedMessage (msgParams, msgId) {
+ createMsgNotification({
+ title: 'New Unsigned Message',
+ msgParams: msgParams,
+ confirm: idStore.approveMessage.bind(idStore, msgId, noop),
+ cancel: idStore.cancelMessage.bind(idStore, msgId),
+ })
+}
+
+function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
+ createTxNotification({
+ title: 'New Unsigned Transaction',
+ txParams: txParams,
+ confirm: idStore.approveTransaction.bind(idStore, txData.id, noop),
+ cancel: idStore.cancelTransaction.bind(idStore, txData.id),
+ })
+}
//
// connect to other contexts
@@ -37,8 +69,8 @@ function setupUntrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
var mx = setupMultiplex(connectionStream)
// connect features
- setupProviderConnection(mx.createStream('provider'), originDomain)
- setupPublicConfig(mx.createStream('publicConfig'))
+ controller.setupProviderConnection(mx.createStream('provider'), originDomain)
+ controller.setupPublicConfig(mx.createStream('publicConfig'))
}
function setupTrustedCommunication (connectionStream, originDomain) {
@@ -46,181 +78,28 @@ function setupTrustedCommunication (connectionStream, originDomain) {
var mx = setupMultiplex(connectionStream)
// connect features
setupControllerConnection(mx.createStream('controller'))
- setupProviderConnection(mx.createStream('provider'), originDomain)
-}
-
-//
-// state and network
-//
-
-var idStore = new IdentityStore()
-
-var providerOpts = {
- rpcUrl: configManager.getCurrentRpcAddress(),
- // account mgmt
- getAccounts: function (cb) {
- var selectedAddress = idStore.getSelectedAddress()
- var result = selectedAddress ? [selectedAddress] : []
- cb(null, result)
- },
- // tx signing
- approveTransaction: newUnsignedTransaction,
- signTransaction: idStore.signTransaction.bind(idStore),
- // msg signing
- approveMessage: newUnsignedMessage,
- signMessage: idStore.signMessage.bind(idStore),
-}
-var provider = MetaMaskProvider(providerOpts)
-var web3 = new Web3(provider)
-idStore.web3 = web3
-idStore.getNetwork()
-
-// log new blocks
-provider.on('block', function (block) {
- console.log('BLOCK CHANGED:', '#' + block.number.toString('hex'), '0x' + block.hash.toString('hex'))
-
- // Check network when restoring connectivity:
- if (idStore._currentState.network === 'loading') {
- idStore.getNetwork()
- }
-})
-
-provider.on('error', idStore.getNetwork.bind(idStore))
-
-var ethStore = new EthStore(provider)
-idStore.setStore(ethStore)
-
-function getState () {
- var state = extend(
- ethStore.getState(),
- idStore.getState(),
- configManager.getConfig()
- )
- return state
-}
-
-//
-// public store
-//
-
-// get init state
-var initPublicState = extend(
- idStoreToPublic(idStore.getState()),
- configToPublic(configManager.getConfig())
-)
-
-var publicConfigStore = new HostStore(initPublicState)
-
-// subscribe to changes
-configManager.subscribe(function (state) {
- storeSetFromObj(publicConfigStore, configToPublic(state))
-})
-idStore.on('update', function (state) {
- storeSetFromObj(publicConfigStore, idStoreToPublic(state))
-})
-
-// idStore substate
-function idStoreToPublic (state) {
- return {
- selectedAddress: state.selectedAddress,
- }
-}
-// config substate
-function configToPublic (state) {
- return {
- provider: state.provider,
- }
-}
-// dump obj into store
-function storeSetFromObj (store, obj) {
- Object.keys(obj).forEach(function (key) {
- store.set(key, obj[key])
- })
+ controller.setupProviderConnection(mx.createStream('provider'), originDomain)
}
//
// remote features
//
-function setupPublicConfig (stream) {
- var storeStream = publicConfigStore.createStream()
- stream.pipe(storeStream).pipe(stream)
-}
-
-function setupProviderConnection (stream, originDomain) {
- // decorate all payloads with origin domain
- stream.on('data', function onRpcRequest (request) {
- var payloads = Array.isArray(request) ? request : [request]
- payloads.forEach(function (payload) {
- // Append origin to rpc payload
- payload.origin = originDomain
- // Append origin to signature request
- if (payload.method === 'eth_sendTransaction') {
- payload.params[0].origin = originDomain
- } else if (payload.method === 'eth_sign') {
- payload.params.push({ origin: originDomain })
- }
- })
- // handle rpc request
- provider.sendAsync(request, function onPayloadHandled (err, response) {
- if (err) {
- return logger(err)
- }
- logger(null, request, response)
- try {
- stream.write(response)
- } catch (err) {
- logger(err)
- }
- })
- })
-
- function logger (err, request, response) {
- if (err) return console.error(err.stack)
- if (!request.isMetamaskInternal) {
- console.log(`RPC (${originDomain}):`, request, '->', response)
- if (response.error) console.error('Error in RPC response:\n' + response.error.message)
- }
- }
-}
-
function setupControllerConnection (stream) {
- var dnode = Dnode({
- getState: function (cb) { cb(null, getState()) },
- setRpcTarget: setRpcTarget,
- setProviderType: setProviderType,
- useEtherscanProvider: useEtherscanProvider,
- agreeToDisclaimer: agreeToDisclaimer,
- // forward directly to idStore
- createNewVault: idStore.createNewVault.bind(idStore),
- recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
- submitPassword: idStore.submitPassword.bind(idStore),
- setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
- approveTransaction: idStore.approveTransaction.bind(idStore),
- cancelTransaction: idStore.cancelTransaction.bind(idStore),
- signMessage: idStore.signMessage.bind(idStore),
- cancelMessage: idStore.cancelMessage.bind(idStore),
- setLocked: idStore.setLocked.bind(idStore),
- clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
- exportAccount: idStore.exportAccount.bind(idStore),
- revealAccount: idStore.revealAccount.bind(idStore),
- saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
- tryPassword: idStore.tryPassword.bind(idStore),
- recoverSeed: idStore.recoverSeed.bind(idStore),
- })
+ controller.stream = stream
+ var api = controller.getApi()
+ var dnode = Dnode(api)
stream.pipe(dnode).pipe(stream)
- dnode.on('remote', function (remote) {
+ dnode.on('remote', (remote) => {
// push updates to popup
- ethStore.on('update', sendUpdate)
- idStore.on('update', sendUpdate)
+ controller.ethStore.on('update', controller.sendUpdate.bind(controller))
+ controller.remote = remote
+ idStore.on('update', controller.sendUpdate.bind(controller))
+
// teardown on disconnect
- eos(stream, function unsubscribe () {
- ethStore.removeListener('update', sendUpdate)
+ eos(stream, () => {
+ controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller))
})
- function sendUpdate () {
- var state = getState()
- remote.sendUpdate(state)
- }
})
}
@@ -232,7 +111,7 @@ idStore.on('update', updateBadge)
function updateBadge (state) {
var label = ''
- var unconfTxs = configManager.unconfirmedTxs()
+ var unconfTxs = controller.configManager.unconfirmedTxs()
var unconfTxLen = Object.keys(unconfTxs).length
var unconfMsgs = messageManager.unconfirmedMsgs()
var unconfMsgLen = Object.keys(unconfMsgs).length
@@ -244,86 +123,54 @@ function updateBadge (state) {
chrome.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
}
-//
-// Add unconfirmed Tx + Msg
-//
-
-function newUnsignedTransaction (txParams, onTxDoneCb) {
- var state = idStore.getState()
- if (!state.isUnlocked) {
- createUnlockRequestNotification({
- title: 'Account Unlock Request',
- })
- idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, noop)
- } else {
- addUnconfirmedTx(txParams, onTxDoneCb)
- }
-}
-
-function newUnsignedMessage (msgParams, cb) {
- var state = idStore.getState()
- if (!state.isUnlocked) {
- createUnlockRequestNotification({
- title: 'Account Unlock Request',
- })
- } else {
- addUnconfirmedMsg(msgParams, cb)
- }
-}
-
-function addUnconfirmedTx (txParams, onTxDoneCb) {
- idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, function (err, txData) {
- if (err) return onTxDoneCb(err)
- createTxNotification({
- title: 'New Unsigned Transaction',
- txParams: txParams,
- confirm: idStore.approveTransaction.bind(idStore, txData.id, noop),
- cancel: idStore.cancelTransaction.bind(idStore, txData.id),
- })
- })
-}
-
-function addUnconfirmedMsg (msgParams, cb) {
- var msgId = idStore.addUnconfirmedMessage(msgParams, cb)
- createMsgNotification({
- title: 'New Unsigned Message',
- msgParams: msgParams,
- confirm: idStore.approveMessage.bind(idStore, msgId, noop),
- cancel: idStore.cancelMessage.bind(idStore, msgId),
- })
-}
-
-//
-// config
-//
-
-function agreeToDisclaimer (cb) {
+function loadData () {
+ var oldData = getOldStyleData()
+ var newData
try {
- configManager.setConfirmed(true)
- cb()
- } catch (e) {
- cb(e)
+ newData = JSON.parse(window.localStorage[STORAGE_KEY])
+ } catch (e) {}
+
+ var data = extend({
+ meta: {
+ version: 0,
+ },
+ data: {
+ config: {
+ provider: {
+ type: 'testnet',
+ },
+ },
+ },
+ }, oldData || null, newData || null)
+ return data
+}
+
+function getOldStyleData () {
+ var config, wallet, seedWords
+
+ var result = {
+ meta: { version: 0 },
+ data: {},
}
-}
-// called from popup
-function setRpcTarget (rpcTarget) {
- configManager.setRpcTarget(rpcTarget)
- chrome.runtime.reload()
- idStore.getNetwork()
-}
+ try {
+ config = JSON.parse(window.localStorage['config'])
+ result.data.config = config
+ } catch (e) {}
+ try {
+ wallet = JSON.parse(window.localStorage['lightwallet'])
+ result.data.wallet = wallet
+ } catch (e) {}
+ try {
+ seedWords = window.localStorage['seedWords']
+ result.data.seedWords = seedWords
+ } catch (e) {}
-function setProviderType (type) {
- configManager.setProviderType(type)
- chrome.runtime.reload()
- idStore.getNetwork()
+ return result
}
-function useEtherscanProvider () {
- configManager.useEtherscanProvider()
- chrome.runtime.reload()
+function setData (data) {
+ window.localStorage[STORAGE_KEY] = JSON.stringify(data)
}
-// util
-
function noop () {}
diff --git a/app/scripts/lib/config-manager-singleton.js b/app/scripts/lib/config-manager-singleton.js
deleted file mode 100644
index 5915c401b..000000000
--- a/app/scripts/lib/config-manager-singleton.js
+++ /dev/null
@@ -1,3 +0,0 @@
-var ConfigManager = require('./config-manager')
-
-module.exports = new ConfigManager()
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index a3ff0bdfb..0af82c89c 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -1,9 +1,7 @@
const Migrator = require('pojo-migrator')
-const extend = require('xtend')
const MetamaskConfig = require('../config.js')
const migrations = require('./migrations')
-const STORAGE_KEY = 'metamask-config'
const TESTNET_RPC = MetamaskConfig.network.testnet
const MAINNET_RPC = MetamaskConfig.network.mainnet
@@ -15,7 +13,7 @@ const MAINNET_RPC = MetamaskConfig.network.mainnet
* particular portions of the state.
*/
module.exports = ConfigManager
-function ConfigManager () {
+function ConfigManager (opts) {
// ConfigManager is observable and will emit updates
this._subs = []
@@ -37,12 +35,10 @@ function ConfigManager () {
// How to load initial config.
// Includes step on migrating pre-pojo-migrator data.
- loadData: loadData,
+ loadData: opts.loadData,
// How to persist migrated config.
- setData: function (data) {
- window.localStorage[STORAGE_KEY] = JSON.stringify(data)
- },
+ setData: opts.setData,
})
}
@@ -280,49 +276,3 @@ ConfigManager.prototype.getConfirmed = function () {
return ('isConfirmed' in data) && data.isConfirmed
}
-function loadData () {
- var oldData = getOldStyleData()
- var newData
- try {
- newData = JSON.parse(window.localStorage[STORAGE_KEY])
- } catch (e) {}
-
- var data = extend({
- meta: {
- version: 0,
- },
- data: {
- config: {
- provider: {
- type: 'testnet',
- },
- },
- },
- }, oldData || null, newData || null)
- return data
-}
-
-function getOldStyleData () {
- var config, wallet, seedWords
-
- var result = {
- meta: { version: 0 },
- data: {},
- }
-
- try {
- config = JSON.parse(window.localStorage['config'])
- result.data.config = config
- } catch (e) {}
- try {
- wallet = JSON.parse(window.localStorage['lightwallet'])
- result.data.wallet = wallet
- } catch (e) {}
- try {
- seedWords = window.localStorage['seedWords']
- result.data.seedWords = seedWords
- } catch (e) {}
-
- return result
-}
-
diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js
index cc50bd649..9b8ceb415 100644
--- a/app/scripts/lib/id-management.js
+++ b/app/scripts/lib/id-management.js
@@ -1,6 +1,5 @@
const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx')
-const configManager = require('./config-manager-singleton')
module.exports = IdManagement
@@ -9,6 +8,7 @@ function IdManagement (opts) {
this.keyStore = opts.keyStore
this.derivedKey = opts.derivedKey
+ this.configManager = opts.configManager
this.hdPathString = "m/44'/60'/0'/0"
this.getAddresses = function () {
@@ -32,9 +32,9 @@ function IdManagement (opts) {
// Add the tx hash to the persisted meta-tx object
var txHash = ethUtil.bufferToHex(tx.hash())
- var metaTx = configManager.getTx(txParams.metamaskId)
+ var metaTx = this.configManager.getTx(txParams.metamaskId)
metaTx.hash = txHash
- configManager.updateTx(metaTx)
+ this.configManager.updateTx(metaTx)
// return raw serialized tx
var rawTx = ethUtil.bufferToHex(tx.serialize())
diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js
index d9657dacf..f705c07a7 100644
--- a/app/scripts/lib/idStore.js
+++ b/app/scripts/lib/idStore.js
@@ -7,7 +7,6 @@ const extend = require('xtend')
const createId = require('web3-provider-engine/util/random-id')
const ethBinToOps = require('eth-bin-to-ops')
const autoFaucet = require('./auto-faucet')
-const configManager = require('./config-manager-singleton')
const messageManager = require('./message-manager')
const DEFAULT_RPC = 'https://testrpc.metamask.io/'
const IdManagement = require('./id-management')
@@ -20,6 +19,7 @@ function IdentityStore (opts = {}) {
// we just use the ethStore to auto-add accounts
this._ethStore = opts.ethStore
+ this.configManager = opts.configManager
// lightwallet key store
this._keyStore = null
// lightwallet wrapper
@@ -43,7 +43,10 @@ function IdentityStore (opts = {}) {
IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
delete this._keyStore
- configManager.clearWallet()
+ if (this.configManager) {
+ this.configManager.clearWallet()
+ }
+
this._createIdmgmt(password, null, entropy, (err) => {
if (err) return cb(err)
@@ -51,14 +54,14 @@ IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
this._didUpdate()
this._autoFaucet()
- configManager.setShowSeedWords(true)
+ this.configManager.setShowSeedWords(true)
var seedWords = this._idmgmt.getSeed()
cb(null, seedWords)
})
}
IdentityStore.prototype.recoverSeed = function (cb) {
- configManager.setShowSeedWords(true)
+ this.configManager.setShowSeedWords(true)
if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.'))
var seedWords = this._idmgmt.getSeed()
cb(null, seedWords)
@@ -79,11 +82,13 @@ IdentityStore.prototype.setStore = function (store) {
}
IdentityStore.prototype.clearSeedWordCache = function (cb) {
+ const configManager = this.configManager
configManager.setShowSeedWords(false)
cb(null, configManager.getSelectedAccount())
}
IdentityStore.prototype.getState = function () {
+ const configManager = this.configManager
var seedWords = this.getSeedIfUnlocked()
return clone(extend(this._currentState, {
isInitialized: !!configManager.getWallet() && !seedWords,
@@ -99,6 +104,7 @@ IdentityStore.prototype.getState = function () {
}
IdentityStore.prototype.getSeedIfUnlocked = function () {
+ const configManager = this.configManager
var showSeed = configManager.getShouldShowSeedWords()
var idmgmt = this._idmgmt
var shouldShow = showSeed && !!idmgmt
@@ -107,10 +113,12 @@ IdentityStore.prototype.getSeedIfUnlocked = function () {
}
IdentityStore.prototype.getSelectedAddress = function () {
+ const configManager = this.configManager
return configManager.getSelectedAccount()
}
IdentityStore.prototype.setSelectedAddress = function (address, cb) {
+ const configManager = this.configManager
if (!address) {
var addresses = this._getAddresses()
address = addresses[0]
@@ -123,6 +131,7 @@ IdentityStore.prototype.setSelectedAddress = function (address, cb) {
IdentityStore.prototype.revealAccount = function (cb) {
const derivedKey = this._idmgmt.derivedKey
const keyStore = this._keyStore
+ const configManager = this.configManager
keyStore.setDefaultHdDerivationPath(this.hdPathString)
keyStore.generateNewAddress(derivedKey, 1)
@@ -158,6 +167,7 @@ IdentityStore.prototype.setLocked = function (cb) {
}
IdentityStore.prototype.submitPassword = function (password, cb) {
+ const configManager = this.configManager
this.tryPassword(password, (err) => {
if (err) return cb(err)
// load identities before returning...
@@ -177,6 +187,7 @@ IdentityStore.prototype.exportAccount = function (address, cb) {
// comes from dapp via zero-client hooked-wallet provider
IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDoneCb, cb) {
+ const configManager = this.configManager
var self = this
// create txData obj with parameters and meta data
var time = (new Date()).getTime()
@@ -227,6 +238,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
// comes from metamask ui
IdentityStore.prototype.approveTransaction = function (txId, cb) {
+ const configManager = this.configManager
var approvalCb = this._unconfTxCbs[txId] || noop
// accept tx
@@ -240,6 +252,7 @@ IdentityStore.prototype.approveTransaction = function (txId, cb) {
// comes from metamask ui
IdentityStore.prototype.cancelTransaction = function (txId) {
+ const configManager = this.configManager
var approvalCb = this._unconfTxCbs[txId] || noop
// reject tx
@@ -347,6 +360,7 @@ IdentityStore.prototype._isUnlocked = function () {
// load identities from keyStoreet
IdentityStore.prototype._loadIdentities = function () {
+ const configManager = this.configManager
if (!this._isUnlocked()) throw new Error('not unlocked')
var addresses = this._getAddresses()
@@ -367,6 +381,7 @@ IdentityStore.prototype._loadIdentities = function () {
}
IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
+ const configManager = this.configManager
configManager.setNicknameForWallet(account, label)
this._loadIdentities()
cb(null, label)
@@ -379,6 +394,7 @@ IdentityStore.prototype.saveAccountLabel = function (account, label, cb) {
// If there is no balance and it mayBeFauceting,
// then it is in fact fauceting.
IdentityStore.prototype._mayBeFauceting = function (i) {
+ const configManager = this.configManager
var config = configManager.getProvider()
if (i === 0 &&
config.type === 'rpc' &&
@@ -397,6 +413,7 @@ IdentityStore.prototype.tryPassword = function (password, cb) {
}
IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
+ const configManager = this.configManager
var keyStore = null
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
if (err) return cb(err)
@@ -425,6 +442,7 @@ IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
keyStore: keyStore,
derivedKey: derivedKey,
hdPathSTring: this.hdPathString,
+ configManager: this.configManager,
})
cb()
@@ -432,6 +450,7 @@ IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
}
IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey) {
+ const configManager = this.configManager
var keyStore = new LightwalletKeyStore(seed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
keyStore.setDefaultHdDerivationPath(this.hdPathString)
@@ -443,6 +462,7 @@ IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey)
}
IdentityStore.prototype._createFirstWallet = function (entropy, derivedKey) {
+ const configManager = this.configManager
var secretSeed = LightwalletKeyStore.generateRandomSeed(entropy)
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey, this.hdPathString)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
new file mode 100644
index 000000000..398086274
--- /dev/null
+++ b/app/scripts/metamask-controller.js
@@ -0,0 +1,257 @@
+const extend = require('xtend')
+const EthStore = require('eth-store')
+const MetaMaskProvider = require('web3-provider-engine/zero.js')
+const IdentityStore = require('./lib/idStore')
+const messageManager = require('./lib/message-manager')
+const HostStore = require('./lib/remote-store.js').HostStore
+const Web3 = require('web3')
+const ConfigManager = require('./lib/config-manager')
+
+module.exports = class MetamaskController {
+
+ constructor (opts) {
+ this.opts = opts
+ this.configManager = new ConfigManager(opts)
+ this.idStore = new IdentityStore({
+ configManager: this.configManager,
+ })
+ this.provider = this.initializeProvider(opts)
+ this.ethStore = new EthStore(this.provider)
+ this.idStore.setStore(this.ethStore)
+ this.messageManager = messageManager
+ this.publicConfigStore = this.initPublicConfigStore()
+ }
+
+ getState () {
+ return extend(
+ this.ethStore.getState(),
+ this.idStore.getState(),
+ this.configManager.getConfig()
+ )
+ }
+
+ getApi () {
+ const idStore = this.idStore
+
+ return {
+ getState: (cb) => { cb(null, this.getState()) },
+ setRpcTarget: this.setRpcTarget.bind(this),
+ setProviderType: this.setProviderType.bind(this),
+ useEtherscanProvider: this.useEtherscanProvider.bind(this),
+ agreeToDisclaimer: this.agreeToDisclaimer.bind(this),
+ // forward directly to idStore
+ createNewVault: idStore.createNewVault.bind(idStore),
+ recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
+ submitPassword: idStore.submitPassword.bind(idStore),
+ setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
+ approveTransaction: idStore.approveTransaction.bind(idStore),
+ cancelTransaction: idStore.cancelTransaction.bind(idStore),
+ signMessage: idStore.signMessage.bind(idStore),
+ cancelMessage: idStore.cancelMessage.bind(idStore),
+ setLocked: idStore.setLocked.bind(idStore),
+ clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
+ exportAccount: idStore.exportAccount.bind(idStore),
+ revealAccount: idStore.revealAccount.bind(idStore),
+ saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
+ tryPassword: idStore.tryPassword.bind(idStore),
+ recoverSeed: idStore.recoverSeed.bind(idStore),
+ }
+ }
+
+ setupProviderConnection (stream, originDomain) {
+ stream.on('data', this.onRpcRequest.bind(this, stream, originDomain))
+ }
+
+ onRpcRequest (stream, originDomain, request) {
+ var payloads = Array.isArray(request) ? request : [request]
+ payloads.forEach(function (payload) {
+ // Append origin to rpc payload
+ payload.origin = originDomain
+ // Append origin to signature request
+ if (payload.method === 'eth_sendTransaction') {
+ payload.params[0].origin = originDomain
+ } else if (payload.method === 'eth_sign') {
+ payload.params.push({ origin: originDomain })
+ }
+ })
+
+ // handle rpc request
+ this.provider.sendAsync(request, function onPayloadHandled (err, response) {
+ if (err) {
+ return logger(err)
+ }
+ logger(null, request, response)
+ try {
+ stream.write(response)
+ } catch (err) {
+ logger(err)
+ }
+ })
+
+ function logger (err, request, response) {
+ if (err) return console.error(err.stack)
+ if (!request.isMetamaskInternal) {
+ console.log(`RPC (${originDomain}):`, request, '->', response)
+ if (response.error) console.error('Error in RPC response:\n' + response.error.message)
+ }
+ }
+ }
+
+ sendUpdate () {
+ if (this.remote) {
+ this.remote.sendUpdate(this.getState())
+ }
+ }
+
+ initializeProvider (opts) {
+ const idStore = this.idStore
+
+ var providerOpts = {
+ rpcUrl: this.configManager.getCurrentRpcAddress(),
+ // account mgmt
+ getAccounts: (cb) => {
+ var selectedAddress = idStore.getSelectedAddress()
+ var result = selectedAddress ? [selectedAddress] : []
+ cb(null, result)
+ },
+ // tx signing
+ approveTransaction: this.newUnsignedTransaction.bind(this),
+ signTransaction: idStore.signTransaction.bind(idStore),
+ // msg signing
+ approveMessage: this.newUnsignedMessage.bind(this),
+ signMessage: idStore.signMessage.bind(idStore),
+ }
+
+ var provider = MetaMaskProvider(providerOpts)
+ var web3 = new Web3(provider)
+ idStore.web3 = web3
+ idStore.getNetwork()
+
+ provider.on('block', this.processBlock.bind(this))
+ provider.on('error', idStore.getNetwork.bind(idStore))
+
+ return provider
+ }
+
+ initPublicConfigStore () {
+ // get init state
+ var initPublicState = extend(
+ idStoreToPublic(this.idStore.getState()),
+ configToPublic(this.configManager.getConfig())
+ )
+
+ var publicConfigStore = new HostStore(initPublicState)
+
+ // subscribe to changes
+ this.configManager.subscribe(function (state) {
+ storeSetFromObj(publicConfigStore, configToPublic(state))
+ })
+ this.idStore.on('update', function (state) {
+ storeSetFromObj(publicConfigStore, idStoreToPublic(state))
+ })
+
+ // idStore substate
+ function idStoreToPublic (state) {
+ return {
+ selectedAddress: state.selectedAddress,
+ }
+ }
+ // config substate
+ function configToPublic (state) {
+ return {
+ provider: state.provider,
+ }
+ }
+ // dump obj into store
+ function storeSetFromObj (store, obj) {
+ Object.keys(obj).forEach(function (key) {
+ store.set(key, obj[key])
+ })
+ }
+
+ return publicConfigStore
+ }
+
+ newUnsignedTransaction (txParams, onTxDoneCb) {
+ const idStore = this.idStore
+ var state = idStore.getState()
+
+ // It's locked
+ if (!state.isUnlocked) {
+ this.opts.unlockAccountMessage()
+ idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, noop)
+
+ // It's unlocked
+ } else {
+ idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
+ if (err) return onTxDoneCb(err)
+ this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
+ })
+ }
+ }
+
+ newUnsignedMessage (msgParams, cb) {
+ var state = this.idStore.getState()
+ if (!state.isUnlocked) {
+ this.opts.unlockAccountMessage()
+ } else {
+ this.addUnconfirmedMsg(msgParams, cb)
+ }
+ }
+
+ addUnconfirmedMessage (msgParams, cb) {
+ const idStore = this.idStore
+ const msgId = idStore.addUnconfirmedMessage(msgParams, cb)
+ this.opts.showUnconfirmedMessage(msgParams, msgId)
+ }
+
+ setupPublicConfig (stream) {
+ var storeStream = this.publicConfigStore.createStream()
+ stream.pipe(storeStream).pipe(stream)
+ }
+
+ // Log blocks
+ processBlock (block) {
+ console.log(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
+ this.verifyNetwork()
+ }
+
+ verifyNetwork () {
+ // Check network when restoring connectivity:
+ if (this.idStore._currentState.network === 'loading') {
+ this.idStore.getNetwork()
+ }
+ }
+
+ // config
+ //
+
+ agreeToDisclaimer (cb) {
+ try {
+ this.configManager.setConfirmed(true)
+ cb()
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ // called from popup
+ setRpcTarget (rpcTarget) {
+ this.configManager.setRpcTarget(rpcTarget)
+ chrome.runtime.reload()
+ this.idStore.getNetwork()
+ }
+
+ setProviderType (type) {
+ this.configManager.setProviderType(type)
+ chrome.runtime.reload()
+ this.idStore.getNetwork()
+ }
+
+ useEtherscanProvider () {
+ this.configManager.useEtherscanProvider()
+ chrome.runtime.reload()
+ }
+}
+
+function noop () {}
diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js
new file mode 100644
index 000000000..fe841f455
--- /dev/null
+++ b/test/lib/mock-config-manager.js
@@ -0,0 +1,57 @@
+var ConfigManager = require('../../app/scripts/lib/config-manager')
+const STORAGE_KEY = 'metamask-persistance-key'
+const extend = require('xtend')
+
+module.exports = function() {
+ return new ConfigManager({ loadData, setData })
+}
+
+function loadData () {
+ var oldData = getOldStyleData()
+ var newData
+ try {
+ newData = JSON.parse(window.localStorage[STORAGE_KEY])
+ } catch (e) {}
+
+ var data = extend({
+ meta: {
+ version: 0,
+ },
+ data: {
+ config: {
+ provider: {
+ type: 'testnet',
+ },
+ },
+ },
+ }, oldData || null, newData || null)
+ return data
+}
+
+function getOldStyleData () {
+ var config, wallet, seedWords
+
+ var result = {
+ meta: { version: 0 },
+ data: {},
+ }
+
+ try {
+ config = JSON.parse(window.localStorage['config'])
+ result.data.config = config
+ } catch (e) {}
+ try {
+ wallet = JSON.parse(window.localStorage['lightwallet'])
+ result.data.wallet = wallet
+ } catch (e) {}
+ try {
+ seedWords = window.localStorage['seedWords']
+ result.data.seedWords = seedWords
+ } catch (e) {}
+
+ return result
+}
+
+function setData (data) {
+ window.localStorage[STORAGE_KEY] = JSON.stringify(data)
+}
diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js
index 130bde2ff..7891c5c9e 100644
--- a/test/unit/config-manager-test.js
+++ b/test/unit/config-manager-test.js
@@ -1,12 +1,14 @@
var assert = require('assert')
-var ConfigManager = require('../../app/scripts/lib/config-manager')
+const extend = require('xtend')
+const STORAGE_KEY = 'metamask-persistance-key'
+var configManagerGen = require('../lib/mock-config-manager')
var configManager
describe('config-manager', function() {
beforeEach(function() {
window.localStorage = {} // Hacking localStorage support into JSDom
- configManager = new ConfigManager()
+ configManager = configManagerGen()
})
describe('confirmation', function() {
@@ -209,3 +211,4 @@ describe('config-manager', function() {
})
})
})
+
diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js
index e9611d7e8..ee4613236 100644
--- a/test/unit/idStore-test.js
+++ b/test/unit/idStore-test.js
@@ -1,5 +1,6 @@
var assert = require('assert')
var IdentityStore = require('../../app/scripts/lib/idStore')
+var configManagerGen = require('../lib/mock-config-manager')
describe('IdentityStore', function() {
@@ -15,6 +16,7 @@ describe('IdentityStore', function() {
window.localStorage = {} // Hacking localStorage support into JSDom
idStore = new IdentityStore({
+ configManager: configManagerGen(),
ethStore: {
addAccount(acct) { accounts.push(acct) },
},
@@ -34,6 +36,7 @@ describe('IdentityStore', function() {
window.localStorage = {} // Hacking localStorage support into JSDom
idStore = new IdentityStore({
+ configManager: configManagerGen(),
ethStore: {
addAccount(acct) { newAccounts.push(acct) },
},
@@ -65,6 +68,7 @@ describe('IdentityStore', function() {
window.localStorage = {} // Hacking localStorage support into JSDom
idStore = new IdentityStore({
+ configManager: configManagerGen(),
ethStore: {
addAccount(acct) { accounts.push(acct) },
},