aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/scripts/background.js45
-rw-r--r--app/scripts/lib/config-manager.js10
-rw-r--r--app/scripts/lib/inpage-provider.js18
-rw-r--r--app/scripts/lib/observable/host.js50
-rw-r--r--app/scripts/lib/observable/index.js33
-rw-r--r--app/scripts/lib/observable/remote.js51
-rw-r--r--app/scripts/lib/observable/util/transform.js13
-rw-r--r--app/scripts/lib/remote-store.js97
-rw-r--r--app/scripts/metamask-controller.js53
-rw-r--r--mock-dev.js8
-rw-r--r--test/integration/lib/idStore-migrator-test.js33
11 files changed, 224 insertions, 187 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 6b7926526..f3c0b52b3 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -13,15 +13,18 @@ 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,
+ // initial state
+ initState: loadData(),
})
+// setup state persistence
+controller.store.subscribe(setData)
+
const txManager = controller.txManager
function triggerUi () {
if (!popupIsOpen) notification.show()
@@ -112,13 +115,7 @@ function updateBadge () {
// data :: setters/getters
function loadData () {
- var oldData = getOldStyleData()
- var newData
- try {
- newData = JSON.parse(window.localStorage[STORAGE_KEY])
- } catch (e) {}
-
- var data = extend({
+ let defaultData = {
meta: {
version: 0,
},
@@ -129,32 +126,16 @@ function loadData () {
},
},
},
- }, oldData || null, newData || null)
- return data
-}
-
-function getOldStyleData () {
- var config, wallet, seedWords
-
- var result = {
- meta: { version: 0 },
- data: {},
}
+ var persisted
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) {}
+ persisted = JSON.parse(window.localStorage[STORAGE_KEY])
+ } catch (err) {
+ persisted = null
+ }
- return result
+ return extend(defaultData, persisted)
}
function setData (data) {
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index 3a1f12ac0..01e6ccc3c 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -19,6 +19,7 @@ module.exports = ConfigManager
function ConfigManager (opts) {
// ConfigManager is observable and will emit updates
this._subs = []
+ this.store = opts.store
/* The migrator exported on the config-manager
* has two methods the user should be concerned with:
@@ -36,12 +37,9 @@ function ConfigManager (opts) {
// 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,
+ // Data persistence methods
+ loadData: () => this.store.get(),
+ setData: (value) => this.store.put(value),
})
}
diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js
index 11bd5cc3a..64301be78 100644
--- a/app/scripts/lib/inpage-provider.js
+++ b/app/scripts/lib/inpage-provider.js
@@ -1,7 +1,7 @@
const Streams = require('mississippi')
const StreamProvider = require('web3-stream-provider')
const ObjectMultiplex = require('./obj-multiplex')
-const RemoteStore = require('./remote-store.js').RemoteStore
+const RemoteStore = require('./observable/remote')
const createRandomId = require('./random-id')
module.exports = MetamaskInpageProvider
@@ -72,13 +72,13 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'eth_accounts':
// read from localStorage
- selectedAccount = self.publicConfigStore.get('selectedAccount')
+ selectedAccount = self.publicConfigStore.get().selectedAccount
result = selectedAccount ? [selectedAccount] : []
break
case 'eth_coinbase':
// read from localStorage
- selectedAccount = self.publicConfigStore.get('selectedAccount')
+ selectedAccount = self.publicConfigStore.get().selectedAccount
result = selectedAccount || '0x0000000000000000000000000000000000000000'
break
@@ -117,9 +117,15 @@ MetamaskInpageProvider.prototype.isMetaMask = true
function remoteStoreWithLocalStorageCache (storageKey) {
// read local cache
- var initState = JSON.parse(localStorage[storageKey] || '{}')
- var store = new RemoteStore(initState)
- // cache the latest state locally
+ let initState
+ try {
+ initState = JSON.parse(localStorage[storageKey] || '{}')
+ } catch (err) {
+ initState = {}
+ }
+ // intialize store
+ const store = new RemoteStore(initState)
+ // write local cache
store.subscribe(function (state) {
localStorage[storageKey] = JSON.stringify(state)
})
diff --git a/app/scripts/lib/observable/host.js b/app/scripts/lib/observable/host.js
new file mode 100644
index 000000000..69f674be8
--- /dev/null
+++ b/app/scripts/lib/observable/host.js
@@ -0,0 +1,50 @@
+const Dnode = require('dnode')
+const ObservableStore = require('./index')
+const endOfStream = require('end-of-stream')
+
+//
+// HostStore
+//
+// plays host to many RemoteStores and sends its state over a stream
+//
+
+class HostStore extends ObservableStore {
+
+ constructor (initState, opts) {
+ super(initState)
+ this.opts = opts || {}
+ }
+
+ createStream () {
+ const self = this
+ // setup remotely exposed api
+ let remoteApi = {}
+ if (!self.opts.readOnly) {
+ remoteApi.put = (newState) => self.put(newState)
+ }
+ // listen for connection to remote
+ const dnode = Dnode(remoteApi)
+ dnode.on('remote', (remote) => {
+ // setup update subscription lifecycle
+ const updateHandler = (state) => remote.put(state)
+ self._onConnect(updateHandler)
+ endOfStream(dnode, () => self._onDisconnect(updateHandler))
+ })
+ return dnode
+ }
+
+ _onConnect (updateHandler) {
+ // subscribe to updates
+ this.subscribe(updateHandler)
+ // send state immediately
+ updateHandler(this.get())
+ }
+
+ _onDisconnect (updateHandler) {
+ // unsubscribe to updates
+ this.unsubscribe(updateHandler)
+ }
+
+}
+
+module.exports = HostStore
diff --git a/app/scripts/lib/observable/index.js b/app/scripts/lib/observable/index.js
new file mode 100644
index 000000000..d193e5554
--- /dev/null
+++ b/app/scripts/lib/observable/index.js
@@ -0,0 +1,33 @@
+const EventEmitter = require('events').EventEmitter
+
+class ObservableStore extends EventEmitter {
+
+ constructor (initialState) {
+ super()
+ this._state = initialState
+ }
+
+ get () {
+ return this._state
+ }
+
+ put (newState) {
+ this._put(newState)
+ }
+
+ subscribe (handler) {
+ this.on('update', handler)
+ }
+
+ unsubscribe (handler) {
+ this.removeListener('update', handler)
+ }
+
+ _put (newState) {
+ this._state = newState
+ this.emit('update', newState)
+ }
+
+}
+
+module.exports = ObservableStore
diff --git a/app/scripts/lib/observable/remote.js b/app/scripts/lib/observable/remote.js
new file mode 100644
index 000000000..b5a3254a2
--- /dev/null
+++ b/app/scripts/lib/observable/remote.js
@@ -0,0 +1,51 @@
+const Dnode = require('dnode')
+const ObservableStore = require('./index')
+const endOfStream = require('end-of-stream')
+
+//
+// RemoteStore
+//
+// connects to a HostStore and receives its latest state
+//
+
+class RemoteStore extends ObservableStore {
+
+ constructor (initState, opts) {
+ super(initState)
+ this.opts = opts || {}
+ this._remote = null
+ }
+
+ put (newState) {
+ if (!this._remote) throw new Error('RemoteStore - "put" called before connection to HostStore')
+ this._put(newState)
+ this._remote.put(newState)
+ }
+
+ createStream () {
+ const self = this
+ const dnode = Dnode({
+ put: (newState) => self._put(newState),
+ })
+ // listen for connection to remote
+ dnode.once('remote', (remote) => {
+ // setup connection lifecycle
+ self._onConnect(remote)
+ endOfStream(dnode, () => self._onDisconnect())
+ })
+ return dnode
+ }
+
+ _onConnect (remote) {
+ this._remote = remote
+ this.emit('connected')
+ }
+
+ _onDisconnect () {
+ this._remote = null
+ this.emit('disconnected')
+ }
+
+}
+
+module.exports = RemoteStore \ No newline at end of file
diff --git a/app/scripts/lib/observable/util/transform.js b/app/scripts/lib/observable/util/transform.js
new file mode 100644
index 000000000..87946f402
--- /dev/null
+++ b/app/scripts/lib/observable/util/transform.js
@@ -0,0 +1,13 @@
+
+module.exports = transformStore
+
+
+function transformStore(inStore, outStore, stateTransform) {
+ const initState = stateTransform(inStore.get())
+ outStore.put(initState)
+ inStore.subscribe((inState) => {
+ const outState = stateTransform(inState)
+ outStore.put(outState)
+ })
+ return outStore
+} \ No newline at end of file
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 1fc97e81d..8e0eaf54c 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -6,26 +6,38 @@ 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')
const autoFaucet = require('./lib/auto-faucet')
const nodeify = require('./lib/nodeify')
const IdStoreMigrator = require('./lib/idStore-migrator')
+const ObservableStore = require('./lib/observable/')
+const HostStore = require('./lib/observable/host')
+const transformStore = require('./lib/observable/util/transform')
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,
@@ -228,29 +240,20 @@ 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,
+ var initPublicState = this.store.get()
+ var publicConfigStore = new HostStore(initPublicState, { readOnly: true })
+
+ // sync publicConfigStore with transform
+ transformStore(this.store, publicConfigStore, selectPublicState)
+
+ function selectPublicState(state) {
+ let result = { selectedAccount: undefined }
+ try {
+ result.selectedAccount = state.data.config.selectedAccount
+ } catch (err) {
+ console.warn('Error in "selectPublicState": ' + err.message)
}
- }
- // dump obj into store
- function storeSetFromObj (store, obj) {
- Object.keys(obj).forEach(function (key) {
- store.set(key, obj[key])
- })
+ return result
}
return publicConfigStore
diff --git a/mock-dev.js b/mock-dev.js
index 283bc2c79..dfd0b4961 100644
--- a/mock-dev.js
+++ b/mock-dev.js
@@ -47,11 +47,13 @@ const controller = new MetamaskController({
showUnconfirmedMessage: noop,
unlockAccountMessage: noop,
showUnapprovedTx: noop,
- // Persistence Methods:
- setData,
- loadData,
+ // initial state
+ initState: loadData(),
})
+// setup state persistence
+controller.store.subscribe(setData)
+
// Stub out localStorage for non-browser environments
if (!window.localStorage) {
window.localStorage = {}
diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js
index 4ae30411d..1ceaac442 100644
--- a/test/integration/lib/idStore-migrator-test.js
+++ b/test/integration/lib/idStore-migrator-test.js
@@ -1,25 +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('../../../app/scripts/lib/observable/')
+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')
+const badStyleVault = require('../mocks/badVault.json')
-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)
+ let store = new ObservableStore(oldStyleVault)
this.configManager = new ConfigManager({
- loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
- setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
+ store: store,
})
this.migrator = new IdStoreMigrator({
@@ -46,11 +44,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)
+ let store = new ObservableStore(badStyleVault)
this.configManager = new ConfigManager({
- loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
- setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
+ store: store,
})
this.migrator = new IdStoreMigrator({