From 8012ede12698477692b80769781096b559159a32 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 11 Jan 2017 19:04:19 -0800 Subject: background - introduce ObservableStore --- app/scripts/background.js | 45 ++++--------- app/scripts/lib/config-manager.js | 10 ++- app/scripts/lib/inpage-provider.js | 18 ++++-- app/scripts/lib/observable/host.js | 50 ++++++++++++++ app/scripts/lib/observable/index.js | 33 ++++++++++ app/scripts/lib/observable/remote.js | 51 +++++++++++++++ app/scripts/lib/observable/util/transform.js | 13 ++++ app/scripts/lib/remote-store.js | 97 ---------------------------- app/scripts/metamask-controller.js | 53 ++++++++------- 9 files changed, 204 insertions(+), 166 deletions(-) create mode 100644 app/scripts/lib/observable/host.js create mode 100644 app/scripts/lib/observable/index.js create mode 100644 app/scripts/lib/observable/remote.js create mode 100644 app/scripts/lib/observable/util/transform.js delete mode 100644 app/scripts/lib/remote-store.js (limited to 'app/scripts') 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 -- cgit v1.2.3 From 3bc996878b467e1fa5fd63656bd465377daa137d Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 11 Jan 2017 22:47:56 -0800 Subject: background - move pojo migrator to outside of metamask controller --- app/scripts/background.js | 94 ++++++++++++++-------------- app/scripts/first-time-state.js | 11 ++++ app/scripts/lib/config-manager.js | 49 ++++----------- app/scripts/lib/migrations.js | 13 ++++ app/scripts/lib/observable/host.js | 4 +- app/scripts/lib/observable/index.js | 10 ++- app/scripts/lib/observable/local-storage.js | 37 +++++++++++ app/scripts/lib/observable/remote.js | 2 +- app/scripts/lib/observable/util/sync.js | 24 +++++++ app/scripts/lib/observable/util/transform.js | 13 ---- app/scripts/metamask-controller.js | 6 +- 11 files changed, 159 insertions(+), 104 deletions(-) create mode 100644 app/scripts/first-time-state.js create mode 100644 app/scripts/lib/observable/local-storage.js create mode 100644 app/scripts/lib/observable/util/sync.js delete mode 100644 app/scripts/lib/observable/util/transform.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index f3c0b52b3..8aa886594 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,41 +1,57 @@ const urlUtil = require('url') -const extend = require('xtend') const Dnode = require('dnode') const eos = require('end-of-stream') +const Migrator = require('pojo-migrator') +const migrations = require('./lib/migrations') +const LocalStorageStore = require('./lib/observable/local-storage') 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 +let popupIsOpen = false +// +// State and Persistence +// + +// state persistence + +let dataStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) +// initial state for first time users +if (!dataStore.get()) { + dataStore.put({ meta: { version: 0 }, data: firstTimeState }) +} + +// migrations + +let migrator = new Migrator({ + migrations, + // Data persistence methods + loadData: () => dataStore.get(), + setData: (newState) => dataStore.put(newState), +}) + +// +// MetaMask Controller +// + const controller = new MetamaskController({ // User confirmation callbacks: showUnconfirmedMessage: triggerUi, unlockAccountMessage: triggerUi, showUnapprovedTx: triggerUi, // initial state - initState: loadData(), + initState: migrator.getData(), }) // setup state persistence -controller.store.subscribe(setData) - -const txManager = controller.txManager -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'}) - } -}) +controller.store.subscribe((newState) => migrator.saveData(newState)) // // connect to other contexts @@ -94,11 +110,23 @@ function setupControllerConnection (stream) { } // -// plugin badge text +// User Interface setup // -txManager.on('updateBadge', updateBadge) +// 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'}) + } +}) + +// plugin badge text +controller.txManager.on('updateBadge', updateBadge) function updateBadge () { var label = '' var unapprovedTxCount = controller.txManager.unapprovedTxCount @@ -111,33 +139,3 @@ function updateBadge () { extension.browserAction.setBadgeText({ text: label }) extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' }) } - -// data :: setters/getters - -function loadData () { - let defaultData = { - meta: { - version: 0, - }, - data: { - config: { - provider: { - type: 'testnet', - }, - }, - }, - } - - var persisted - try { - persisted = JSON.parse(window.localStorage[STORAGE_KEY]) - } catch (err) { - persisted = null - } - - return extend(defaultData, persisted) -} - -function setData (data) { - window.localStorage[STORAGE_KEY] = JSON.stringify(data) -} 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 01e6ccc3c..6d7305377 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 @@ -20,38 +18,17 @@ 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: - * - * 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, - - // Data persistence methods - loadData: () => this.store.get(), - setData: (value) => this.store.put(value), - }) } 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 { @@ -94,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) } @@ -119,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) } @@ -140,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 } @@ -187,7 +164,7 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { } ConfigManager.prototype.setData = function (data) { - this.migrator.saveData(data) + this.store.put(data) } // @@ -195,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 { @@ -204,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/migrations.js b/app/scripts/lib/migrations.js index f026cbe53..12f60def1 100644 --- a/app/scripts/lib/migrations.js +++ b/app/scripts/lib/migrations.js @@ -1,3 +1,16 @@ +/* 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('../migrations/002'), require('../migrations/003'), diff --git a/app/scripts/lib/observable/host.js b/app/scripts/lib/observable/host.js index 69f674be8..d1b110503 100644 --- a/app/scripts/lib/observable/host.js +++ b/app/scripts/lib/observable/host.js @@ -12,14 +12,14 @@ class HostStore extends ObservableStore { constructor (initState, opts) { super(initState) - this.opts = opts || {} + this._opts = opts || {} } createStream () { const self = this // setup remotely exposed api let remoteApi = {} - if (!self.opts.readOnly) { + if (!self._opts.readOnly) { remoteApi.put = (newState) => self.put(newState) } // listen for connection to remote diff --git a/app/scripts/lib/observable/index.js b/app/scripts/lib/observable/index.js index d193e5554..1ff112e95 100644 --- a/app/scripts/lib/observable/index.js +++ b/app/scripts/lib/observable/index.js @@ -7,22 +7,30 @@ class ObservableStore extends EventEmitter { 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) 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 index b5a3254a2..603f6f0b8 100644 --- a/app/scripts/lib/observable/remote.js +++ b/app/scripts/lib/observable/remote.js @@ -12,7 +12,7 @@ class RemoteStore extends ObservableStore { constructor (initState, opts) { super(initState) - this.opts = opts || {} + this._opts = opts || {} this._remote = null } 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/observable/util/transform.js b/app/scripts/lib/observable/util/transform.js deleted file mode 100644 index 87946f402..000000000 --- a/app/scripts/lib/observable/util/transform.js +++ /dev/null @@ -1,13 +0,0 @@ - -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/metamask-controller.js b/app/scripts/metamask-controller.js index 8e0eaf54c..e15844a56 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -14,7 +14,7 @@ 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 synchronizeStore = require('./lib/observable/util/sync') const version = require('../manifest.json').version module.exports = class MetamaskController extends EventEmitter { @@ -244,12 +244,12 @@ module.exports = class MetamaskController extends EventEmitter { var publicConfigStore = new HostStore(initPublicState, { readOnly: true }) // sync publicConfigStore with transform - transformStore(this.store, publicConfigStore, selectPublicState) + synchronizeStore(this.store, publicConfigStore, selectPublicState) function selectPublicState(state) { let result = { selectedAccount: undefined } try { - result.selectedAccount = state.data.config.selectedAccount + result.selectedAccount = state.config.selectedAccount } catch (err) { console.warn('Error in "selectPublicState": ' + err.message) } -- cgit v1.2.3 From b33c51c0a6c7c8a7b0c0a9a6ca101f874f2db3d1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 12 Jan 2017 01:17:05 -0800 Subject: migrations - introduce promise-based migrator --- app/scripts/background.js | 212 +++++++++++++++++++++----------------- app/scripts/lib/migrator/index.js | 31 ++++++ app/scripts/migrations/002.js | 15 +-- app/scripts/migrations/003.js | 16 +-- app/scripts/migrations/004.js | 17 +-- 5 files changed, 179 insertions(+), 112 deletions(-) create mode 100644 app/scripts/lib/migrator/index.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 8aa886594..697417fd2 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,7 +1,8 @@ const urlUtil = require('url') const Dnode = require('dnode') const eos = require('end-of-stream') -const Migrator = require('pojo-migrator') +const asyncQ = require('async-q') +const Migrator = require('./lib/migrator/') const migrations = require('./lib/migrations') const LocalStorageStore = require('./lib/observable/local-storage') const PortStream = require('./lib/port-stream.js') @@ -16,101 +17,143 @@ const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' let popupIsOpen = false +// state persistence +const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) + +// initialization flow +asyncQ.waterfall([ + () => loadStateFromPersistence(), + (initState) => setupController(initState), +]) +.then(() => console.log('MetaMask initialization complete.')) +.catch((err) => { console.error(err) }) // // State and Persistence // -// state persistence - -let dataStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) -// initial state for first time users -if (!dataStore.get()) { - dataStore.put({ meta: { version: 0 }, data: firstTimeState }) +function loadStateFromPersistence() { + // migrations + let migrator = new Migrator({ migrations }) + let initialState = { + meta: { version: migrator.defaultVersion }, + data: firstTimeState, + } + return asyncQ.waterfall([ + // read from disk + () => Promise.resolve(diskStore.get() || initialState), + // migrate data + (versionedData) => migrator.migrateData(versionedData), + // write to disk + (versionedData) => { + diskStore.put(versionedData) + return Promise.resolve(versionedData) + }, + // resolve to just data + (versionedData) => Promise.resolve(versionedData.data), + ]) } -// migrations +function setupController (initState) { -let migrator = new Migrator({ - migrations, - // Data persistence methods - loadData: () => dataStore.get(), - setData: (newState) => dataStore.put(newState), -}) + // + // MetaMask Controller + // -// -// MetaMask Controller -// + const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage: triggerUi, + unlockAccountMessage: triggerUi, + showUnapprovedTx: triggerUi, + // initial state + initState, + }) -const controller = new MetamaskController({ - // User confirmation callbacks: - showUnconfirmedMessage: triggerUi, - unlockAccountMessage: triggerUi, - showUnapprovedTx: triggerUi, - // initial state - initState: migrator.getData(), -}) -// setup state persistence -controller.store.subscribe((newState) => migrator.saveData(newState)) + // setup state persistence + controller.store.subscribe((newState) => diskStore) + + // + // 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) + } + } -// -// connect to other contexts -// + function setupUntrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + controller.setupPublicConfig(mx.createStream('publicConfig')) + } -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 setupTrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + setupControllerConnection(mx.createStream('controller')) + controller.setupProviderConnection(mx.createStream('provider'), originDomain) } -} -function setupUntrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - controller.setupPublicConfig(mx.createStream('publicConfig')) -} + // + // 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 + }) + }) + } -function setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - setupControllerConnection(mx.createStream('controller')) - controller.setupProviderConnection(mx.createStream('provider'), originDomain) -} + // + // User Interface setup + // + + 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' }) + } -// -// remote features -// + return Promise.resolve() -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 - }) - }) } // -// User Interface setup +// Etc... // // popup trigger @@ -123,19 +166,4 @@ extension.runtime.onInstalled.addListener(function (details) { if ((details.reason === 'install') && (!METAMASK_DEBUG)) { extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) } -}) - -// plugin badge text -controller.txManager.on('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) - } - extension.browserAction.setBadgeText({ text: label }) - extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' }) -} +}) \ No newline at end of file diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js new file mode 100644 index 000000000..02d8c2335 --- /dev/null +++ b/app/scripts/lib/migrator/index.js @@ -0,0 +1,31 @@ +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 (meta = { version: this.defaultVersion }) { + let remaining = this.migrations.filter(migrationIsPending) + + return ( + asyncQ.eachSeries(remaining, (migration) => migration.migrate(meta)) + .then(() => meta) + ) + + // migration is "pending" if hit has a higher + // version number than currentVersion + function migrationIsPending(migration) { + return migration.version > meta.version + } + } + +} + +module.exports = Migrator diff --git a/app/scripts/migrations/002.js b/app/scripts/migrations/002.js index 0b654f825..97f427d3a 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 (meta) { + meta.version = version try { - if (data.config.provider.type === 'etherscan') { - data.config.provider.type = 'rpc' - data.config.provider.rpcTarget = 'https://rpc.metamask.io/' + if (meta.data.config.provider.type === 'etherscan') { + meta.data.config.provider.type = 'rpc' + meta.data.config.provider.rpcTarget = 'https://rpc.metamask.io/' } } catch (e) {} - return data + return Promise.resolve(meta) }, } diff --git a/app/scripts/migrations/003.js b/app/scripts/migrations/003.js index 617c55c09..b25e26e01 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 (meta) { + meta.version = version try { - if (data.config.provider.rpcTarget === oldTestRpc) { - data.config.provider.rpcTarget = newTestRpc + if (meta.data.config.provider.rpcTarget === oldTestRpc) { + meta.data.config.provider.rpcTarget = newTestRpc } } catch (e) {} - return data + return Promise.resolve(meta) }, } diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js index 1329a1eed..e72eef2b7 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 (meta) { + meta.version = version try { - if (data.config.provider.type !== 'rpc') return data - switch (data.config.provider.rpcTarget) { + if (meta.data.config.provider.type !== 'rpc') return Promise.resolve(meta) + switch (meta.data.config.provider.rpcTarget) { case 'https://testrpc.metamask.io/': - data.config.provider = { + meta.data.config.provider = { type: 'testnet', } break case 'https://rpc.metamask.io/': - data.config.provider = { + meta.data.config.provider = { type: 'mainnet', } break } } catch (_) {} - return data + return Promise.resolve(meta) }, } -- cgit v1.2.3 From 80514d73b5bc6887cea877194091c941cfb9a8e6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 12 Jan 2017 02:24:33 -0800 Subject: migrations - wip - 005 multivault migration --- app/scripts/background.js | 8 +++---- app/scripts/lib/migrations.js | 18 ---------------- app/scripts/lib/migrator/index.js | 19 ++++++++++++----- app/scripts/migrations/002.js | 12 +++++------ app/scripts/migrations/003.js | 10 ++++----- app/scripts/migrations/004.js | 14 ++++++------- app/scripts/migrations/005.js | 44 +++++++++++++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 18 ++++++++++++++++ 8 files changed, 97 insertions(+), 46 deletions(-) delete mode 100644 app/scripts/lib/migrations.js create mode 100644 app/scripts/migrations/005.js create mode 100644 app/scripts/migrations/index.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 697417fd2..0e5a76d51 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -3,7 +3,7 @@ const Dnode = require('dnode') const eos = require('end-of-stream') const asyncQ = require('async-q') const Migrator = require('./lib/migrator/') -const migrations = require('./lib/migrations') +const migrations = require('./lib/migrations/') const LocalStorageStore = require('./lib/observable/local-storage') const PortStream = require('./lib/port-stream.js') const notification = require('./lib/notifications.js') @@ -35,10 +35,7 @@ asyncQ.waterfall([ function loadStateFromPersistence() { // migrations let migrator = new Migrator({ migrations }) - let initialState = { - meta: { version: migrator.defaultVersion }, - data: firstTimeState, - } + let initialState = migrator.generateInitialState(firstTimeState) return asyncQ.waterfall([ // read from disk () => Promise.resolve(diskStore.get() || initialState), @@ -68,6 +65,7 @@ function setupController (initState) { // initial state initState, }) + global.metamaskController = controller // setup state persistence controller.store.subscribe((newState) => diskStore) diff --git a/app/scripts/lib/migrations.js b/app/scripts/lib/migrations.js deleted file mode 100644 index 12f60def1..000000000 --- a/app/scripts/lib/migrations.js +++ /dev/null @@ -1,18 +0,0 @@ -/* 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('../migrations/002'), - require('../migrations/003'), - require('../migrations/004'), -] diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 02d8c2335..ab5a757b3 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -7,22 +7,31 @@ class Migrator { 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 + this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0 } // run all pending migrations on meta in place - migrateData (meta = { version: this.defaultVersion }) { + migrateData (versionedData = this.generateInitialState()) { let remaining = this.migrations.filter(migrationIsPending) return ( - asyncQ.eachSeries(remaining, (migration) => migration.migrate(meta)) - .then(() => meta) + 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 > meta.version + return migration.version > versionedData.meta.version + } + } + + generateInitialState (initState) { + return { + meta: { + version: this.defaultVersion, + }, + data: initState, } } diff --git a/app/scripts/migrations/002.js b/app/scripts/migrations/002.js index 97f427d3a..476b0a43a 100644 --- a/app/scripts/migrations/002.js +++ b/app/scripts/migrations/002.js @@ -3,14 +3,14 @@ const version = 2 module.exports = { version, - migrate: function (meta) { - meta.version = version + migrate: function (versionedData) { + versionedData.meta.version = version try { - if (meta.data.config.provider.type === 'etherscan') { - meta.data.config.provider.type = 'rpc' - meta.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 Promise.resolve(meta) + return Promise.resolve(versionedData) }, } diff --git a/app/scripts/migrations/003.js b/app/scripts/migrations/003.js index b25e26e01..eceaeaa4b 100644 --- a/app/scripts/migrations/003.js +++ b/app/scripts/migrations/003.js @@ -5,13 +5,13 @@ const newTestRpc = 'https://testrpc.metamask.io/' module.exports = { version, - migrate: function (meta) { - meta.version = version + migrate: function (versionedData) { + versionedData.meta.version = version try { - if (meta.data.config.provider.rpcTarget === oldTestRpc) { - meta.data.config.provider.rpcTarget = newTestRpc + if (versionedData.data.config.provider.rpcTarget === oldTestRpc) { + versionedData.data.config.provider.rpcTarget = newTestRpc } } catch (e) {} - return Promise.resolve(meta) + return Promise.resolve(versionedData) }, } diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js index e72eef2b7..0f9850208 100644 --- a/app/scripts/migrations/004.js +++ b/app/scripts/migrations/004.js @@ -3,23 +3,23 @@ const version = 4 module.exports = { version, - migrate: function (meta) { - meta.version = version + migrate: function (versionedData) { + versionedData.meta.version = version try { - if (meta.data.config.provider.type !== 'rpc') return Promise.resolve(meta) - switch (meta.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/': - meta.data.config.provider = { + versionedData.data.config.provider = { type: 'testnet', } break case 'https://rpc.metamask.io/': - meta.data.config.provider = { + versionedData.data.config.provider = { type: 'mainnet', } break } } catch (_) {} - return Promise.resolve(meta) + return Promise.resolve(versionedData) }, } diff --git a/app/scripts/migrations/005.js b/app/scripts/migrations/005.js new file mode 100644 index 000000000..4c76b1dcf --- /dev/null +++ b/app/scripts/migrations/005.js @@ -0,0 +1,44 @@ +const version = 5 + +const ObservableStore = require('../../app/scripts/lib/observable/') +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'), +] -- cgit v1.2.3 From bc1615f032186e88aebfa9dac38039dcff263162 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 12 Jan 2017 14:40:04 -0800 Subject: background - fix metamaskController store -> diskStore persistence --- app/scripts/background.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 0e5a76d51..1f269da7b 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -5,6 +5,7 @@ const asyncQ = require('async-q') const Migrator = require('./lib/migrator/') const migrations = require('./lib/migrations/') const LocalStorageStore = require('./lib/observable/local-storage') +const synchronizeStore = require('./lib/observable/util/sync') const PortStream = require('./lib/port-stream.js') const notification = require('./lib/notifications.js') const messageManager = require('./lib/message-manager') @@ -68,7 +69,11 @@ function setupController (initState) { global.metamaskController = controller // setup state persistence - controller.store.subscribe((newState) => diskStore) + synchronizeStore(controller.store, diskStore, (state) => { + let versionedData = diskStore.get() + versionedData.data = state + return versionedData + }) // // connect to other contexts -- cgit v1.2.3 From a06ee454045b9087160d3af1c081556662bbd3cb Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 24 Jan 2017 16:30:42 -0800 Subject: migrations - rename 005 to stubbed _multi-keyring --- app/scripts/migrations/005.js | 44 --------------------------- app/scripts/migrations/_multi-keyring.js | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 44 deletions(-) delete mode 100644 app/scripts/migrations/005.js create mode 100644 app/scripts/migrations/_multi-keyring.js (limited to 'app/scripts') diff --git a/app/scripts/migrations/005.js b/app/scripts/migrations/005.js deleted file mode 100644 index 4c76b1dcf..000000000 --- a/app/scripts/migrations/005.js +++ /dev/null @@ -1,44 +0,0 @@ -const version = 5 - -const ObservableStore = require('../../app/scripts/lib/observable/') -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/_multi-keyring.js b/app/scripts/migrations/_multi-keyring.js new file mode 100644 index 000000000..5fa9a33f3 --- /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('../../app/scripts/lib/observable/') +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) + }) + }) + + }, +} -- cgit v1.2.3 From 76ce348a04b83693eda0e8a40f9888c1f5fe7ef5 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 24 Jan 2017 19:47:00 -0800 Subject: obs-store - use published module --- app/scripts/background.js | 23 ++++++--- app/scripts/lib/config-manager.js | 44 ++++++++-------- app/scripts/lib/inpage-provider.js | 79 ++++++++++++----------------- app/scripts/lib/observable/host.js | 50 ------------------ app/scripts/lib/observable/index.js | 41 --------------- app/scripts/lib/observable/local-storage.js | 37 -------------- app/scripts/lib/observable/remote.js | 51 ------------------- app/scripts/lib/observable/util/sync.js | 24 --------- app/scripts/metamask-controller.js | 29 ++++++----- app/scripts/migrations/_multi-keyring.js | 2 +- 10 files changed, 85 insertions(+), 295 deletions(-) delete mode 100644 app/scripts/lib/observable/host.js delete mode 100644 app/scripts/lib/observable/index.js delete mode 100644 app/scripts/lib/observable/local-storage.js delete mode 100644 app/scripts/lib/observable/remote.js delete mode 100644 app/scripts/lib/observable/util/sync.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index f95e194dd..18882e5d5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -2,10 +2,11 @@ const urlUtil = require('url') 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('./lib/migrations/') -const LocalStorageStore = require('./lib/observable/local-storage') -const synchronizeStore = require('./lib/observable/util/sync') +const migrations = require('./migrations/') const PortStream = require('./lib/port-stream.js') const notification = require('./lib/notifications.js') const messageManager = require('./lib/message-manager') @@ -40,12 +41,12 @@ function loadStateFromPersistence() { let initialState = migrator.generateInitialState(firstTimeState) return asyncQ.waterfall([ // read from disk - () => Promise.resolve(diskStore.get() || initialState), + () => Promise.resolve(diskStore.getState() || initialState), // migrate data (versionedData) => migrator.migrateData(versionedData), // write to disk (versionedData) => { - diskStore.put(versionedData) + diskStore.putState(versionedData) return Promise.resolve(versionedData) }, // resolve to just data @@ -70,11 +71,17 @@ function setupController (initState) { global.metamaskController = controller // setup state persistence - synchronizeStore(controller.store, diskStore, (state) => { - let versionedData = diskStore.get() + pipe( + controller.store, + storeTransform(versionifyData), + diskStore + ) + + function versionifyData(state) { + let versionedData = diskStore.getState() versionedData.data = state return versionedData - }) + } // // connect to other contexts diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index daba8bc7b..6d088906c 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -21,14 +21,14 @@ function ConfigManager (opts) { } ConfigManager.prototype.setConfig = function (config) { - var data = this.store.get() + var data = this.getData() data.config = config this.setData(data) this._emitUpdates(config) } ConfigManager.prototype.getConfig = function () { - var data = this.store.get() + var data = this.getData() if ('config' in data) { return data.config } else { @@ -71,15 +71,15 @@ ConfigManager.prototype.getProvider = function () { } ConfigManager.prototype.setData = function (data) { - this.store.put(data) + this.store.putState(data) } ConfigManager.prototype.getData = function () { - return this.store.get() + return this.store.getState() } ConfigManager.prototype.setWallet = function (wallet) { - var data = this.store.get() + var data = this.getData() data.wallet = wallet this.setData(data) } @@ -96,11 +96,11 @@ ConfigManager.prototype.getVault = function () { } ConfigManager.prototype.getKeychains = function () { - return this.store.get().keychains || [] + return this.getData().keychains || [] } ConfigManager.prototype.setKeychains = function (keychains) { - var data = this.store.get() + var data = this.getData() data.keychains = keychains this.setData(data) } @@ -117,19 +117,19 @@ ConfigManager.prototype.setSelectedAccount = function (address) { } ConfigManager.prototype.getWallet = function () { - return this.store.get().wallet + return this.getData().wallet } // Takes a boolean ConfigManager.prototype.setShowSeedWords = function (should) { - var data = this.store.get() + var data = this.getData() data.showSeedWords = should this.setData(data) } ConfigManager.prototype.getShouldShowSeedWords = function () { - var data = this.store.get() + var data = this.getData() return data.showSeedWords } @@ -141,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 () { @@ -163,16 +163,12 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { } } -ConfigManager.prototype.setData = function (data) { - this.store.put(data) -} - // // Tx // ConfigManager.prototype.getTxList = function () { - var data = this.store.get() + var data = this.getData() if (data.transactions !== undefined) { return data.transactions } else { @@ -181,7 +177,7 @@ ConfigManager.prototype.getTxList = function () { } ConfigManager.prototype.setTxList = function (txList) { - var data = this.store.get() + var data = this.getData() data.transactions = txList this.setData(data) } @@ -214,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) { @@ -248,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) { @@ -259,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) { @@ -270,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 () { @@ -301,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 () { @@ -345,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 64301be78..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('./observable/remote') 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,24 +112,6 @@ MetamaskInpageProvider.prototype.isMetaMask = true // util -function remoteStoreWithLocalStorageCache (storageKey) { - // read local cache - 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) - }) - - return store -} - function eachJsonMessage (payload, transformFn) { if (Array.isArray(payload)) { return payload.map(transformFn) @@ -141,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/observable/host.js b/app/scripts/lib/observable/host.js deleted file mode 100644 index d1b110503..000000000 --- a/app/scripts/lib/observable/host.js +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index 1ff112e95..000000000 --- a/app/scripts/lib/observable/index.js +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 6ed3860f6..000000000 --- a/app/scripts/lib/observable/local-storage.js +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index 603f6f0b8..000000000 --- a/app/scripts/lib/observable/remote.js +++ /dev/null @@ -1,51 +0,0 @@ -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 deleted file mode 100644 index c61feb02e..000000000 --- a/app/scripts/lib/observable/util/sync.js +++ /dev/null @@ -1,24 +0,0 @@ - -// -// 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/metamask-controller.js b/app/scripts/metamask-controller.js index d3077817d..8f157a45e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1,6 +1,9 @@ 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') @@ -13,9 +16,6 @@ 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 synchronizeStore = require('./lib/observable/util/sync') const accountImporter = require('./account-import-strategies') const version = require('../manifest.json').version @@ -258,18 +258,21 @@ module.exports = class MetamaskController extends EventEmitter { initPublicConfigStore () { // get init state - var initPublicState = this.store.get() - var publicConfigStore = new HostStore(initPublicState, { readOnly: true }) + const publicConfigStore = new ObservableStore() // sync publicConfigStore with transform - synchronizeStore(this.store, publicConfigStore, selectPublicState) + pipe( + this.store, + storeTransform(selectPublicState), + publicConfigStore + ) function selectPublicState(state) { - let result = { selectedAccount: undefined } + const result = { selectedAccount: undefined } try { result.selectedAccount = state.config.selectedAccount - } catch (err) { - console.warn('Error in "selectPublicState": ' + err.message) + } catch (_) { + // thats fine, im sure it will be there next time... } return result } @@ -314,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/_multi-keyring.js b/app/scripts/migrations/_multi-keyring.js index 5fa9a33f3..04c966d4d 100644 --- a/app/scripts/migrations/_multi-keyring.js +++ b/app/scripts/migrations/_multi-keyring.js @@ -7,7 +7,7 @@ which we dont have access to at the time of this writing. */ -const ObservableStore = require('../../app/scripts/lib/observable/') +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') -- cgit v1.2.3