aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/lib
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/lib')
-rw-r--r--app/scripts/lib/config-manager.js53
-rw-r--r--app/scripts/lib/inpage-provider.js18
-rw-r--r--app/scripts/lib/migrations.js5
-rw-r--r--app/scripts/lib/migrator/index.js40
-rw-r--r--app/scripts/lib/observable/host.js50
-rw-r--r--app/scripts/lib/observable/index.js41
-rw-r--r--app/scripts/lib/observable/local-storage.js37
-rw-r--r--app/scripts/lib/observable/remote.js51
-rw-r--r--app/scripts/lib/observable/util/sync.js24
-rw-r--r--app/scripts/lib/remote-store.js97
10 files changed, 269 insertions, 147 deletions
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index e927c78ec..daba8bc7b 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.store.get()
data.config = config
this.setData(data)
this._emitUpdates(config)
}
ConfigManager.prototype.getConfig = function () {
- var data = this.migrator.getData()
+ var data = this.store.get()
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.put(data)
}
ConfigManager.prototype.getData = function () {
- return this.migrator.getData()
+ return this.store.get()
}
ConfigManager.prototype.setWallet = function (wallet) {
- var data = this.migrator.getData()
+ var data = this.store.get()
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.store.get().keychains || []
}
ConfigManager.prototype.setKeychains = function (keychains) {
- var data = this.migrator.getData()
+ var data = this.store.get()
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.store.get().wallet
}
// Takes a boolean
ConfigManager.prototype.setShowSeedWords = function (should) {
- var data = this.migrator.getData()
+ var data = this.store.get()
data.showSeedWords = should
this.setData(data)
}
ConfigManager.prototype.getShouldShowSeedWords = function () {
- var data = this.migrator.getData()
+ var data = this.store.get()
return data.showSeedWords
}
@@ -189,7 +164,7 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
}
ConfigManager.prototype.setData = function (data) {
- this.migrator.saveData(data)
+ this.store.put(data)
}
//
@@ -197,7 +172,7 @@ ConfigManager.prototype.setData = function (data) {
//
ConfigManager.prototype.getTxList = function () {
- var data = this.migrator.getData()
+ var data = this.store.get()
if (data.transactions !== undefined) {
return data.transactions
} else {
@@ -206,7 +181,7 @@ ConfigManager.prototype.getTxList = function () {
}
ConfigManager.prototype.setTxList = function (txList) {
- var data = this.migrator.getData()
+ var data = this.store.get()
data.transactions = txList
this.setData(data)
}
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/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/observable/host.js b/app/scripts/lib/observable/host.js
new file mode 100644
index 000000000..d1b110503
--- /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..1ff112e95
--- /dev/null
+++ b/app/scripts/lib/observable/index.js
@@ -0,0 +1,41 @@
+const EventEmitter = require('events').EventEmitter
+
+class ObservableStore extends EventEmitter {
+
+ constructor (initialState) {
+ super()
+ this._state = initialState
+ }
+
+ // wrapper around internal get
+ get () {
+ return this._state
+ }
+
+ // wrapper around internal put
+ put (newState) {
+ this._put(newState)
+ }
+
+ // subscribe to changes
+ subscribe (handler) {
+ this.on('update', handler)
+ }
+
+ // unsubscribe to changes
+ unsubscribe (handler) {
+ this.removeListener('update', handler)
+ }
+
+ //
+ // private
+ //
+
+ _put (newState) {
+ this._state = newState
+ this.emit('update', newState)
+ }
+
+}
+
+module.exports = ObservableStore
diff --git a/app/scripts/lib/observable/local-storage.js b/app/scripts/lib/observable/local-storage.js
new file mode 100644
index 000000000..6ed3860f6
--- /dev/null
+++ b/app/scripts/lib/observable/local-storage.js
@@ -0,0 +1,37 @@
+const ObservableStore = require('./index')
+
+//
+// LocalStorageStore
+//
+// uses localStorage instead of a cache
+//
+
+class LocalStorageStore extends ObservableStore {
+
+ constructor (opts) {
+ super()
+ delete this._state
+
+ this._opts = opts || {}
+ if (!this._opts.storageKey) {
+ throw new Error('LocalStorageStore - no "storageKey" specified')
+ }
+ this._storageKey = this._opts.storageKey
+ }
+
+ get() {
+ try {
+ return JSON.parse(global.localStorage[this._storageKey])
+ } catch (err) {
+ return undefined
+ }
+ }
+
+ _put(newState) {
+ global.localStorage[this._storageKey] = JSON.stringify(newState)
+ this.emit('update', newState)
+ }
+
+}
+
+module.exports = LocalStorageStore
diff --git a/app/scripts/lib/observable/remote.js b/app/scripts/lib/observable/remote.js
new file mode 100644
index 000000000..603f6f0b8
--- /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/sync.js b/app/scripts/lib/observable/util/sync.js
new file mode 100644
index 000000000..c61feb02e
--- /dev/null
+++ b/app/scripts/lib/observable/util/sync.js
@@ -0,0 +1,24 @@
+
+//
+// synchronizeStore(inStore, outStore, stateTransform)
+//
+// keeps outStore synchronized with inStore, via an optional stateTransform
+//
+
+module.exports = synchronizeStore
+
+
+function synchronizeStore(inStore, outStore, stateTransform) {
+ stateTransform = stateTransform || transformNoop
+ const initState = stateTransform(inStore.get())
+ outStore.put(initState)
+ inStore.subscribe((inState) => {
+ const outState = stateTransform(inState)
+ outStore.put(outState)
+ })
+ return outStore
+}
+
+function transformNoop(state) {
+ return state
+} \ 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)
-}