aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2017-01-25 12:38:13 +0800
committerGitHub <noreply@github.com>2017-01-25 12:38:13 +0800
commitd30612a2168b02c39a3eaa86f29e47d9edda07d8 (patch)
tree27eab17687c0c7a1b2583f51abf8064fa54de82a
parent4f39e8192cd94ad45d68992d5d1129f1612b1aa6 (diff)
parent0f33acb80ca90e07e6f7b7c083f52a1f4344c48e (diff)
downloadtangerine-wallet-browser-d30612a2168b02c39a3eaa86f29e47d9edda07d8.tar.gz
tangerine-wallet-browser-d30612a2168b02c39a3eaa86f29e47d9edda07d8.tar.zst
tangerine-wallet-browser-d30612a2168b02c39a3eaa86f29e47d9edda07d8.zip
Merge pull request #999 from MetaMask/obs-store2
background - introduce ObservableStore (mark II)
-rw-r--r--app/scripts/background.js277
-rw-r--r--app/scripts/first-time-state.js11
-rw-r--r--app/scripts/lib/config-manager.js71
-rw-r--r--app/scripts/lib/inpage-provider.js73
-rw-r--r--app/scripts/lib/migrations.js5
-rw-r--r--app/scripts/lib/migrator/index.js40
-rw-r--r--app/scripts/lib/remote-store.js97
-rw-r--r--app/scripts/metamask-controller.js65
-rw-r--r--app/scripts/migrations/002.js15
-rw-r--r--app/scripts/migrations/003.js16
-rw-r--r--app/scripts/migrations/004.js17
-rw-r--r--app/scripts/migrations/_multi-keyring.js51
-rw-r--r--app/scripts/migrations/index.js18
-rw-r--r--mock-dev.js96
-rw-r--r--package.json3
-rw-r--r--test/integration/lib/idStore-migrator-test.js65
-rw-r--r--test/lib/mock-config-manager.js62
-rw-r--r--test/unit/config-manager-test.js26
-rw-r--r--test/unit/idStore-migration-test.js8
-rw-r--r--test/unit/metamask-controller-test.js6
-rw-r--r--test/unit/migrations-test.js48
21 files changed, 508 insertions, 562 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index f3837a028..18882e5d5 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -1,162 +1,181 @@
const urlUtil = require('url')
-const extend = require('xtend')
const Dnode = require('dnode')
const eos = require('end-of-stream')
+const asyncQ = require('async-q')
+const pipe = require('pump')
+const LocalStorageStore = require('obs-store/lib/localStorage')
+const storeTransform = require('obs-store/lib/transform')
+const Migrator = require('./lib/migrator/')
+const migrations = require('./migrations/')
const PortStream = require('./lib/port-stream.js')
const notification = require('./lib/notifications.js')
const messageManager = require('./lib/message-manager')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const MetamaskController = require('./metamask-controller')
const extension = require('./lib/extension')
+const firstTimeState = require('./first-time-state')
const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
-var popupIsOpen = false
-
-const controller = new MetamaskController({
- // User confirmation callbacks:
- showUnconfirmedMessage: triggerUi,
- unlockAccountMessage: triggerUi,
- showUnapprovedTx: triggerUi,
- // Persistence Methods:
- setData,
- loadData,
-})
-function triggerUi () {
- if (!popupIsOpen) notification.show()
-}
-// On first install, open a window to MetaMask website to how-it-works.
-extension.runtime.onInstalled.addListener(function (details) {
- if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
- extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
- }
-})
-
-//
-// connect to other contexts
-//
+let popupIsOpen = false
-extension.runtime.onConnect.addListener(connectRemote)
-function connectRemote (remotePort) {
- var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
- var portStream = new PortStream(remotePort)
- if (isMetaMaskInternalProcess) {
- // communication with popup
- popupIsOpen = remotePort.name === 'popup'
- setupTrustedCommunication(portStream, 'MetaMask', remotePort.name)
- } else {
- // communication with page
- var originDomain = urlUtil.parse(remotePort.sender.url).hostname
- setupUntrustedCommunication(portStream, originDomain)
- }
-}
-
-function setupUntrustedCommunication (connectionStream, originDomain) {
- // setup multiplexing
- var mx = setupMultiplex(connectionStream)
- // connect features
- controller.setupProviderConnection(mx.createStream('provider'), originDomain)
- controller.setupPublicConfig(mx.createStream('publicConfig'))
-}
+// state persistence
+const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
-function setupTrustedCommunication (connectionStream, originDomain) {
- // setup multiplexing
- var mx = setupMultiplex(connectionStream)
- // connect features
- setupControllerConnection(mx.createStream('controller'))
- controller.setupProviderConnection(mx.createStream('provider'), originDomain)
-}
+// initialization flow
+asyncQ.waterfall([
+ () => loadStateFromPersistence(),
+ (initState) => setupController(initState),
+])
+.then(() => console.log('MetaMask initialization complete.'))
+.catch((err) => { console.error(err) })
//
-// remote features
+// State and Persistence
//
-function setupControllerConnection (stream) {
- controller.stream = stream
- var api = controller.getApi()
- var dnode = Dnode(api)
- stream.pipe(dnode).pipe(stream)
- dnode.on('remote', (remote) => {
- // push updates to popup
- var sendUpdate = remote.sendUpdate.bind(remote)
- controller.on('update', sendUpdate)
- // teardown on disconnect
- eos(stream, () => {
- controller.removeListener('update', sendUpdate)
- popupIsOpen = false
- })
- })
+function loadStateFromPersistence() {
+ // migrations
+ let migrator = new Migrator({ migrations })
+ let initialState = migrator.generateInitialState(firstTimeState)
+ return asyncQ.waterfall([
+ // read from disk
+ () => Promise.resolve(diskStore.getState() || initialState),
+ // migrate data
+ (versionedData) => migrator.migrateData(versionedData),
+ // write to disk
+ (versionedData) => {
+ diskStore.putState(versionedData)
+ return Promise.resolve(versionedData)
+ },
+ // resolve to just data
+ (versionedData) => Promise.resolve(versionedData.data),
+ ])
}
-//
-// plugin badge text
-//
+function setupController (initState) {
+
+ //
+ // MetaMask Controller
+ //
-controller.txManager.on('updateBadge', updateBadge)
-updateBadge()
-
-function updateBadge () {
- var label = ''
- var unapprovedTxCount = controller.txManager.unapprovedTxCount
- var unconfMsgs = messageManager.unconfirmedMsgs()
- var unconfMsgLen = Object.keys(unconfMsgs).length
- var count = unapprovedTxCount + unconfMsgLen
- if (count) {
- label = String(count)
+ const controller = new MetamaskController({
+ // User confirmation callbacks:
+ showUnconfirmedMessage: triggerUi,
+ unlockAccountMessage: triggerUi,
+ showUnapprovedTx: triggerUi,
+ // initial state
+ initState,
+ })
+ global.metamaskController = controller
+
+ // setup state persistence
+ pipe(
+ controller.store,
+ storeTransform(versionifyData),
+ diskStore
+ )
+
+ function versionifyData(state) {
+ let versionedData = diskStore.getState()
+ versionedData.data = state
+ return versionedData
}
- extension.browserAction.setBadgeText({ text: label })
- extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
-}
-// data :: setters/getters
+ //
+ // connect to other contexts
+ //
+
+ extension.runtime.onConnect.addListener(connectRemote)
+ function connectRemote (remotePort) {
+ var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
+ var portStream = new PortStream(remotePort)
+ if (isMetaMaskInternalProcess) {
+ // communication with popup
+ popupIsOpen = remotePort.name === 'popup'
+ setupTrustedCommunication(portStream, 'MetaMask', remotePort.name)
+ } else {
+ // communication with page
+ var originDomain = urlUtil.parse(remotePort.sender.url).hostname
+ setupUntrustedCommunication(portStream, originDomain)
+ }
+ }
-function loadData () {
- var oldData = getOldStyleData()
- var newData
- try {
- newData = JSON.parse(window.localStorage[STORAGE_KEY])
- } catch (e) {}
+ function setupUntrustedCommunication (connectionStream, originDomain) {
+ // setup multiplexing
+ var mx = setupMultiplex(connectionStream)
+ // connect features
+ controller.setupProviderConnection(mx.createStream('provider'), originDomain)
+ controller.setupPublicConfig(mx.createStream('publicConfig'))
+ }
- var data = extend({
- meta: {
- version: 0,
- },
- data: {
- config: {
- provider: {
- type: 'testnet',
- },
- },
- },
- }, oldData || null, newData || null)
- return data
-}
+ function setupTrustedCommunication (connectionStream, originDomain) {
+ // setup multiplexing
+ var mx = setupMultiplex(connectionStream)
+ // connect features
+ setupControllerConnection(mx.createStream('controller'))
+ controller.setupProviderConnection(mx.createStream('provider'), originDomain)
+ }
-function getOldStyleData () {
- var config, wallet, seedWords
+ //
+ // remote features
+ //
+
+ function setupControllerConnection (stream) {
+ controller.stream = stream
+ var api = controller.getApi()
+ var dnode = Dnode(api)
+ stream.pipe(dnode).pipe(stream)
+ dnode.on('remote', (remote) => {
+ // push updates to popup
+ var sendUpdate = remote.sendUpdate.bind(remote)
+ controller.on('update', sendUpdate)
+ // teardown on disconnect
+ eos(stream, () => {
+ controller.removeListener('update', sendUpdate)
+ popupIsOpen = false
+ })
+ })
+ }
- var result = {
- meta: { version: 0 },
- data: {},
+ //
+ // User Interface setup
+ //
+
+ updateBadge()
+ controller.txManager.on('updateBadge', updateBadge)
+
+ // plugin badge text
+ function updateBadge () {
+ var label = ''
+ var unapprovedTxCount = controller.txManager.unapprovedTxCount
+ var unconfMsgs = messageManager.unconfirmedMsgs()
+ var unconfMsgLen = Object.keys(unconfMsgs).length
+ var count = unapprovedTxCount + unconfMsgLen
+ if (count) {
+ label = String(count)
+ }
+ extension.browserAction.setBadgeText({ text: label })
+ extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
}
- 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
+ return Promise.resolve()
+
}
-function setData (data) {
- window.localStorage[STORAGE_KEY] = JSON.stringify(data)
+//
+// Etc...
+//
+
+// popup trigger
+function triggerUi () {
+ if (!popupIsOpen) notification.show()
}
+
+// On first install, open a window to MetaMask website to how-it-works.
+extension.runtime.onInstalled.addListener(function (details) {
+ if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
+ extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
+ }
+}) \ No newline at end of file
diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js
new file mode 100644
index 000000000..3196981ba
--- /dev/null
+++ b/app/scripts/first-time-state.js
@@ -0,0 +1,11 @@
+//
+// The default state of MetaMask
+//
+
+module.exports = {
+ config: {
+ provider: {
+ type: 'testnet',
+ },
+ },
+} \ No newline at end of file
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index e927c78ec..6d088906c 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -1,6 +1,4 @@
-const Migrator = require('pojo-migrator')
const MetamaskConfig = require('../config.js')
-const migrations = require('./migrations')
const ethUtil = require('ethereumjs-util')
const normalize = require('./sig-util').normalize
@@ -19,41 +17,18 @@ module.exports = ConfigManager
function ConfigManager (opts) {
// ConfigManager is observable and will emit updates
this._subs = []
-
- /* The migrator exported on the config-manager
- * has two methods the user should be concerned with:
- *
- * getData(), which returns the app-consumable data object
- * saveData(), which persists the app-consumable data object.
- */
- this.migrator = new Migrator({
-
- // Migrations must start at version 1 or later.
- // They are objects with a `version` number
- // and a `migrate` function.
- //
- // The `migrate` function receives the previous
- // config data format, and returns the new one.
- migrations: migrations,
-
- // How to load initial config.
- // Includes step on migrating pre-pojo-migrator data.
- loadData: opts.loadData,
-
- // How to persist migrated config.
- setData: opts.setData,
- })
+ this.store = opts.store
}
ConfigManager.prototype.setConfig = function (config) {
- var data = this.migrator.getData()
+ var data = this.getData()
data.config = config
this.setData(data)
this._emitUpdates(config)
}
ConfigManager.prototype.getConfig = function () {
- var data = this.migrator.getData()
+ var data = this.getData()
if ('config' in data) {
return data.config
} else {
@@ -96,15 +71,15 @@ ConfigManager.prototype.getProvider = function () {
}
ConfigManager.prototype.setData = function (data) {
- this.migrator.saveData(data)
+ this.store.putState(data)
}
ConfigManager.prototype.getData = function () {
- return this.migrator.getData()
+ return this.store.getState()
}
ConfigManager.prototype.setWallet = function (wallet) {
- var data = this.migrator.getData()
+ var data = this.getData()
data.wallet = wallet
this.setData(data)
}
@@ -121,11 +96,11 @@ ConfigManager.prototype.getVault = function () {
}
ConfigManager.prototype.getKeychains = function () {
- return this.migrator.getData().keychains || []
+ return this.getData().keychains || []
}
ConfigManager.prototype.setKeychains = function (keychains) {
- var data = this.migrator.getData()
+ var data = this.getData()
data.keychains = keychains
this.setData(data)
}
@@ -142,19 +117,19 @@ ConfigManager.prototype.setSelectedAccount = function (address) {
}
ConfigManager.prototype.getWallet = function () {
- return this.migrator.getData().wallet
+ return this.getData().wallet
}
// Takes a boolean
ConfigManager.prototype.setShowSeedWords = function (should) {
- var data = this.migrator.getData()
+ var data = this.getData()
data.showSeedWords = should
this.setData(data)
}
ConfigManager.prototype.getShouldShowSeedWords = function () {
- var data = this.migrator.getData()
+ var data = this.getData()
return data.showSeedWords
}
@@ -166,7 +141,7 @@ ConfigManager.prototype.setSeedWords = function (words) {
ConfigManager.prototype.getSeedWords = function () {
var data = this.getData()
- return ('seedWords' in data) && data.seedWords
+ return data.seedWords
}
ConfigManager.prototype.getCurrentRpcAddress = function () {
@@ -188,16 +163,12 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
}
}
-ConfigManager.prototype.setData = function (data) {
- this.migrator.saveData(data)
-}
-
//
// Tx
//
ConfigManager.prototype.getTxList = function () {
- var data = this.migrator.getData()
+ var data = this.getData()
if (data.transactions !== undefined) {
return data.transactions
} else {
@@ -206,7 +177,7 @@ ConfigManager.prototype.getTxList = function () {
}
ConfigManager.prototype.setTxList = function (txList) {
- var data = this.migrator.getData()
+ var data = this.getData()
data.transactions = txList
this.setData(data)
}
@@ -239,7 +210,7 @@ ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
ConfigManager.prototype.getSalt = function () {
var data = this.getData()
- return ('salt' in data) && data.salt
+ return data.salt
}
ConfigManager.prototype.setSalt = function (salt) {
@@ -273,7 +244,7 @@ ConfigManager.prototype.setConfirmedDisclaimer = function (confirmed) {
ConfigManager.prototype.getConfirmedDisclaimer = function () {
var data = this.getData()
- return ('isDisclaimerConfirmed' in data) && data.isDisclaimerConfirmed
+ return data.isDisclaimerConfirmed
}
ConfigManager.prototype.setTOSHash = function (hash) {
@@ -284,7 +255,7 @@ ConfigManager.prototype.setTOSHash = function (hash) {
ConfigManager.prototype.getTOSHash = function () {
var data = this.getData()
- return ('TOSHash' in data) && data.TOSHash
+ return data.TOSHash
}
ConfigManager.prototype.setCurrentFiat = function (currency) {
@@ -295,7 +266,7 @@ ConfigManager.prototype.setCurrentFiat = function (currency) {
ConfigManager.prototype.getCurrentFiat = function () {
var data = this.getData()
- return ('fiatCurrency' in data) && data.fiatCurrency
+ return data.fiatCurrency
}
ConfigManager.prototype.updateConversionRate = function () {
@@ -326,12 +297,12 @@ ConfigManager.prototype.setConversionDate = function (datestring) {
ConfigManager.prototype.getConversionRate = function () {
var data = this.getData()
- return (('conversionRate' in data) && data.conversionRate) || 0
+ return (data.conversionRate) || 0
}
ConfigManager.prototype.getConversionDate = function () {
var data = this.getData()
- return (('conversionDate' in data) && data.conversionDate) || 'N/A'
+ return (data.conversionDate) || 'N/A'
}
ConfigManager.prototype.getShapeShiftTxList = function () {
@@ -370,7 +341,7 @@ ConfigManager.prototype.createShapeShiftTx = function (depositAddress, depositTy
ConfigManager.prototype.getGasMultiplier = function () {
var data = this.getData()
- return ('gasMultiplier' in data) && data.gasMultiplier
+ return data.gasMultiplier
}
ConfigManager.prototype.setGasMultiplier = function (gasMultiplier) {
diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js
index 11bd5cc3a..066916b4d 100644
--- a/app/scripts/lib/inpage-provider.js
+++ b/app/scripts/lib/inpage-provider.js
@@ -1,7 +1,7 @@
-const Streams = require('mississippi')
+const pipe = require('pump')
const StreamProvider = require('web3-stream-provider')
+const LocalStorageStore = require('obs-store')
const ObjectMultiplex = require('./obj-multiplex')
-const RemoteStore = require('./remote-store.js').RemoteStore
const createRandomId = require('./random-id')
module.exports = MetamaskInpageProvider
@@ -10,33 +10,30 @@ function MetamaskInpageProvider (connectionStream) {
const self = this
// setup connectionStream multiplexing
- var multiStream = ObjectMultiplex()
- Streams.pipe(connectionStream, multiStream, connectionStream, function (err) {
- let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask'
- if (err) warningMsg += '\n' + err.stack
- console.warn(warningMsg)
- })
- self.multiStream = multiStream
-
- // subscribe to metamask public config
- var publicConfigStore = remoteStoreWithLocalStorageCache('MetaMask-Config')
- var storeStream = publicConfigStore.createStream()
- Streams.pipe(storeStream, multiStream.createStream('publicConfig'), storeStream, function (err) {
- let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask publicConfig'
- if (err) warningMsg += '\n' + err.stack
- console.warn(warningMsg)
- })
- self.publicConfigStore = publicConfigStore
+ var multiStream = self.multiStream = ObjectMultiplex()
+ pipe(
+ connectionStream,
+ multiStream,
+ connectionStream,
+ (err) => logStreamDisconnectWarning('MetaMask', err)
+ )
+
+ // subscribe to metamask public config (one-way)
+ self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' })
+ pipe(
+ multiStream.createStream('publicConfig'),
+ self.publicConfigStore,
+ (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err)
+ )
// connect to async provider
- var asyncProvider = new StreamProvider()
- Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function (err) {
- let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask provider'
- if (err) warningMsg += '\n' + err.stack
- console.warn(warningMsg)
- })
- asyncProvider.on('error', console.error.bind(console))
- self.asyncProvider = asyncProvider
+ const asyncProvider = self.asyncProvider = new StreamProvider()
+ pipe(
+ asyncProvider,
+ multiStream.createStream('provider'),
+ asyncProvider,
+ (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err)
+ )
self.idMap = {}
// handle sendAsync requests via asyncProvider
@@ -72,13 +69,13 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'eth_accounts':
// read from localStorage
- selectedAccount = self.publicConfigStore.get('selectedAccount')
+ selectedAccount = self.publicConfigStore.getState().selectedAccount
result = selectedAccount ? [selectedAccount] : []
break
case 'eth_coinbase':
// read from localStorage
- selectedAccount = self.publicConfigStore.get('selectedAccount')
+ selectedAccount = self.publicConfigStore.getState().selectedAccount
result = selectedAccount || '0x0000000000000000000000000000000000000000'
break
@@ -115,18 +112,6 @@ MetamaskInpageProvider.prototype.isMetaMask = true
// util
-function remoteStoreWithLocalStorageCache (storageKey) {
- // read local cache
- var initState = JSON.parse(localStorage[storageKey] || '{}')
- var store = new RemoteStore(initState)
- // cache the latest state locally
- store.subscribe(function (state) {
- localStorage[storageKey] = JSON.stringify(state)
- })
-
- return store
-}
-
function eachJsonMessage (payload, transformFn) {
if (Array.isArray(payload)) {
return payload.map(transformFn)
@@ -135,4 +120,10 @@ function eachJsonMessage (payload, transformFn) {
}
}
+function logStreamDisconnectWarning(remoteLabel, err){
+ let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}`
+ if (err) warningMsg += '\n' + err.stack
+ console.warn(warningMsg)
+}
+
function noop () {}
diff --git a/app/scripts/lib/migrations.js b/app/scripts/lib/migrations.js
deleted file mode 100644
index f026cbe53..000000000
--- a/app/scripts/lib/migrations.js
+++ /dev/null
@@ -1,5 +0,0 @@
-module.exports = [
- require('../migrations/002'),
- require('../migrations/003'),
- require('../migrations/004'),
-]
diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js
new file mode 100644
index 000000000..ab5a757b3
--- /dev/null
+++ b/app/scripts/lib/migrator/index.js
@@ -0,0 +1,40 @@
+const asyncQ = require('async-q')
+
+class Migrator {
+
+ constructor (opts = {}) {
+ let migrations = opts.migrations || []
+ this.migrations = migrations.sort((a, b) => a.version - b.version)
+ let lastMigration = this.migrations.slice(-1)[0]
+ // use specified defaultVersion or highest migration version
+ this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0
+ }
+
+ // run all pending migrations on meta in place
+ migrateData (versionedData = this.generateInitialState()) {
+ let remaining = this.migrations.filter(migrationIsPending)
+
+ return (
+ asyncQ.eachSeries(remaining, (migration) => migration.migrate(versionedData))
+ .then(() => versionedData)
+ )
+
+ // migration is "pending" if hit has a higher
+ // version number than currentVersion
+ function migrationIsPending(migration) {
+ return migration.version > versionedData.meta.version
+ }
+ }
+
+ generateInitialState (initState) {
+ return {
+ meta: {
+ version: this.defaultVersion,
+ },
+ data: initState,
+ }
+ }
+
+}
+
+module.exports = Migrator
diff --git a/app/scripts/lib/remote-store.js b/app/scripts/lib/remote-store.js
deleted file mode 100644
index fbfab7bad..000000000
--- a/app/scripts/lib/remote-store.js
+++ /dev/null
@@ -1,97 +0,0 @@
-const Dnode = require('dnode')
-const inherits = require('util').inherits
-
-module.exports = {
- HostStore: HostStore,
- RemoteStore: RemoteStore,
-}
-
-function BaseStore (initState) {
- this._state = initState || {}
- this._subs = []
-}
-
-BaseStore.prototype.set = function (key, value) {
- throw Error('Not implemented.')
-}
-
-BaseStore.prototype.get = function (key) {
- return this._state[key]
-}
-
-BaseStore.prototype.subscribe = function (fn) {
- this._subs.push(fn)
- var unsubscribe = this.unsubscribe.bind(this, fn)
- return unsubscribe
-}
-
-BaseStore.prototype.unsubscribe = function (fn) {
- var index = this._subs.indexOf(fn)
- if (index !== -1) this._subs.splice(index, 1)
-}
-
-BaseStore.prototype._emitUpdates = function (state) {
- this._subs.forEach(function (handler) {
- handler(state)
- })
-}
-
-//
-// host
-//
-
-inherits(HostStore, BaseStore)
-function HostStore (initState, opts) {
- BaseStore.call(this, initState)
-}
-
-HostStore.prototype.set = function (key, value) {
- this._state[key] = value
- process.nextTick(this._emitUpdates.bind(this, this._state))
-}
-
-HostStore.prototype.createStream = function () {
- var dnode = Dnode({
- // update: this._didUpdate.bind(this),
- })
- dnode.on('remote', this._didConnect.bind(this))
- return dnode
-}
-
-HostStore.prototype._didConnect = function (remote) {
- this.subscribe(function (state) {
- remote.update(state)
- })
- remote.update(this._state)
-}
-
-//
-// remote
-//
-
-inherits(RemoteStore, BaseStore)
-function RemoteStore (initState, opts) {
- BaseStore.call(this, initState)
- this._remote = null
-}
-
-RemoteStore.prototype.set = function (key, value) {
- this._remote.set(key, value)
-}
-
-RemoteStore.prototype.createStream = function () {
- var dnode = Dnode({
- update: this._didUpdate.bind(this),
- })
- dnode.once('remote', this._didConnect.bind(this))
- return dnode
-}
-
-RemoteStore.prototype._didConnect = function (remote) {
- this._remote = remote
-}
-
-RemoteStore.prototype._didUpdate = function (state) {
- this._state = state
- this._emitUpdates(state)
-}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 5c663255a..8f157a45e 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -1,13 +1,15 @@
const EventEmitter = require('events')
const extend = require('xtend')
const promiseToCallback = require('promise-to-callback')
+const pipe = require('pump')
+const ObservableStore = require('obs-store')
+const storeTransform = require('obs-store/lib/transform')
const EthStore = require('./lib/eth-store')
const MetaMaskProvider = require('web3-provider-engine/zero.js')
const KeyringController = require('./keyring-controller')
const NoticeController = require('./notice-controller')
const messageManager = require('./lib/message-manager')
const TxManager = require('./transaction-manager')
-const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3')
const ConfigManager = require('./lib/config-manager')
const extension = require('./lib/extension')
@@ -15,19 +17,30 @@ const autoFaucet = require('./lib/auto-faucet')
const nodeify = require('./lib/nodeify')
const IdStoreMigrator = require('./lib/idStore-migrator')
const accountImporter = require('./account-import-strategies')
+
const version = require('../manifest.json').version
module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
super()
- this.state = { network: 'loading' }
this.opts = opts
- this.configManager = new ConfigManager(opts)
+ this.state = { network: 'loading' }
+
+ // observable state store
+ this.store = new ObservableStore(opts.initState)
+ // config manager
+ this.configManager = new ConfigManager({
+ store: this.store,
+ })
+ // key mgmt
this.keyringController = new KeyringController({
configManager: this.configManager,
getNetwork: this.getStateNetwork.bind(this),
})
+ this.keyringController.on('newAccount', (account) => {
+ autoFaucet(account)
+ })
// notices
this.noticeController = new NoticeController({
configManager: this.configManager,
@@ -245,29 +258,23 @@ module.exports = class MetamaskController extends EventEmitter {
initPublicConfigStore () {
// get init state
- var initPublicState = configToPublic(this.configManager.getConfig())
- var publicConfigStore = new HostStore(initPublicState)
-
- // subscribe to changes
- this.configManager.subscribe(function (state) {
- storeSetFromObj(publicConfigStore, configToPublic(state))
- })
-
- this.keyringController.on('newAccount', (account) => {
- autoFaucet(account)
- })
-
- // config substate
- function configToPublic (state) {
- return {
- selectedAccount: state.selectedAccount,
+ const publicConfigStore = new ObservableStore()
+
+ // sync publicConfigStore with transform
+ pipe(
+ this.store,
+ storeTransform(selectPublicState),
+ publicConfigStore
+ )
+
+ function selectPublicState(state) {
+ const result = { selectedAccount: undefined }
+ try {
+ result.selectedAccount = state.config.selectedAccount
+ } catch (_) {
+ // thats fine, im sure it will be there next time...
}
- }
- // dump obj into store
- function storeSetFromObj (store, obj) {
- Object.keys(obj).forEach(function (key) {
- store.set(key, obj[key])
- })
+ return result
}
return publicConfigStore
@@ -310,9 +317,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.opts.showUnconfirmedMessage(msgParams, msgId)
}
- setupPublicConfig (stream) {
- var storeStream = this.publicConfigStore.createStream()
- stream.pipe(storeStream).pipe(stream)
+ setupPublicConfig (outStream) {
+ pipe(
+ this.publicConfigStore,
+ outStream
+ )
}
// Log blocks
diff --git a/app/scripts/migrations/002.js b/app/scripts/migrations/002.js
index 0b654f825..476b0a43a 100644
--- a/app/scripts/migrations/002.js
+++ b/app/scripts/migrations/002.js
@@ -1,13 +1,16 @@
+const version = 2
+
module.exports = {
- version: 2,
+ version,
- migrate: function (data) {
+ migrate: function (versionedData) {
+ versionedData.meta.version = version
try {
- if (data.config.provider.type === 'etherscan') {
- data.config.provider.type = 'rpc'
- data.config.provider.rpcTarget = 'https://rpc.metamask.io/'
+ if (versionedData.data.config.provider.type === 'etherscan') {
+ versionedData.data.config.provider.type = 'rpc'
+ versionedData.data.config.provider.rpcTarget = 'https://rpc.metamask.io/'
}
} catch (e) {}
- return data
+ return Promise.resolve(versionedData)
},
}
diff --git a/app/scripts/migrations/003.js b/app/scripts/migrations/003.js
index 617c55c09..eceaeaa4b 100644
--- a/app/scripts/migrations/003.js
+++ b/app/scripts/migrations/003.js
@@ -1,15 +1,17 @@
-var oldTestRpc = 'https://rawtestrpc.metamask.io/'
-var newTestRpc = 'https://testrpc.metamask.io/'
+const version = 3
+const oldTestRpc = 'https://rawtestrpc.metamask.io/'
+const newTestRpc = 'https://testrpc.metamask.io/'
module.exports = {
- version: 3,
+ version,
- migrate: function (data) {
+ migrate: function (versionedData) {
+ versionedData.meta.version = version
try {
- if (data.config.provider.rpcTarget === oldTestRpc) {
- data.config.provider.rpcTarget = newTestRpc
+ if (versionedData.data.config.provider.rpcTarget === oldTestRpc) {
+ versionedData.data.config.provider.rpcTarget = newTestRpc
}
} catch (e) {}
- return data
+ return Promise.resolve(versionedData)
},
}
diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js
index 1329a1eed..0f9850208 100644
--- a/app/scripts/migrations/004.js
+++ b/app/scripts/migrations/004.js
@@ -1,22 +1,25 @@
+const version = 4
+
module.exports = {
- version: 4,
+ version,
- migrate: function (data) {
+ migrate: function (versionedData) {
+ versionedData.meta.version = version
try {
- if (data.config.provider.type !== 'rpc') return data
- switch (data.config.provider.rpcTarget) {
+ if (versionedData.data.config.provider.type !== 'rpc') return Promise.resolve(versionedData)
+ switch (versionedData.data.config.provider.rpcTarget) {
case 'https://testrpc.metamask.io/':
- data.config.provider = {
+ versionedData.data.config.provider = {
type: 'testnet',
}
break
case 'https://rpc.metamask.io/':
- data.config.provider = {
+ versionedData.data.config.provider = {
type: 'mainnet',
}
break
}
} catch (_) {}
- return data
+ return Promise.resolve(versionedData)
},
}
diff --git a/app/scripts/migrations/_multi-keyring.js b/app/scripts/migrations/_multi-keyring.js
new file mode 100644
index 000000000..04c966d4d
--- /dev/null
+++ b/app/scripts/migrations/_multi-keyring.js
@@ -0,0 +1,51 @@
+const version = 5
+
+/*
+
+This is an incomplete migration bc it requires post-decrypted data
+which we dont have access to at the time of this writing.
+
+*/
+
+const ObservableStore = require('obs-store')
+const ConfigManager = require('../../app/scripts/lib/config-manager')
+const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator')
+const KeyringController = require('../../app/scripts/lib/keyring-controller')
+
+const password = 'obviously not correct'
+
+module.exports = {
+ version,
+
+ migrate: function (versionedData) {
+ versionedData.meta.version = version
+
+ let store = new ObservableStore(versionedData.data)
+ let configManager = new ConfigManager({ store })
+ let idStoreMigrator = new IdentityStoreMigrator({ configManager })
+ let keyringController = new KeyringController({
+ configManager: configManager,
+ })
+
+ // attempt to migrate to multiVault
+ return idStoreMigrator.migratedVaultForPassword(password)
+ .then((result) => {
+ // skip if nothing to migrate
+ if (!result) return Promise.resolve(versionedData)
+ delete versionedData.data.wallet
+ // create new keyrings
+ const privKeys = result.lostAccounts.map(acct => acct.privateKey)
+ return Promise.all([
+ keyringController.restoreKeyring(result.serialized),
+ keyringController.restoreKeyring({ type: 'Simple Key Pair', data: privKeys }),
+ ]).then(() => {
+ return keyringController.persistAllKeyrings(password)
+ }).then(() => {
+ // copy result on to state object
+ versionedData.data = store.get()
+ return Promise.resolve(versionedData)
+ })
+ })
+
+ },
+}
diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js
new file mode 100644
index 000000000..d2ac221b9
--- /dev/null
+++ b/app/scripts/migrations/index.js
@@ -0,0 +1,18 @@
+/* The migrator has two methods the user should be concerned with:
+ *
+ * getData(), which returns the app-consumable data object
+ * saveData(), which persists the app-consumable data object.
+ */
+
+// Migrations must start at version 1 or later.
+// They are objects with a `version` number
+// and a `migrate` function.
+//
+// The `migrate` function receives the previous
+// config data format, and returns the new one.
+
+module.exports = [
+ require('./002'),
+ require('./003'),
+ require('./004'),
+]
diff --git a/mock-dev.js b/mock-dev.js
index 283bc2c79..bd3a1ad77 100644
--- a/mock-dev.js
+++ b/mock-dev.js
@@ -15,97 +15,71 @@
const extend = require('xtend')
const render = require('react-dom').render
const h = require('react-hyperscript')
+const pipe = require('mississippi').pipe
+const LocalStorageStore = require('obs-store/lib/localStorage')
const Root = require('./ui/app/root')
const configureStore = require('./ui/app/store')
const actions = require('./ui/app/actions')
const states = require('./development/states')
const Selector = require('./development/selector')
const MetamaskController = require('./app/scripts/metamask-controller')
+const firstTimeState = require('./app/scripts/first-time-state')
const extension = require('./development/mockExtension')
+const noop = function () {}
+
+const STORAGE_KEY = 'metamask-config'
+//
// Query String
+//
+
const qs = require('qs')
let queryString = qs.parse(window.location.href.split('#')[1])
let selectedView = queryString.view || 'first time'
const firstState = states[selectedView]
updateQueryParams(selectedView)
+function updateQueryParams(newView) {
+ queryString.view = newView
+ const params = qs.stringify(queryString)
+ window.location.href = window.location.href.split('#')[0] + `#${params}`
+}
+
+//
// CSS
+//
+
const MetaMaskUiCss = require('./ui/css')
const injectCss = require('inject-css')
+//
+// MetaMask Controller
+//
-function updateQueryParams(newView) {
- queryString.view = newView
- const params = qs.stringify(queryString)
- window.location.href = window.location.href.split('#')[0] + `#${params}`
+let dataStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
+// initial state for first time users
+if (!dataStore.getState()) {
+ dataStore.putState(firstTimeState)
}
-const noop = function () {}
const controller = new MetamaskController({
// User confirmation callbacks:
showUnconfirmedMessage: noop,
unlockAccountMessage: noop,
showUnapprovedTx: noop,
- // Persistence Methods:
- setData,
- loadData,
+ // initial state
+ initState: dataStore.getState(),
})
-// Stub out localStorage for non-browser environments
-if (!window.localStorage) {
- window.localStorage = {}
-}
-const STORAGE_KEY = 'metamask-config'
-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
-}
+// setup state persistence
+pipe(
+ controller.store,
+ dataStore
+)
-function setData (data) {
- window.localStorage[STORAGE_KEY] = JSON.stringify(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
-}
+//
+// User Interface
+//
actions._setBackgroundConnection(controller.getApi())
actions.update = function(stateName) {
diff --git a/package.json b/package.json
index 569cab2a0..67bd3b209 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
},
"dependencies": {
"async": "^1.5.2",
+ "async-q": "^0.3.1",
"bip39": "^2.2.0",
"browser-passworder": "^2.0.3",
"browserify-derequire": "^0.9.4",
@@ -69,6 +70,7 @@
"mississippi": "^1.2.0",
"mkdirp": "^0.5.1",
"multiplex": "^6.7.0",
+ "obs-store": "^2.2.3",
"once": "^1.3.3",
"ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0",
@@ -76,6 +78,7 @@
"post-message-stream": "^1.0.0",
"promise-filter": "^1.1.0",
"promise-to-callback": "^1.0.0",
+ "pump": "^1.0.2",
"pumpify": "^1.3.4",
"qrcode-npm": "0.0.3",
"react": "^15.0.2",
diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js
index 4ae30411d..f2a437a7c 100644
--- a/test/integration/lib/idStore-migrator-test.js
+++ b/test/integration/lib/idStore-migrator-test.js
@@ -1,30 +1,23 @@
-var ConfigManager = require('../../../app/scripts/lib/config-manager')
-var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
-var SimpleKeyring = require('../../../app/scripts/keyrings/simple')
-var normalize = require('../../../app/scripts/lib/sig-util').normalize
+const ObservableStore = require('obs-store')
+const ConfigManager = require('../../../app/scripts/lib/config-manager')
+const IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
+const SimpleKeyring = require('../../../app/scripts/keyrings/simple')
+const normalize = require('../../../app/scripts/lib/sig-util').normalize
-var oldStyleVault = require('../mocks/oldVault.json')
-var badStyleVault = require('../mocks/badVault.json')
+const oldStyleVault = require('../mocks/oldVault.json').data
+const badStyleVault = require('../mocks/badVault.json').data
-var STORAGE_KEY = 'metamask-config'
-var PASSWORD = '12345678'
-var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
-var SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner'
-
-var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
+const PASSWORD = '12345678'
+const FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
+const BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
+const SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner'
QUnit.module('Old Style Vaults', {
beforeEach: function () {
- window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault)
-
- this.configManager = new ConfigManager({
- loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
- setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
- })
-
- this.migrator = new IdStoreMigrator({
- configManager: this.configManager,
- })
+ let managers = managersFromInitState(oldStyleVault)
+
+ this.configManager = managers.configManager
+ this.migrator = managers.migrator
}
})
@@ -37,6 +30,7 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
this.migrator.migratedVaultForPassword(PASSWORD)
.then((result) => {
+ assert.ok(result, 'migratedVaultForPassword returned result')
const { serialized, lostAccounts } = result
assert.equal(serialized.data.mnemonic, SEED, 'seed phrase recovered')
assert.equal(lostAccounts.length, 0, 'no lost accounts')
@@ -46,16 +40,10 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
QUnit.module('Old Style Vaults with bad HD seed', {
beforeEach: function () {
- window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault)
-
- this.configManager = new ConfigManager({
- loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
- setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
- })
-
- this.migrator = new IdStoreMigrator({
- configManager: this.configManager,
- })
+ let managers = managersFromInitState(badStyleVault)
+
+ this.configManager = managers.configManager
+ this.migrator = managers.migrator
}
})
@@ -64,6 +52,7 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
this.migrator.migratedVaultForPassword(PASSWORD)
.then((result) => {
+ assert.ok(result, 'migratedVaultForPassword returned result')
const { serialized, lostAccounts } = result
assert.equal(lostAccounts.length, 1, 'one lost account')
@@ -89,3 +78,15 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
})
})
+function managersFromInitState(initState){
+
+ let configManager = new ConfigManager({
+ store: new ObservableStore(initState),
+ })
+
+ let migrator = new IdStoreMigrator({
+ configManager: configManager,
+ })
+
+ return { configManager, migrator }
+} \ No newline at end of file
diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js
index b79f63090..72be86ed1 100644
--- a/test/lib/mock-config-manager.js
+++ b/test/lib/mock-config-manager.js
@@ -1,58 +1,10 @@
-var ConfigManager = require('../../app/scripts/lib/config-manager')
+const ObservableStore = require('obs-store')
+const clone = require('clone')
+const ConfigManager = require('../../app/scripts/lib/config-manager')
+const firstTimeState = require('../../app/scripts/first-time-state')
const STORAGE_KEY = 'metamask-config'
-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)
-}
+ let store = new ObservableStore(clone(firstTimeState))
+ return new ConfigManager({ store })
+} \ No newline at end of file
diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js
index 77d431d5f..fa3929599 100644
--- a/test/unit/config-manager-test.js
+++ b/test/unit/config-manager-test.js
@@ -1,24 +1,23 @@
// polyfill fetch
global.fetch = global.fetch || require('isomorphic-fetch')
+
const assert = require('assert')
const extend = require('xtend')
const rp = require('request-promise')
const nock = require('nock')
const configManagerGen = require('../lib/mock-config-manager')
-const STORAGE_KEY = 'metamask-persistance-key'
describe('config-manager', function() {
var configManager
beforeEach(function() {
- window.localStorage = {} // Hacking localStorage support into JSDom
configManager = configManagerGen()
})
describe('currency conversions', function() {
describe('#getCurrentFiat', function() {
- it('should return false if no previous key exists', function() {
+ it('should return undefined if no previous key exists', function() {
var result = configManager.getCurrentFiat()
assert.ok(!result)
})
@@ -26,14 +25,14 @@ describe('config-manager', function() {
describe('#setCurrentFiat', function() {
it('should make getCurrentFiat return true once set', function() {
- assert.equal(configManager.getCurrentFiat(), false)
+ assert.equal(configManager.getCurrentFiat(), undefined)
configManager.setCurrentFiat('USD')
var result = configManager.getCurrentFiat()
assert.equal(result, 'USD')
})
it('should work with other currencies as well', function() {
- assert.equal(configManager.getCurrentFiat(), false)
+ assert.equal(configManager.getCurrentFiat(), undefined)
configManager.setCurrentFiat('JPY')
var result = configManager.getCurrentFiat()
assert.equal(result, 'JPY')
@@ -41,7 +40,7 @@ describe('config-manager', function() {
})
describe('#getConversionRate', function() {
- it('should return false if non-existent', function() {
+ it('should return undefined if non-existent', function() {
var result = configManager.getConversionRate()
assert.ok(!result)
})
@@ -54,7 +53,7 @@ describe('config-manager', function() {
.get('/api/ticker/eth-USD')
.reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
- assert.equal(configManager.getConversionRate(), false)
+ assert.equal(configManager.getConversionRate(), 0)
var promise = new Promise(
function (resolve, reject) {
configManager.setCurrentFiat('USD')
@@ -75,7 +74,7 @@ describe('config-manager', function() {
it('should work for JPY as well.', function() {
this.timeout(15000)
- assert.equal(configManager.getConversionRate(), false)
+ assert.equal(configManager.getConversionRate(), 0)
var jpyMock = nock('https://www.cryptonator.com')
.get('/api/ticker/eth-JPY')
@@ -103,7 +102,7 @@ describe('config-manager', function() {
describe('confirmation', function() {
describe('#getConfirmedDisclaimer', function() {
- it('should return false if no previous key exists', function() {
+ it('should return undefined if no previous key exists', function() {
var result = configManager.getConfirmedDisclaimer()
assert.ok(!result)
})
@@ -111,16 +110,16 @@ describe('config-manager', function() {
describe('#setConfirmedDisclaimer', function() {
it('should make getConfirmedDisclaimer return true once set', function() {
- assert.equal(configManager.getConfirmedDisclaimer(), false)
+ assert.equal(configManager.getConfirmedDisclaimer(), undefined)
configManager.setConfirmedDisclaimer(true)
var result = configManager.getConfirmedDisclaimer()
assert.equal(result, true)
})
- it('should be able to set false', function() {
- configManager.setConfirmedDisclaimer(false)
+ it('should be able to set undefined', function() {
+ configManager.setConfirmedDisclaimer(undefined)
var result = configManager.getConfirmedDisclaimer()
- assert.equal(result, false)
+ assert.equal(result, undefined)
})
it('should persist to local storage', function() {
@@ -132,7 +131,6 @@ describe('config-manager', function() {
})
describe('#setConfig', function() {
- window.localStorage = {} // Hacking localStorage support into JSDom
it('should set the config key', function () {
var testConfig = {
diff --git a/test/unit/idStore-migration-test.js b/test/unit/idStore-migration-test.js
index 54f38fb2f..38667fc3e 100644
--- a/test/unit/idStore-migration-test.js
+++ b/test/unit/idStore-migration-test.js
@@ -1,5 +1,6 @@
const async = require('async')
const assert = require('assert')
+const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const ConfigManager = require('../../app/scripts/lib/config-manager')
@@ -42,10 +43,9 @@ describe('IdentityStore to KeyringController migration', function() {
beforeEach(function(done) {
this.sinon = sinon.sandbox.create()
window.localStorage = {} // Hacking localStorage support into JSDom
- configManager = new ConfigManager({
- loadData,
- setData: (d) => { window.localStorage = d }
- })
+ let store = new ObservableStore(loadData())
+ store.subscribe(setData)
+ configManager = new ConfigManager({ store })
idStore = new IdentityStore({
diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js
index a6164c9a0..24d9ddd67 100644
--- a/test/unit/metamask-controller-test.js
+++ b/test/unit/metamask-controller-test.js
@@ -10,9 +10,11 @@ describe('MetaMaskController', function() {
showUnconfirmedMessage: noop,
unlockAccountMessage: noop,
showUnapprovedTx: noop,
- setData,
- loadData,
+ // initial state
+ initState: loadData(),
})
+ // setup state persistence
+ controller.store.subscribe(setData)
beforeEach(function() {
// sinon allows stubbing methods that are easily verified
diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js
index 9ea8d5c5a..715a5feb0 100644
--- a/test/unit/migrations-test.js
+++ b/test/unit/migrations-test.js
@@ -1,34 +1,34 @@
-var assert = require('assert')
-var path = require('path')
+const assert = require('assert')
+const path = require('path')
-var wallet1 = require(path.join('..', 'lib', 'migrations', '001.json'))
+const wallet1 = require(path.join('..', 'lib', 'migrations', '001.json'))
-var migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002'))
-var migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003'))
-var migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004'))
+const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002'))
+const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003'))
+const migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004'))
-describe('wallet1 is migrated successfully', function() {
+const oldTestRpc = 'https://rawtestrpc.metamask.io/'
+const newTestRpc = 'https://testrpc.metamask.io/'
- it('should convert providers', function(done) {
+describe('wallet1 is migrated successfully', function() {
+ it('should convert providers', function() {
wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null }
- var firstResult = migration2.migrate(wallet1.data)
- assert.equal(firstResult.config.provider.type, 'rpc', 'provider should be rpc')
- assert.equal(firstResult.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc')
-
- var oldTestRpc = 'https://rawtestrpc.metamask.io/'
- var newTestRpc = 'https://testrpc.metamask.io/'
- firstResult.config.provider.rpcTarget = oldTestRpc
-
- var secondResult = migration3.migrate(firstResult)
- assert.equal(secondResult.config.provider.rpcTarget, newTestRpc)
-
- var thirdResult = migration4.migrate(secondResult)
- assert.equal(secondResult.config.provider.rpcTarget, null)
- assert.equal(secondResult.config.provider.type, 'testnet')
-
- done()
+ return migration2.migrate(wallet1)
+ .then((firstResult) => {
+ assert.equal(firstResult.data.config.provider.type, 'rpc', 'provider should be rpc')
+ assert.equal(firstResult.data.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc')
+ firstResult.data.config.provider.rpcTarget = oldTestRpc
+ return migration3.migrate(firstResult)
+ }).then((secondResult) => {
+ assert.equal(secondResult.data.config.provider.rpcTarget, newTestRpc)
+ return migration4.migrate(secondResult)
+ }).then((thirdResult) => {
+ assert.equal(thirdResult.data.config.provider.rpcTarget, null)
+ assert.equal(thirdResult.data.config.provider.type, 'testnet')
+ })
+
})
})