diff --git a/docs/jsdocs/inpage.js.html b/docs/jsdocs/inpage.js.html
new file mode 100644
index 000000000..acfd6223c
--- /dev/null
+++ b/docs/jsdocs/inpage.js.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/jsdocs/lib_ComposableObservableStore.js.html b/docs/jsdocs/lib_ComposableObservableStore.js.html
new file mode 100644
index 000000000..dc39a9f55
--- /dev/null
+++ b/docs/jsdocs/lib_ComposableObservableStore.js.html
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/jsdocs/metamask-controller.js.html b/docs/jsdocs/metamask-controller.js.html
index 2162138d1..5f8748577 100644
--- a/docs/jsdocs/metamask-controller.js.html
+++ b/docs/jsdocs/metamask-controller.js.html
@@ -32,7 +32,7 @@
@@ -54,10 +54,10 @@
*/
const EventEmitter = require('events')
-const extend = require('xtend')
const pump = require('pump')
const Dnode = require('dnode')
const ObservableStore = require('obs-store')
+const ComposableObservableStore = require('./lib/ComposableObservableStore')
const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker')
const RpcEngine = require('json-rpc-engine')
@@ -83,6 +83,7 @@ const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances')
+const TokenRatesController = require('./controllers/token-rates')
const ConfigManager = require('./lib/config-manager')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
@@ -93,11 +94,7 @@ const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
-
-/**
- * @typedef {object} MetaMaskOptions
- * @property {Platform} platform - An object including platform-specific functions.
- */
+const log = require('loglevel')
module.exports = class MetamaskController extends EventEmitter {
@@ -108,7 +105,6 @@ module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
super()
- // Avoids warnings when we use lots of emitters.
this.defaultMaxListeners = 20
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
@@ -120,7 +116,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.platform = opts.platform
// observable state store
- this.store = new ObservableStore(initState)
+ this.store = new ComposableObservableStore(initState)
// lock to ensure only one vault created at once
this.createVaultMutex = new Mutex()
@@ -159,6 +155,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker
+ // token exchange rate tracker
+ this.tokenRatesController = new TokenRatesController({
+ preferences: this.preferencesController.store,
+ })
+
this.recentBlocksController = new RecentBlocksController({
blockTracker: this.blockTracker,
provider: this.provider,
@@ -239,53 +240,37 @@ module.exports = class MetamaskController extends EventEmitter {
this.typedMessageManager = new TypedMessageManager()
this.publicConfigStore = this.initPublicConfigStore()
- // manual disk state subscriptions
- this.txController.store.subscribe((state) => {
- this.store.updateState({ TransactionController: state })
- })
- this.keyringController.store.subscribe((state) => {
- this.store.updateState({ KeyringController: state })
- })
- this.preferencesController.store.subscribe((state) => {
- this.store.updateState({ PreferencesController: state })
- })
- this.addressBookController.store.subscribe((state) => {
- this.store.updateState({ AddressBookController: state })
- })
- this.currencyController.store.subscribe((state) => {
- this.store.updateState({ CurrencyController: state })
- })
- this.noticeController.store.subscribe((state) => {
- this.store.updateState({ NoticeController: state })
- })
- this.shapeshiftController.store.subscribe((state) => {
- this.store.updateState({ ShapeShiftController: state })
- })
- this.networkController.store.subscribe((state) => {
- this.store.updateState({ NetworkController: state })
+ this.store.updateStructure({
+ TransactionController: this.txController.store,
+ KeyringController: this.keyringController.store,
+ PreferencesController: this.preferencesController.store,
+ AddressBookController: this.addressBookController.store,
+ CurrencyController: this.currencyController.store,
+ NoticeController: this.noticeController.store,
+ ShapeShiftController: this.shapeshiftController.store,
+ NetworkController: this.networkController.store,
+ InfuraController: this.infuraController.store,
})
- this.infuraController.store.subscribe((state) => {
- this.store.updateState({ InfuraController: state })
+ this.memStore = new ComposableObservableStore(null, {
+ NetworkController: this.networkController.store,
+ AccountTracker: this.accountTracker.store,
+ TxController: this.txController.memStore,
+ BalancesController: this.balancesController.store,
+ TokenRatesController: this.tokenRatesController.store,
+ MessageManager: this.messageManager.memStore,
+ PersonalMessageManager: this.personalMessageManager.memStore,
+ TypesMessageManager: this.typedMessageManager.memStore,
+ KeyringController: this.keyringController.memStore,
+ PreferencesController: this.preferencesController.store,
+ RecentBlocksController: this.recentBlocksController.store,
+ AddressBookController: this.addressBookController.store,
+ CurrencyController: this.currencyController.store,
+ NoticeController: this.noticeController.memStore,
+ ShapeshiftController: this.shapeshiftController.store,
+ InfuraController: this.infuraController.store,
})
-
- // manual mem state subscriptions
- const sendUpdate = this.sendUpdate.bind(this)
- this.networkController.store.subscribe(sendUpdate)
- this.accountTracker.store.subscribe(sendUpdate)
- this.txController.memStore.subscribe(sendUpdate)
- this.balancesController.store.subscribe(sendUpdate)
- this.messageManager.memStore.subscribe(sendUpdate)
- this.personalMessageManager.memStore.subscribe(sendUpdate)
- this.typedMessageManager.memStore.subscribe(sendUpdate)
- this.keyringController.memStore.subscribe(sendUpdate)
- this.preferencesController.store.subscribe(sendUpdate)
- this.recentBlocksController.store.subscribe(sendUpdate)
- this.addressBookController.store.subscribe(sendUpdate)
- this.currencyController.store.subscribe(sendUpdate)
- this.noticeController.memStore.subscribe(sendUpdate)
- this.shapeshiftController.store.subscribe(sendUpdate)
- this.infuraController.store.subscribe(sendUpdate)
+ this.memStore.subscribe(this.sendUpdate.bind(this))
}
/**
@@ -334,6 +319,7 @@ module.exports = class MetamaskController extends EventEmitter {
// memStore -> transform -> publicConfigStore
this.on('update', (memState) => {
+ this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
const publicState = selectPublicState(memState)
publicConfigStore.putState(publicState)
})
@@ -363,33 +349,16 @@ module.exports = class MetamaskController extends EventEmitter {
const vault = this.keyringController.store.getState().vault
const isInitialized = (!!wallet || !!vault)
- return extend(
- {
- isInitialized,
- },
- this.networkController.store.getState(),
- this.accountTracker.store.getState(),
- this.txController.memStore.getState(),
- this.messageManager.memStore.getState(),
- this.personalMessageManager.memStore.getState(),
- this.typedMessageManager.memStore.getState(),
- this.keyringController.memStore.getState(),
- this.balancesController.store.getState(),
- this.preferencesController.store.getState(),
- this.addressBookController.store.getState(),
- this.currencyController.store.getState(),
- this.noticeController.memStore.getState(),
- this.infuraController.store.getState(),
- this.recentBlocksController.store.getState(),
- // config manager
- this.configManager.getConfig(),
- this.shapeshiftController.store.getState(),
- {
+ return {
+ ...{ isInitialized },
+ ...this.memStore.getFlatState(),
+ ...this.configManager.getConfig(),
+ ...{
lostAccounts: this.configManager.getLostAccounts(),
seedWords: this.configManager.getSeedWords(),
forgottenPassword: this.configManager.getPasswordForgotten(),
- }
- )
+ },
+ }
}
/**
@@ -1103,15 +1072,6 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
- /**
- * Records the MetaMask version and time of first installation,
- * mutating the initState param.
- *
- * @private
- *
- * @param {object} initState The initial state passed to the controller,
- * which may be new.
- */
recordFirstTimeInfo (initState) {
if (!('firstTimeInfo' in initState)) {
initState.firstTimeInfo = {
@@ -1121,6 +1081,14 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ set isClientOpen (open) {
+ this._isClientOpen = open
+ this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
+ }
+
+ set isClientOpenAndUnlocked (active) {
+ this.tokenRatesController.isActive = active
+ }
}
@@ -1134,7 +1102,7 @@ module.exports = class MetamaskController extends EventEmitter {
diff --git a/docs/jsdocs/module.exports_module.exports.html b/docs/jsdocs/module.exports_module.exports.html
index 5558cea07..f5c96b6eb 100644
--- a/docs/jsdocs/module.exports_module.exports.html
+++ b/docs/jsdocs/module.exports_module.exports.html
@@ -32,7 +32,7 @@
@@ -75,7 +75,7 @@
Source:
@@ -219,7 +219,7 @@
diff --git a/docs/jsdocs/popup-core.js.html b/docs/jsdocs/popup-core.js.html
new file mode 100644
index 000000000..919637cfe
--- /dev/null
+++ b/docs/jsdocs/popup-core.js.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
popup-core.js - Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
popup-core.js
+
+
+
+
+
+
+
+
+
+ const EventEmitter = require('events').EventEmitter
+const async = require('async')
+const Dnode = require('dnode')
+const Eth = require('ethjs')
+const EthQuery = require('eth-query')
+const launchMetamaskUi = require('../../ui')
+const StreamProvider = require('web3-stream-provider')
+const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
+
+module.exports = initializePopup
+
+/**
+ * Asynchronously initializes the MetaMask popup UI
+ *
+ * @param {{ container: Element, connectionStream: any }} config Popup configuration object
+ * @param {Function} cb Called when initialization is comlete
+ */
+function initializePopup ({ container, connectionStream }, cb) {
+ // setup app
+ async.waterfall([
+ (cb) => connectToAccountManager(connectionStream, cb),
+ (accountManager, cb) => launchMetamaskUi({ container, accountManager }, cb),
+ ], cb)
+}
+
+/**
+ * Establishes streamed connections to background scripts and a Web3 provider
+ *
+ * @param {any} connectionStream PortStream instance establishing a background connection
+ * @param {Function} cb Called when controller connection is established
+ */
+function connectToAccountManager (connectionStream, cb) {
+ // setup communication with background
+ // setup multiplexing
+ var mx = setupMultiplex(connectionStream)
+ // connect features
+ setupControllerConnection(mx.createStream('controller'), cb)
+ setupWeb3Connection(mx.createStream('provider'))
+}
+
+/**
+ * Establishes a streamed connection to a Web3 provider
+ *
+ * @param {any} connectionStream PortStream instance establishing a background connection
+ */
+function setupWeb3Connection (connectionStream) {
+ var providerStream = new StreamProvider()
+ providerStream.pipe(connectionStream).pipe(providerStream)
+ connectionStream.on('error', console.error.bind(console))
+ providerStream.on('error', console.error.bind(console))
+ global.ethereumProvider = providerStream
+ global.ethQuery = new EthQuery(providerStream)
+ global.eth = new Eth(providerStream)
+}
+
+/**
+ * Establishes a streamed connection to the background account manager
+ *
+ * @param {any} connectionStream PortStream instance establishing a background connection
+ * @param {Function} cb Called when the remote account manager connection is established
+ */
+function setupControllerConnection (connectionStream, cb) {
+ // this is a really sneaky way of adding EventEmitter api
+ // to a bi-directional dnode instance
+ var eventEmitter = new EventEmitter()
+ var accountManagerDnode = Dnode({
+ sendUpdate: function (state) {
+ eventEmitter.emit('update', state)
+ },
+ })
+ connectionStream.pipe(accountManagerDnode).pipe(connectionStream)
+ accountManagerDnode.once('remote', function (accountManager) {
+ // setup push events
+ accountManager.on = eventEmitter.on.bind(eventEmitter)
+ cb(null, accountManager)
+ })
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+