aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
authorDan <danjm.com@gmail.com>2018-04-20 23:53:17 +0800
committerDan <danjm.com@gmail.com>2018-04-20 23:53:17 +0800
commit71b0de76ffdbdfc0ae696a009d5ee34971541e0b (patch)
treed0f2f4fd891a20eb7cbe5e7bfb82a375286fbc43 /app/scripts
parent9f12c26d44a0d78f28af25056857b993f80bbd95 (diff)
parent00efcf9e8ba34d448b628c98d32ad12d5be2ffc9 (diff)
downloadtangerine-wallet-browser-71b0de76ffdbdfc0ae696a009d5ee34971541e0b.tar.gz
tangerine-wallet-browser-71b0de76ffdbdfc0ae696a009d5ee34971541e0b.tar.zst
tangerine-wallet-browser-71b0de76ffdbdfc0ae696a009d5ee34971541e0b.zip
Merge branch 'master' into dm-docs-2
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js68
-rw-r--r--app/scripts/controllers/currency.js47
-rw-r--r--app/scripts/controllers/infura.js1
-rw-r--r--app/scripts/controllers/network.js1
-rw-r--r--app/scripts/controllers/preferences.js158
-rw-r--r--app/scripts/controllers/recent-blocks.js1
-rw-r--r--app/scripts/controllers/shapeshift.js69
-rw-r--r--app/scripts/controllers/token-rates.js77
-rw-r--r--app/scripts/controllers/transactions.js1
-rw-r--r--app/scripts/inpage.js7
-rw-r--r--app/scripts/lib/ComposableObservableStore.js49
-rw-r--r--app/scripts/lib/config-manager.js22
-rw-r--r--app/scripts/lib/createLoggerMiddleware.js16
-rw-r--r--app/scripts/lib/createOriginMiddleware.js12
-rw-r--r--app/scripts/lib/enums.js9
-rw-r--r--app/scripts/lib/environment-type.js19
-rw-r--r--app/scripts/lib/events-proxy.js25
-rw-r--r--app/scripts/lib/hex-to-bn.js7
-rw-r--r--app/scripts/lib/is-popup-or-notification.js18
-rw-r--r--app/scripts/lib/local-store.js38
-rw-r--r--app/scripts/lib/migrator/index.js35
-rw-r--r--app/scripts/lib/personal-message-manager.js1
-rw-r--r--app/scripts/lib/seed-phrase-verifier.js1
-rw-r--r--app/scripts/lib/stream-utils.js18
-rw-r--r--app/scripts/lib/typed-message-manager.js1
-rw-r--r--app/scripts/lib/util.js41
-rw-r--r--app/scripts/metamask-controller.js125
-rw-r--r--app/scripts/platforms/sw.js27
-rw-r--r--app/scripts/platforms/window.js23
-rw-r--r--app/scripts/ui.js8
30 files changed, 596 insertions, 329 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 5878cd2e8..6550e8944 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -21,12 +21,16 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
+const {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_FULLSCREEN,
+} = require('./lib/enums')
const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
-window.log = log
-log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
+log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
const platform = new ExtensionPlatform()
const notificationManager = new NotificationManager()
@@ -44,7 +48,7 @@ const isEdge = !isIE && !!window.StyleMedia
let popupIsOpen = false
let notificationIsOpen = false
-let openMetamaskTabsIDs = {}
+const openMetamaskTabsIDs = {}
// state persistence
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
@@ -173,7 +177,7 @@ function setupController (initState, initLangCode) {
return versionedData
}
- function persistData(state) {
+ function persistData (state) {
if (!state) {
throw new Error('MetaMask - updated state is missing', state)
}
@@ -192,30 +196,53 @@ function setupController (initState, initLangCode) {
//
// connect to other contexts
//
-
extension.runtime.onConnect.addListener(connectRemote)
+
+ const metamaskInternalProcessHash = {
+ [ENVIRONMENT_TYPE_POPUP]: true,
+ [ENVIRONMENT_TYPE_NOTIFICATION]: true,
+ [ENVIRONMENT_TYPE_FULLSCREEN]: true,
+ }
+
+ const isClientOpenStatus = () => {
+ return popupIsOpen || Boolean(Object.keys(openMetamaskTabsIDs).length) || notificationIsOpen
+ }
+
function connectRemote (remotePort) {
- const isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
+ const processName = remotePort.name
+ const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
const portStream = new PortStream(remotePort)
+
if (isMetaMaskInternalProcess) {
// communication with popup
- popupIsOpen = popupIsOpen || (remotePort.name === 'popup')
+ controller.isClientOpen = true
controller.setupTrustedCommunication(portStream, 'MetaMask')
- // record popup as closed
- if (remotePort.sender.url.match(/home.html$/)) {
- openMetamaskTabsIDs[remotePort.sender.tab.id] = true
- }
- if (remotePort.name === 'popup') {
+
+ if (processName === ENVIRONMENT_TYPE_POPUP) {
+ popupIsOpen = true
+
endOfStream(portStream, () => {
popupIsOpen = false
- if (remotePort.sender.url.match(/home.html$/)) {
- openMetamaskTabsIDs[remotePort.sender.tab.id] = false
- }
+ controller.isClientOpen = isClientOpenStatus()
})
}
- if (remotePort.name === 'notification') {
+
+ if (processName === ENVIRONMENT_TYPE_NOTIFICATION) {
+ notificationIsOpen = true
+
endOfStream(portStream, () => {
notificationIsOpen = false
+ controller.isClientOpen = isClientOpenStatus()
+ })
+ }
+
+ if (processName === ENVIRONMENT_TYPE_FULLSCREEN) {
+ const tabId = remotePort.sender.tab.id
+ openMetamaskTabsIDs[tabId] = true
+
+ endOfStream(portStream, () => {
+ delete openMetamaskTabsIDs[tabId]
+ controller.isClientOpen = isClientOpenStatus()
})
}
} else {
@@ -258,10 +285,11 @@ function setupController (initState, initLangCode) {
// popup trigger
function triggerUi () {
- extension.tabs.query({ active: true }, (tabs) => {
- const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id])
- if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) notificationManager.showPopup()
- notificationIsOpen = true
+ extension.tabs.query({ active: true }, tabs => {
+ const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id]))
+ if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) {
+ notificationManager.showPopup()
+ }
})
}
diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js
index c23c7f616..480c08b1c 100644
--- a/app/scripts/controllers/currency.js
+++ b/app/scripts/controllers/currency.js
@@ -1,17 +1,18 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
+const log = require('loglevel')
// every ten minutes
const POLLING_INTERVAL = 600000
class CurrencyController {
- /**
- * Controller responsible for managing data associated with the currently selected currency.
- *
+ /**
+ * Controller responsible for managing data associated with the currently selected currency.
+ *
* @typedef {Object} CurrencyController
- * @param {object} opts Overrides the defaults for the initial state of this.store
- * @property {array} opts.initState initializes the the state of the CurrencyController. Can contain an
+ * @param {object} opts Overrides the defaults for the initial state of this.store
+ * @property {array} opts.initState initializes the the state of the CurrencyController. Can contain an
* currentCurrency, conversionRate and conversionDate properties
* @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently
* selected by the user
@@ -20,8 +21,8 @@ class CurrencyController {
* since midnight of January 1, 1970
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
* Used to clear an existing interval on subsequent calls of that method.
- *
- */
+ *
+ */
constructor (opts = {}) {
const initState = extend({
currentCurrency: 'usd',
@@ -35,22 +36,22 @@ class CurrencyController {
// PUBLIC METHODS
//
- /**
- * A getter for the currentCurrency property
- *
- * @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user
- *
- */
+ /**
+ * A getter for the currentCurrency property
+ *
+ * @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user
+ *
+ */
getCurrentCurrency () {
return this.store.getState().currentCurrency
}
- /**
- * A setter for the currentCurrency property
- *
- * @param {string} currentCurrency The new currency to set as the currentCurrency in the store
- *
- */
+ /**
+ * A setter for the currentCurrency property
+ *
+ * @param {string} currentCurrency The new currency to set as the currentCurrency in the store
+ *
+ */
setCurrentCurrency (currentCurrency) {
this.store.updateState({ currentCurrency })
}
@@ -117,12 +118,12 @@ class CurrencyController {
}
}
- /**
- * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is
+ /**
+ * Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is
* stored at the controller's conversionInterval property. If it is called and such an id already exists, the
* previous interval is clear and a new one is created.
- *
- */
+ *
+ */
scheduleConversionInterval () {
if (this.conversionInterval) {
clearInterval(this.conversionInterval)
diff --git a/app/scripts/controllers/infura.js b/app/scripts/controllers/infura.js
index c6b4c9de2..8f6dd837e 100644
--- a/app/scripts/controllers/infura.js
+++ b/app/scripts/controllers/infura.js
@@ -1,5 +1,6 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
+const log = require('loglevel')
// every ten minutes
const POLLING_INTERVAL = 10 * 60 * 1000
diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js
index 617456cd7..45574e673 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network.js
@@ -9,6 +9,7 @@ const extend = require('xtend')
const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../lib/events-proxy.js')
const networkConfig = require('../config.js')
+const log = require('loglevel')
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index d54efb889..bdedde2fb 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -4,8 +4,8 @@ const extend = require('xtend')
class PreferencesController {
- /**
- *
+ /**
+ *
* @typedef {Object} PreferencesController
* @param {object} opts Overrides the defaults for the initial state of this.store
* @property {object} store The stored object containing a users preferences, stored in local storage
@@ -18,7 +18,7 @@ class PreferencesController {
* @property {string} store.currentLocale The preferred language locale key
* @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
*
- */
+ */
constructor (opts = {}) {
const initState = extend({
frequentRpcList: [],
@@ -32,43 +32,43 @@ class PreferencesController {
}
// PUBLIC METHODS
- /**
- * Setter for the `useBlockie` property
- *
- * @param {boolean} val Whether or not the user prefers blockie indicators
- *
- */
+ /**
+ * Setter for the `useBlockie` property
+ *
+ * @param {boolean} val Whether or not the user prefers blockie indicators
+ *
+ */
setUseBlockie (val) {
this.store.updateState({ useBlockie: val })
}
- /**
- * Getter for the `useBlockie` property
- *
- * @returns {boolean} this.store.useBlockie
- *
- */
+ /**
+ * Getter for the `useBlockie` property
+ *
+ * @returns {boolean} this.store.useBlockie
+ *
+ */
getUseBlockie () {
return this.store.getState().useBlockie
}
- /**
- * Setter for the `currentLocale` property
+ /**
+ * Setter for the `currentLocale` property
*
* @param {string} key he preferred language locale key
- *
- */
+ *
+ */
setCurrentLocale (key) {
this.store.updateState({ currentLocale: key })
}
- /**
- * Setter for the `selectedAddress` property
- *
- * @param {string} _address A new hex address for an account
- * @returns {Promise<void>} Promise resolves with undefined
- *
- */
+ /**
+ * Setter for the `selectedAddress` property
+ *
+ * @param {string} _address A new hex address for an account
+ * @returns {Promise<void>} Promise resolves with undefined
+ *
+ */
setSelectedAddress (_address) {
return new Promise((resolve, reject) => {
const address = normalizeAddress(_address)
@@ -129,13 +129,13 @@ class PreferencesController {
return Promise.resolve(tokens)
}
- /**
- * Removes a specified token from the tokens array.
- *
- * @param {string} rawAddress Hex address of the token contract to remove.
- * @returns {Promise<array> The new array of AddedToken objects
- *
- */
+ /**
+ * Removes a specified token from the tokens array.
+ *
+ * @param {string} rawAddress Hex address of the token contract to remove.
+ * @returns {Promise<array>} The new array of AddedToken objects
+ *
+ */
removeToken (rawAddress) {
const tokens = this.store.getState().tokens
@@ -145,23 +145,23 @@ class PreferencesController {
return Promise.resolve(updatedTokens)
}
- /**
- * A getter for the `tokens` property
- *
- * @returns {array} The current array of AddedToken objects
- *
- */
+ /**
+ * A getter for the `tokens` property
+ *
+ * @returns {array} The current array of AddedToken objects
+ *
+ */
getTokens () {
return this.store.getState().tokens
}
- /**
- * Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
- *
- * @param {string} _url The the new rpc url to add to the updated list
- * @returns {Promise<void>} Promise resolves with undefined
- *
- */
+ /**
+ * Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
+ *
+ * @param {string} _url The the new rpc url to add to the updated list
+ * @returns {Promise<void>} Promise resolves with undefined
+ *
+ */
updateFrequentRpcList (_url) {
return this.addToFrequentRpcList(_url)
.then((rpcList) => {
@@ -170,13 +170,13 @@ class PreferencesController {
})
}
- /**
- * Setter for the `currentAccountTab` property
- *
- * @param {string} currentAccountTab Specifies the new tab to be marked as current
- * @returns {Promise<void>} Promise resolves with undefined
- *
- */
+ /**
+ * Setter for the `currentAccountTab` property
+ *
+ * @param {string} currentAccountTab Specifies the new tab to be marked as current
+ * @returns {Promise<void>} Promise resolves with undefined
+ *
+ */
setCurrentAccountTab (currentAccountTab) {
return new Promise((resolve, reject) => {
this.store.updateState({ currentAccountTab })
@@ -184,15 +184,15 @@ class PreferencesController {
})
}
- /**
- * Returns an updated rpcList based on the passed url and the current list.
+ /**
+ * Returns an updated rpcList based on the passed url and the current list.
* The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
* end of the list. The current list is modified and returned as a promise.
- *
- * @param {string} _url The rpc url to add to the frequentRpcList.
- * @returns {Promise<array>} The updated frequentRpcList.
- *
- */
+ *
+ * @param {string} _url The rpc url to add to the frequentRpcList.
+ * @returns {Promise<array>} The updated frequentRpcList.
+ *
+ */
addToFrequentRpcList (_url) {
const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === _url })
@@ -208,24 +208,24 @@ class PreferencesController {
return Promise.resolve(rpcList)
}
- /**
- * Getter for the `frequentRpcList` property.
- *
- * @returns {array<string>} An array of one or two rpc urls.
- *
- */
+ /**
+ * Getter for the `frequentRpcList` property.
+ *
+ * @returns {array<string>} An array of one or two rpc urls.
+ *
+ */
getFrequentRpcList () {
return this.store.getState().frequentRpcList
}
- /**
- * Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean.
- *
- * @param {string} feature A key that corresponds to a UI feature.
+ /**
+ * Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean.
+ *
+ * @param {string} feature A key that corresponds to a UI feature.
* @param {boolean} activated Indicates whether or not the UI feature should be displayed
- * @returns {Promise<object>} Promises a new object; the updated featureFlags object.
- *
- */
+ * @returns {Promise<object>} Promises a new object; the updated featureFlags object.
+ *
+ */
setFeatureFlag (feature, activated) {
const currentFeatureFlags = this.store.getState().featureFlags
const updatedFeatureFlags = {
@@ -238,13 +238,13 @@ class PreferencesController {
return Promise.resolve(updatedFeatureFlags)
}
- /**
- * A getter for the `featureFlags` property
- *
- * @returns {object} A key-boolean map, where keys refer to features and booleans to whether the
+ /**
+ * A getter for the `featureFlags` property
+ *
+ * @returns {object} A key-boolean map, where keys refer to features and booleans to whether the
* user wishes to see that feature
- *
- */
+ *
+ */
getFeatureFlags () {
return this.store.getState().featureFlags
}
diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js
index ddcaa7220..1377c1ba9 100644
--- a/app/scripts/controllers/recent-blocks.js
+++ b/app/scripts/controllers/recent-blocks.js
@@ -2,6 +2,7 @@ const ObservableStore = require('obs-store')
const extend = require('xtend')
const BN = require('ethereumjs-util').BN
const EthQuery = require('eth-query')
+const log = require('loglevel')
class RecentBlocksController {
diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js
index 17994d2db..b2a1462c2 100644
--- a/app/scripts/controllers/shapeshift.js
+++ b/app/scripts/controllers/shapeshift.js
@@ -1,5 +1,6 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
+const log = require('loglevel')
// every three seconds when an incomplete tx is waiting
const POLLING_INTERVAL = 3000
@@ -31,7 +32,7 @@ class ShapeshiftController {
* @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
* user's Metamask account
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
- * @constant {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
+ * @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
* @property {number} time - The time at which the tx was created
* @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link
* https://developer.mozilla.org/en-US/docs/Web/API/Response}
@@ -41,38 +42,38 @@ class ShapeshiftController {
// PUBLIC METHODS
//
- /**
- * A getter for the shapeShiftTxList property
- *
- * @returns {array<ShapeShiftTx>}
- *
- */
+ /**
+ * A getter for the shapeShiftTxList property
+ *
+ * @returns {array<ShapeShiftTx>}
+ *
+ */
getShapeShiftTxList () {
const shapeShiftTxList = this.store.getState().shapeShiftTxList
return shapeShiftTxList
}
- /**
- * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit.
- *
- * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete
- *
- */
+ /**
+ * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit.
+ *
+ * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete
+ *
+ */
getPendingTxs () {
const txs = this.getShapeShiftTxList()
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
return pending
}
- /**
- * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
+ /**
+ * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
* pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and
* the polling stops.
*
* this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data
* is saved with saveTx.
- *
- */
+ *
+ */
pollForUpdates () {
const pendingTxs = this.getPendingTxs()
@@ -113,13 +114,13 @@ class ShapeshiftController {
}
}
- /**
- * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
+ /**
+ * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
* shapeShiftTxList, nothing happens.
- *
- * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
- *
- */
+ *
+ * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
+ *
+ */
saveTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(tx)
@@ -129,12 +130,12 @@ class ShapeshiftController {
}
}
- /**
- * Removes a ShapeShiftTx from the shapeShiftTxList
- *
- * @param {ShapeShiftTx} tx The tx to remove
- *
- */
+ /**
+ * Removes a ShapeShiftTx from the shapeShiftTxList
+ *
+ * @param {ShapeShiftTx} tx The tx to remove
+ *
+ */
removeShapeShiftTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(index)
@@ -144,14 +145,14 @@ class ShapeshiftController {
this.updateState({ shapeShiftTxList })
}
- /**
- * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
- *
+ /**
+ * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
+ *
* @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
* user's Metamask account
* @param {string} depositType - An abbreviation of the type of crypto currency to be deposited.
- *
- */
+ *
+ */
createShapeShiftTx (depositAddress, depositType) {
const state = this.store.getState()
let { shapeShiftTxList } = state
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
new file mode 100644
index 000000000..22e3e8154
--- /dev/null
+++ b/app/scripts/controllers/token-rates.js
@@ -0,0 +1,77 @@
+const ObservableStore = require('obs-store')
+
+// By default, poll every 3 minutes
+const DEFAULT_INTERVAL = 180 * 1000
+
+/**
+ * A controller that polls for token exchange
+ * rates based on a user's current token list
+ */
+class TokenRatesController {
+ /**
+ * Creates a TokenRatesController
+ *
+ * @param {Object} [config] - Options to configure controller
+ */
+ constructor ({ interval = DEFAULT_INTERVAL, preferences } = {}) {
+ this.store = new ObservableStore()
+ this.preferences = preferences
+ this.interval = interval
+ }
+
+ /**
+ * Updates exchange rates for all tokens
+ */
+ async updateExchangeRates () {
+ if (!this.isActive) { return }
+ const contractExchangeRates = {}
+ for (const i in this._tokens) {
+ const address = this._tokens[i].address
+ contractExchangeRates[address] = await this.fetchExchangeRate(address)
+ }
+ this.store.putState({ contractExchangeRates })
+ }
+
+ /**
+ * Fetches a token exchange rate by address
+ *
+ * @param {String} address - Token contract address
+ */
+ async fetchExchangeRate (address) {
+ try {
+ const response = await fetch(`https://exchanges.balanc3.net/prices?from=${address}&to=ETH&autoConversion=false&summaryOnly=true`)
+ const json = await response.json()
+ return json && json.length ? json[0].averagePrice : 0
+ } catch (error) { }
+ }
+
+ /**
+ * @type {Number} - Interval used to poll for exchange rates
+ */
+ set interval (interval) {
+ this._handle && clearInterval(this._handle)
+ if (!interval) { return }
+ this._handle = setInterval(() => { this.updateExchangeRates() }, interval)
+ }
+
+ /**
+ * @type {Object} - Preferences controller instance
+ */
+ set preferences (preferences) {
+ this._preferences && this._preferences.unsubscribe()
+ if (!preferences) { return }
+ this._preferences = preferences
+ this.tokens = preferences.getState().tokens
+ preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens })
+ }
+
+ /**
+ * @type {Array} - Array of token objects with contract addresses
+ */
+ set tokens (tokens) {
+ this._tokens = tokens
+ this.updateExchangeRates()
+ }
+}
+
+module.exports = TokenRatesController
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index 336b0d8f7..c8211ebd7 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -7,6 +7,7 @@ const TransactionStateManager = require('../lib/tx-state-manager')
const TxGasUtil = require('../lib/tx-gas-utils')
const PendingTransactionTracker = require('../lib/pending-tx-tracker')
const NonceTracker = require('../lib/nonce-tracker')
+const log = require('loglevel')
/*
Transaction Controller is an aggregate of sub-controllers and trackers
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index ec99bfc35..92c732813 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -3,16 +3,11 @@ cleanContextForImports()
require('web3/dist/web3.min.js')
const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream')
-// const PingStream = require('ping-pong-stream/ping')
-// const endOfStream = require('end-of-stream')
const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
restoreContextAfterImports()
-const METAMASK_DEBUG = process.env.METAMASK_DEBUG
-window.log = log
-log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
-
+log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
//
// setup plugin communication
diff --git a/app/scripts/lib/ComposableObservableStore.js b/app/scripts/lib/ComposableObservableStore.js
new file mode 100644
index 000000000..d5ee708a1
--- /dev/null
+++ b/app/scripts/lib/ComposableObservableStore.js
@@ -0,0 +1,49 @@
+const ObservableStore = require('obs-store')
+
+/**
+ * An ObservableStore that can composes a flat
+ * structure of child stores based on configuration
+ */
+class ComposableObservableStore extends ObservableStore {
+ /**
+ * Create a new store
+ *
+ * @param {Object} [initState] - The initial store state
+ * @param {Object} [config] - Map of internal state keys to child stores
+ */
+ constructor (initState, config) {
+ super(initState)
+ this.updateStructure(config)
+ }
+
+ /**
+ * Composes a new internal store subscription structure
+ *
+ * @param {Object} [config] - Map of internal state keys to child stores
+ */
+ updateStructure (config) {
+ this.config = config
+ this.removeAllListeners()
+ for (const key in config) {
+ config[key].subscribe((state) => {
+ this.updateState({ [key]: state })
+ })
+ }
+ }
+
+ /**
+ * Merges all child store state into a single object rather than
+ * returning an object keyed by child store class name
+ *
+ * @returns {Object} - Object containing merged child store state
+ */
+ getFlatState () {
+ let flatState = {}
+ for (const key in this.config) {
+ flatState = { ...flatState, ...this.config[key].getState() }
+ }
+ return flatState
+ }
+}
+
+module.exports = ComposableObservableStore
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index 34b603b96..63d27c40e 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -102,7 +102,6 @@ ConfigManager.prototype.setShowSeedWords = function (should) {
this.setData(data)
}
-
ConfigManager.prototype.getShouldShowSeedWords = function () {
var data = this.getData()
return data.showSeedWords
@@ -118,6 +117,27 @@ ConfigManager.prototype.getSeedWords = function () {
var data = this.getData()
return data.seedWords
}
+
+/**
+ * Called to set the isRevealingSeedWords flag. This happens only when the user chooses to reveal
+ * the seed words and not during the first time flow.
+ * @param {boolean} reveal - Value to set the isRevealingSeedWords flag.
+ */
+ConfigManager.prototype.setIsRevealingSeedWords = function (reveal = false) {
+ const data = this.getData()
+ data.isRevealingSeedWords = reveal
+ this.setData(data)
+}
+
+/**
+ * Returns the isRevealingSeedWords flag.
+ * @returns {boolean|undefined}
+ */
+ConfigManager.prototype.getIsRevealingSeedWords = function () {
+ const data = this.getData()
+ return data.isRevealingSeedWords
+}
+
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
var config = this.getConfig()
config.provider = {
diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js
index 2707cbd9e..996c3477c 100644
--- a/app/scripts/lib/createLoggerMiddleware.js
+++ b/app/scripts/lib/createLoggerMiddleware.js
@@ -1,14 +1,20 @@
-// log rpc activity
+const log = require('loglevel')
+
module.exports = createLoggerMiddleware
-function createLoggerMiddleware ({ origin }) {
- return function loggerMiddleware (req, res, next, end) {
- next((cb) => {
+/**
+ * Returns a middleware that logs RPC activity
+ * @param {{ origin: string }} opts - The middleware options
+ * @returns {Function}
+ */
+function createLoggerMiddleware (opts) {
+ return function loggerMiddleware (/** @type {any} */ req, /** @type {any} */ res, /** @type {Function} */ next) {
+ next((/** @type {Function} */ cb) => {
if (res.error) {
log.error('Error in RPC response:\n', res)
}
if (req.isMetamaskInternal) return
- log.info(`RPC (${origin}):`, req, '->', res)
+ log.info(`RPC (${opts.origin}):`, req, '->', res)
cb()
})
}
diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js
index f8bdb2dc2..98bb0e3b3 100644
--- a/app/scripts/lib/createOriginMiddleware.js
+++ b/app/scripts/lib/createOriginMiddleware.js
@@ -1,9 +1,13 @@
-// append dapp origin domain to request
module.exports = createOriginMiddleware
-function createOriginMiddleware ({ origin }) {
- return function originMiddleware (req, res, next, end) {
- req.origin = origin
+/**
+ * Returns a middleware that appends the DApp origin to request
+ * @param {{ origin: string }} opts - The middleware options
+ * @returns {Function}
+ */
+function createOriginMiddleware (opts) {
+ return function originMiddleware (/** @type {any} */ req, /** @type {any} */ _, /** @type {Function} */ next) {
+ req.origin = opts.origin
next()
}
}
diff --git a/app/scripts/lib/enums.js b/app/scripts/lib/enums.js
new file mode 100644
index 000000000..0a3afca47
--- /dev/null
+++ b/app/scripts/lib/enums.js
@@ -0,0 +1,9 @@
+const ENVIRONMENT_TYPE_POPUP = 'popup'
+const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
+const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
+
+module.exports = {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_FULLSCREEN,
+}
diff --git a/app/scripts/lib/environment-type.js b/app/scripts/lib/environment-type.js
deleted file mode 100644
index f13a1574d..000000000
--- a/app/scripts/lib/environment-type.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * 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)
- * - 'responsive' refers to the main browser window
- * - 'notification' refers to the popup that appears in its own window when taking action outside of metamask
- *
- * @returns {string} A single word label that represents the type of window through which the app is being viewed
- *
- */
-module.exports = function environmentType () {
- const url = window.location.href
- if (url.match(/popup.html$/)) {
- return 'popup'
- } else if (url.match(/home.html$/)) {
- return 'responsive'
- } else {
- return 'notification'
- }
-}
diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js
index c0a490b05..f83773ccc 100644
--- a/app/scripts/lib/events-proxy.js
+++ b/app/scripts/lib/events-proxy.js
@@ -1,26 +1,37 @@
+/**
+ * Returns an EventEmitter that proxies events from the given event emitter
+ * @param {any} eventEmitter
+ * @param {object} listeners - The listeners to proxy to
+ * @returns {any}
+ */
module.exports = function createEventEmitterProxy (eventEmitter, listeners) {
let target = eventEmitter
const eventHandlers = listeners || {}
- const proxy = new Proxy({}, {
- get: (obj, name) => {
+ const proxy = /** @type {any} */ (new Proxy({}, {
+ get: (_, name) => {
// intercept listeners
if (name === 'on') return addListener
if (name === 'setTarget') return setTarget
if (name === 'proxyEventHandlers') return eventHandlers
- return target[name]
+ return (/** @type {any} */ (target))[name]
},
- set: (obj, name, value) => {
+ set: (_, name, value) => {
target[name] = value
return true
},
- })
- function setTarget (eventEmitter) {
+ }))
+ function setTarget (/** @type {EventEmitter} */ eventEmitter) {
target = eventEmitter
// migrate listeners
Object.keys(eventHandlers).forEach((name) => {
- eventHandlers[name].forEach((handler) => target.on(name, handler))
+ /** @type {Array<Function>} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler))
})
}
+ /**
+ * Attaches a function to be called whenever the specified event is emitted
+ * @param {string} name
+ * @param {Function} handler
+ */
function addListener (name, handler) {
if (!eventHandlers[name]) eventHandlers[name] = []
eventHandlers[name].push(handler)
diff --git a/app/scripts/lib/hex-to-bn.js b/app/scripts/lib/hex-to-bn.js
index 184217279..b28746920 100644
--- a/app/scripts/lib/hex-to-bn.js
+++ b/app/scripts/lib/hex-to-bn.js
@@ -1,6 +1,11 @@
-const ethUtil = require('ethereumjs-util')
+const ethUtil = (/** @type {object} */ (require('ethereumjs-util')))
const BN = ethUtil.BN
+/**
+ * Returns a [BinaryNumber]{@link BN} representation of the given hex value
+ * @param {string} hex
+ * @return {any}
+ */
module.exports = function hexToBn (hex) {
return new BN(ethUtil.stripHexPrefix(hex), 16)
}
diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js
deleted file mode 100644
index 894564def..000000000
--- a/app/scripts/lib/is-popup-or-notification.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Indicates whether the user is viewing the app through an extension like window or through a notification.
- * Used to make some style decisions on the frontend, and when deciding whether to close the popup in the backend.
- *
- * @returns {string} Returns 'popup' if the user is viewing through the browser ('home.html') or popup extension
- * ('popup.html'). Otherwise it returns 'notification'.
- *
- */
-module.exports = function isPopupOrNotification () {
- const url = window.location.href
-
- if (url.match(/popup.html(?:\?.+)*$/) ||
- url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
- return 'popup'
- } else {
- return 'notification'
- }
-}
diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js
index 5b47985f6..139ff86bd 100644
--- a/app/scripts/lib/local-store.js
+++ b/app/scripts/lib/local-store.js
@@ -1,10 +1,13 @@
-// We should not rely on local storage in an extension!
-// We should use this instead!
-// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local
-
const extension = require('extensionizer')
+const log = require('loglevel')
+/**
+ * A wrapper around the extension's storage local API
+ */
module.exports = class ExtensionStore {
+ /**
+ * @constructor
+ */
constructor() {
this.isSupported = !!(extension.storage.local)
if (!this.isSupported) {
@@ -12,6 +15,10 @@ module.exports = class ExtensionStore {
}
}
+ /**
+ * Returns all of the keys currently saved
+ * @return {Promise<*>}
+ */
async get() {
if (!this.isSupported) return undefined
const result = await this._get()
@@ -24,14 +31,24 @@ module.exports = class ExtensionStore {
}
}
+ /**
+ * Sets the key in local state
+ * @param {object} state - The state to set
+ * @return {Promise<void>}
+ */
async set(state) {
return this._set(state)
}
+ /**
+ * Returns all of the keys currently saved
+ * @private
+ * @return {object} the key-value map from local storage
+ */
_get() {
const local = extension.storage.local
return new Promise((resolve, reject) => {
- local.get(null, (result) => {
+ local.get(null, (/** @type {any} */ result) => {
const err = extension.runtime.lastError
if (err) {
reject(err)
@@ -42,6 +59,12 @@ module.exports = class ExtensionStore {
})
}
+ /**
+ * Sets the key in local state
+ * @param {object} obj - The key to set
+ * @return {Promise<void>}
+ * @private
+ */
_set(obj) {
const local = extension.storage.local
return new Promise((resolve, reject) => {
@@ -57,6 +80,11 @@ module.exports = class ExtensionStore {
}
}
+/**
+ * Returns whether or not the given object contains no keys
+ * @param {object} obj - The object to check
+ * @returns {boolean}
+ */
function isEmpty(obj) {
return Object.keys(obj).length === 0
}
diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js
index 85c2717ea..345ca8001 100644
--- a/app/scripts/lib/migrator/index.js
+++ b/app/scripts/lib/migrator/index.js
@@ -1,7 +1,23 @@
const EventEmitter = require('events')
+/**
+ * @typedef {object} Migration
+ * @property {number} version - The migration version
+ * @property {Function} migrate - Returns a promise of the migrated data
+ */
+
+/**
+ * @typedef {object} MigratorOptions
+ * @property {Array<Migration>} [migrations] - The list of migrations to apply
+ * @property {number} [defaultVersion] - The version to use in the initial state
+ */
+
class Migrator extends EventEmitter {
+ /**
+ * @constructor
+ * @param {MigratorOptions} opts
+ */
constructor (opts = {}) {
super()
const migrations = opts.migrations || []
@@ -42,19 +58,30 @@ class Migrator extends EventEmitter {
return versionedData
- // migration is "pending" if it has a higher
- // version number than currentVersion
+ /**
+ * Returns whether or not the migration is pending
+ *
+ * A migration is considered "pending" if it has a higher
+ * version number than the current version.
+ * @param {Migration} migration
+ * @returns {boolean}
+ */
function migrationIsPending (migration) {
return migration.version > versionedData.meta.version
}
}
- generateInitialState (initState) {
+ /**
+ * Returns the initial state for the migrator
+ * @param {object} [data] - The data for the initial state
+ * @returns {{meta: {version: number}, data: any}}
+ */
+ generateInitialState (data) {
return {
meta: {
version: this.defaultVersion,
},
- data: initState,
+ data,
}
}
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index 3c502329c..4f19876db 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -3,6 +3,7 @@ const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const createId = require('./random-id')
const hexRe = /^[0-9A-Fa-f]+$/g
+const log = require('loglevel')
/**
* Represents, and contains data about, an 'personal_sign' type signature request. These are created when a
diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js
index 826e73306..3b5afb800 100644
--- a/app/scripts/lib/seed-phrase-verifier.js
+++ b/app/scripts/lib/seed-phrase-verifier.js
@@ -1,4 +1,5 @@
const KeyringController = require('eth-keyring-controller')
+const log = require('loglevel')
const seedPhraseVerifier = {
diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js
index 8bb0b4f3c..3dbc064b5 100644
--- a/app/scripts/lib/stream-utils.js
+++ b/app/scripts/lib/stream-utils.js
@@ -8,20 +8,34 @@ module.exports = {
setupMultiplex: setupMultiplex,
}
+/**
+ * Returns a stream transform that parses JSON strings passing through
+ * @return {stream.Transform}
+ */
function jsonParseStream () {
- return Through.obj(function (serialized, encoding, cb) {
+ return Through.obj(function (serialized, _, cb) {
this.push(JSON.parse(serialized))
cb()
})
}
+/**
+ * Returns a stream transform that calls {@code JSON.stringify}
+ * on objects passing through
+ * @return {stream.Transform} the stream transform
+ */
function jsonStringifyStream () {
- return Through.obj(function (obj, encoding, cb) {
+ return Through.obj(function (obj, _, cb) {
this.push(JSON.stringify(obj))
cb()
})
}
+/**
+ * Sets up stream multiplexing for the given stream
+ * @param {any} connectionStream - the stream to mux
+ * @return {stream.Stream} the multiplexed stream
+ */
function setupMultiplex (connectionStream) {
const mux = new ObjectMultiplex()
pump(
diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js
index 8716ebf9a..367c6ecb9 100644
--- a/app/scripts/lib/typed-message-manager.js
+++ b/app/scripts/lib/typed-message-manager.js
@@ -3,6 +3,7 @@ const ObservableStore = require('obs-store')
const createId = require('./random-id')
const assert = require('assert')
const sigUtil = require('eth-sig-util')
+const log = require('loglevel')
/**
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js
index cb0d7e5c1..431d1e59c 100644
--- a/app/scripts/lib/util.js
+++ b/app/scripts/lib/util.js
@@ -1,14 +1,11 @@
const ethUtil = require('ethereumjs-util')
const assert = require('assert')
const BN = require('bn.js')
-
-module.exports = {
- getStack,
- sufficientBalance,
- hexToBn,
- bnToHex,
- BnMultiplyByFraction,
-}
+const {
+ ENVIRONMENT_TYPE_POPUP,
+ ENVIRONMENT_TYPE_NOTIFICATION,
+ ENVIRONMENT_TYPE_FULLSCREEN,
+} = require('./enums')
/**
* Generates an example stack trace
@@ -22,6 +19,25 @@ function getStack () {
}
/**
+ * 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)
+ * - 'responsive' refers to the main browser window
+ * - 'notification' refers to the popup that appears in its own window when taking action outside of metamask
+ *
+ * @returns {string} A single word label that represents the type of window through which the app is being viewed
+ *
+ */
+const getEnvironmentType = (url = window.location.href) => {
+ if (url.match(/popup.html(?:\?.+)*$/)) {
+ return ENVIRONMENT_TYPE_POPUP
+ } else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
+ return ENVIRONMENT_TYPE_FULLSCREEN
+ } else {
+ return ENVIRONMENT_TYPE_NOTIFICATION
+ }
+}
+
+/**
* Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee
*
* @param {object} txParams Contains data about a transaction
@@ -82,3 +98,12 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) {
const denomBN = new BN(denominator)
return targetBN.mul(numBN).div(denomBN)
}
+
+module.exports = {
+ getStack,
+ getEnvironmentType,
+ sufficientBalance,
+ hexToBn,
+ bnToHex,
+ BnMultiplyByFraction,
+}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index b96acc9da..782bc50ac 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -5,10 +5,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')
@@ -34,6 +34,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')
@@ -44,6 +45,7 @@ const BN = require('ethereumjs-util').BN
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
+const log = require('loglevel')
module.exports = class MetamaskController extends EventEmitter {
@@ -65,7 +67,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()
@@ -104,6 +106,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,
@@ -184,53 +191,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))
}
/**
@@ -279,6 +270,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)
})
@@ -308,33 +300,17 @@ 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(),
- }
- )
+ isRevealingSeedWords: Boolean(this.configManager.getIsRevealingSeedWords()),
+ },
+ }
}
/**
@@ -372,6 +348,7 @@ module.exports = class MetamaskController extends EventEmitter {
clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
importAccountWithStrategy: this.importAccountWithStrategy.bind(this),
+ setIsRevealingSeedWords: this.configManager.setIsRevealingSeedWords.bind(this.configManager),
// vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController),
@@ -1057,4 +1034,12 @@ 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
+ }
}
diff --git a/app/scripts/platforms/sw.js b/app/scripts/platforms/sw.js
index 007d8dc5b..56c5f2774 100644
--- a/app/scripts/platforms/sw.js
+++ b/app/scripts/platforms/sw.js
@@ -1,20 +1,25 @@
-
class SwPlatform {
-
- //
- // Public
- //
-
+ /**
+ * Reloads the platform
+ */
reload () {
- // you cant actually do this
- global.location.reload()
+ // TODO: you can't actually do this
+ /** @type {any} */ (global).location.reload()
}
- openWindow ({ url }) {
- // this doesnt actually work
- global.open(url, '_blank')
+ /**
+ * Opens a window
+ * @param {{url: string}} opts - The window options
+ */
+ openWindow (opts) {
+ // TODO: this doesn't actually work
+ /** @type {any} */ (global).open(opts.url, '_blank')
}
+ /**
+ * Returns the platform version
+ * @returns {string}
+ */
getVersion () {
return '<unable to read version>'
}
diff --git a/app/scripts/platforms/window.js b/app/scripts/platforms/window.js
index 1527c008b..943b2a703 100644
--- a/app/scripts/platforms/window.js
+++ b/app/scripts/platforms/window.js
@@ -1,18 +1,23 @@
-
class WindowPlatform {
-
- //
- // Public
- //
-
+ /**
+ * Reload the platform
+ */
reload () {
- global.location.reload()
+ /** @type {any} */ (global).location.reload()
}
- openWindow ({ url }) {
- global.open(url, '_blank')
+ /**
+ * Opens a window
+ * @param {{url: string}} opts - The window options
+ */
+ openWindow (opts) {
+ /** @type {any} */ (global).open(opts.url, '_blank')
}
+ /**
+ * Returns the platform version
+ * @returns {string}
+ */
getVersion () {
return '<unable to read version>'
}
diff --git a/app/scripts/ui.js b/app/scripts/ui.js
index 13c7ac5ec..bdab29c1e 100644
--- a/app/scripts/ui.js
+++ b/app/scripts/ui.js
@@ -3,12 +3,14 @@ const OldMetaMaskUiCss = require('../../old-ui/css')
const NewMetaMaskUiCss = require('../../ui/css')
const startPopup = require('./popup-core')
const PortStream = require('./lib/port-stream.js')
-const isPopupOrNotification = require('./lib/is-popup-or-notification')
+const { getEnvironmentType } = require('./lib/util')
+const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums')
const extension = require('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')
start().catch(log.error)
@@ -26,7 +28,7 @@ async function start() {
// injectCss(css)
// identify window type (popup, notification)
- const windowType = isPopupOrNotification()
+ const windowType = getEnvironmentType(window.location.href)
global.METAMASK_UI_TYPE = windowType
closePopupIfOpen(windowType)
@@ -68,7 +70,7 @@ async function start() {
function closePopupIfOpen (windowType) {
- if (windowType !== 'notification') {
+ if (windowType !== ENVIRONMENT_TYPE_NOTIFICATION) {
// should close only chrome popup
notificationManager.closePopup()
}