diff options
Diffstat (limited to 'app/scripts/background.js')
-rw-r--r-- | app/scripts/background.js | 277 |
1 files changed, 148 insertions, 129 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js index f3837a028..18882e5d5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,162 +1,181 @@ const urlUtil = require('url') -const extend = require('xtend') const Dnode = require('dnode') const eos = require('end-of-stream') +const asyncQ = require('async-q') +const pipe = require('pump') +const LocalStorageStore = require('obs-store/lib/localStorage') +const storeTransform = require('obs-store/lib/transform') +const Migrator = require('./lib/migrator/') +const migrations = require('./migrations/') const PortStream = require('./lib/port-stream.js') const notification = require('./lib/notifications.js') const messageManager = require('./lib/message-manager') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const MetamaskController = require('./metamask-controller') const extension = require('./lib/extension') +const firstTimeState = require('./first-time-state') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' -var popupIsOpen = false - -const controller = new MetamaskController({ - // User confirmation callbacks: - showUnconfirmedMessage: triggerUi, - unlockAccountMessage: triggerUi, - showUnapprovedTx: triggerUi, - // Persistence Methods: - setData, - loadData, -}) -function triggerUi () { - if (!popupIsOpen) notification.show() -} -// On first install, open a window to MetaMask website to how-it-works. -extension.runtime.onInstalled.addListener(function (details) { - if ((details.reason === 'install') && (!METAMASK_DEBUG)) { - extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) - } -}) - -// -// connect to other contexts -// +let popupIsOpen = false -extension.runtime.onConnect.addListener(connectRemote) -function connectRemote (remotePort) { - var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification' - var portStream = new PortStream(remotePort) - if (isMetaMaskInternalProcess) { - // communication with popup - popupIsOpen = remotePort.name === 'popup' - setupTrustedCommunication(portStream, 'MetaMask', remotePort.name) - } else { - // communication with page - var originDomain = urlUtil.parse(remotePort.sender.url).hostname - setupUntrustedCommunication(portStream, originDomain) - } -} - -function setupUntrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - controller.setupPublicConfig(mx.createStream('publicConfig')) -} +// state persistence +const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) -function setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - setupControllerConnection(mx.createStream('controller')) - controller.setupProviderConnection(mx.createStream('provider'), originDomain) -} +// initialization flow +asyncQ.waterfall([ + () => loadStateFromPersistence(), + (initState) => setupController(initState), +]) +.then(() => console.log('MetaMask initialization complete.')) +.catch((err) => { console.error(err) }) // -// remote features +// State and Persistence // -function setupControllerConnection (stream) { - controller.stream = stream - var api = controller.getApi() - var dnode = Dnode(api) - stream.pipe(dnode).pipe(stream) - dnode.on('remote', (remote) => { - // push updates to popup - var sendUpdate = remote.sendUpdate.bind(remote) - controller.on('update', sendUpdate) - // teardown on disconnect - eos(stream, () => { - controller.removeListener('update', sendUpdate) - popupIsOpen = false - }) - }) +function loadStateFromPersistence() { + // migrations + let migrator = new Migrator({ migrations }) + let initialState = migrator.generateInitialState(firstTimeState) + return asyncQ.waterfall([ + // read from disk + () => Promise.resolve(diskStore.getState() || initialState), + // migrate data + (versionedData) => migrator.migrateData(versionedData), + // write to disk + (versionedData) => { + diskStore.putState(versionedData) + return Promise.resolve(versionedData) + }, + // resolve to just data + (versionedData) => Promise.resolve(versionedData.data), + ]) } -// -// plugin badge text -// +function setupController (initState) { + + // + // MetaMask Controller + // -controller.txManager.on('updateBadge', updateBadge) -updateBadge() - -function updateBadge () { - var label = '' - var unapprovedTxCount = controller.txManager.unapprovedTxCount - var unconfMsgs = messageManager.unconfirmedMsgs() - var unconfMsgLen = Object.keys(unconfMsgs).length - var count = unapprovedTxCount + unconfMsgLen - if (count) { - label = String(count) + const controller = new MetamaskController({ + // User confirmation callbacks: + showUnconfirmedMessage: triggerUi, + unlockAccountMessage: triggerUi, + showUnapprovedTx: triggerUi, + // initial state + initState, + }) + global.metamaskController = controller + + // setup state persistence + pipe( + controller.store, + storeTransform(versionifyData), + diskStore + ) + + function versionifyData(state) { + let versionedData = diskStore.getState() + versionedData.data = state + return versionedData } - extension.browserAction.setBadgeText({ text: label }) - extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' }) -} -// data :: setters/getters + // + // connect to other contexts + // + + extension.runtime.onConnect.addListener(connectRemote) + function connectRemote (remotePort) { + var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification' + var portStream = new PortStream(remotePort) + if (isMetaMaskInternalProcess) { + // communication with popup + popupIsOpen = remotePort.name === 'popup' + setupTrustedCommunication(portStream, 'MetaMask', remotePort.name) + } else { + // communication with page + var originDomain = urlUtil.parse(remotePort.sender.url).hostname + setupUntrustedCommunication(portStream, originDomain) + } + } -function loadData () { - var oldData = getOldStyleData() - var newData - try { - newData = JSON.parse(window.localStorage[STORAGE_KEY]) - } catch (e) {} + function setupUntrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + controller.setupPublicConfig(mx.createStream('publicConfig')) + } - var data = extend({ - meta: { - version: 0, - }, - data: { - config: { - provider: { - type: 'testnet', - }, - }, - }, - }, oldData || null, newData || null) - return data -} + function setupTrustedCommunication (connectionStream, originDomain) { + // setup multiplexing + var mx = setupMultiplex(connectionStream) + // connect features + setupControllerConnection(mx.createStream('controller')) + controller.setupProviderConnection(mx.createStream('provider'), originDomain) + } -function getOldStyleData () { - var config, wallet, seedWords + // + // remote features + // + + function setupControllerConnection (stream) { + controller.stream = stream + var api = controller.getApi() + var dnode = Dnode(api) + stream.pipe(dnode).pipe(stream) + dnode.on('remote', (remote) => { + // push updates to popup + var sendUpdate = remote.sendUpdate.bind(remote) + controller.on('update', sendUpdate) + // teardown on disconnect + eos(stream, () => { + controller.removeListener('update', sendUpdate) + popupIsOpen = false + }) + }) + } - var result = { - meta: { version: 0 }, - data: {}, + // + // User Interface setup + // + + updateBadge() + controller.txManager.on('updateBadge', updateBadge) + + // plugin badge text + function updateBadge () { + var label = '' + var unapprovedTxCount = controller.txManager.unapprovedTxCount + var unconfMsgs = messageManager.unconfirmedMsgs() + var unconfMsgLen = Object.keys(unconfMsgs).length + var count = unapprovedTxCount + unconfMsgLen + if (count) { + label = String(count) + } + extension.browserAction.setBadgeText({ text: label }) + extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' }) } - try { - config = JSON.parse(window.localStorage['config']) - result.data.config = config - } catch (e) {} - try { - wallet = JSON.parse(window.localStorage['lightwallet']) - result.data.wallet = wallet - } catch (e) {} - try { - seedWords = window.localStorage['seedWords'] - result.data.seedWords = seedWords - } catch (e) {} - - return result + return Promise.resolve() + } -function setData (data) { - window.localStorage[STORAGE_KEY] = JSON.stringify(data) +// +// Etc... +// + +// popup trigger +function triggerUi () { + if (!popupIsOpen) notification.show() } + +// On first install, open a window to MetaMask website to how-it-works. +extension.runtime.onInstalled.addListener(function (details) { + if ((details.reason === 'install') && (!METAMASK_DEBUG)) { + extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) + } +})
\ No newline at end of file |