aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/scripts/background.js212
-rw-r--r--app/scripts/lib/migrator/index.js31
-rw-r--r--app/scripts/migrations/002.js15
-rw-r--r--app/scripts/migrations/003.js16
-rw-r--r--app/scripts/migrations/004.js17
-rw-r--r--package.json1
-rw-r--r--test/unit/migrations-test.js48
7 files changed, 204 insertions, 136 deletions
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)
},
}
diff --git a/package.json b/package.json
index 0d0835a86..954f5a10e 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
},
"dependencies": {
"async": "^1.5.2",
+ "async-q": "^0.3.1",
"bip39": "^2.2.0",
"browser-passworder": "^2.0.3",
"browserify-derequire": "^0.9.4",
diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js
index 9ea8d5c5a..715a5feb0 100644
--- a/test/unit/migrations-test.js
+++ b/test/unit/migrations-test.js
@@ -1,34 +1,34 @@
-var assert = require('assert')
-var path = require('path')
+const assert = require('assert')
+const path = require('path')
-var wallet1 = require(path.join('..', 'lib', 'migrations', '001.json'))
+const wallet1 = require(path.join('..', 'lib', 'migrations', '001.json'))
-var migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002'))
-var migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003'))
-var migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004'))
+const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002'))
+const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003'))
+const migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004'))
-describe('wallet1 is migrated successfully', function() {
+const oldTestRpc = 'https://rawtestrpc.metamask.io/'
+const newTestRpc = 'https://testrpc.metamask.io/'
- it('should convert providers', function(done) {
+describe('wallet1 is migrated successfully', function() {
+ it('should convert providers', function() {
wallet1.data.config.provider = { type: 'etherscan', rpcTarget: null }
- var firstResult = migration2.migrate(wallet1.data)
- assert.equal(firstResult.config.provider.type, 'rpc', 'provider should be rpc')
- assert.equal(firstResult.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc')
-
- var oldTestRpc = 'https://rawtestrpc.metamask.io/'
- var newTestRpc = 'https://testrpc.metamask.io/'
- firstResult.config.provider.rpcTarget = oldTestRpc
-
- var secondResult = migration3.migrate(firstResult)
- assert.equal(secondResult.config.provider.rpcTarget, newTestRpc)
-
- var thirdResult = migration4.migrate(secondResult)
- assert.equal(secondResult.config.provider.rpcTarget, null)
- assert.equal(secondResult.config.provider.type, 'testnet')
-
- done()
+ return migration2.migrate(wallet1)
+ .then((firstResult) => {
+ assert.equal(firstResult.data.config.provider.type, 'rpc', 'provider should be rpc')
+ assert.equal(firstResult.data.config.provider.rpcTarget, 'https://rpc.metamask.io/', 'main provider should be our rpc')
+ firstResult.data.config.provider.rpcTarget = oldTestRpc
+ return migration3.migrate(firstResult)
+ }).then((secondResult) => {
+ assert.equal(secondResult.data.config.provider.rpcTarget, newTestRpc)
+ return migration4.migrate(secondResult)
+ }).then((thirdResult) => {
+ assert.equal(thirdResult.data.config.provider.rpcTarget, null)
+ assert.equal(thirdResult.data.config.provider.type, 'testnet')
+ })
+
})
})