mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #999 from MetaMask/obs-store2
background - introduce ObservableStore (mark II)
This commit is contained in:
commit
d30612a216
@ -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,
|
||||
})
|
||||
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
|
||||
//
|
||||
|
||||
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),
|
||||
])
|
||||
}
|
||||
|
||||
function setupController (initState) {
|
||||
|
||||
//
|
||||
// MetaMask Controller
|
||||
//
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
//
|
||||
// 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 setupUntrustedCommunication (connectionStream, originDomain) {
|
||||
// setup multiplexing
|
||||
var mx = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||
controller.setupPublicConfig(mx.createStream('publicConfig'))
|
||||
}
|
||||
|
||||
function setupTrustedCommunication (connectionStream, originDomain) {
|
||||
// setup multiplexing
|
||||
var mx = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
setupControllerConnection(mx.createStream('controller'))
|
||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||
}
|
||||
|
||||
//
|
||||
// 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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// 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' })
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// 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'})
|
||||
}
|
||||
})
|
||||
|
||||
//
|
||||
// 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 setupUntrustedCommunication (connectionStream, originDomain) {
|
||||
// setup multiplexing
|
||||
var mx = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||
controller.setupPublicConfig(mx.createStream('publicConfig'))
|
||||
}
|
||||
|
||||
function setupTrustedCommunication (connectionStream, originDomain) {
|
||||
// setup multiplexing
|
||||
var mx = setupMultiplex(connectionStream)
|
||||
// connect features
|
||||
setupControllerConnection(mx.createStream('controller'))
|
||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
||||
}
|
||||
|
||||
//
|
||||
// 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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// plugin badge text
|
||||
//
|
||||
|
||||
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)
|
||||
}
|
||||
extension.browserAction.setBadgeText({ text: label })
|
||||
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
|
||||
}
|
||||
|
||||
// data :: setters/getters
|
||||
|
||||
function loadData () {
|
||||
var oldData = getOldStyleData()
|
||||
var newData
|
||||
try {
|
||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
||||
} catch (e) {}
|
||||
|
||||
var data = extend({
|
||||
meta: {
|
||||
version: 0,
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
}, oldData || null, newData || null)
|
||||
return data
|
||||
}
|
||||
|
||||
function getOldStyleData () {
|
||||
var config, wallet, seedWords
|
||||
|
||||
var result = {
|
||||
meta: { version: 0 },
|
||||
data: {},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function setData (data) {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
|
||||
}
|
||||
})
|
11
app/scripts/first-time-state.js
Normal file
11
app/scripts/first-time-state.js
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// The default state of MetaMask
|
||||
//
|
||||
|
||||
module.exports = {
|
||||
config: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
},
|
||||
},
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
const Migrator = require('pojo-migrator')
|
||||
const MetamaskConfig = require('../config.js')
|
||||
const migrations = require('./migrations')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const normalize = require('./sig-util').normalize
|
||||
|
||||
@ -19,41 +17,18 @@ module.exports = ConfigManager
|
||||
function ConfigManager (opts) {
|
||||
// ConfigManager is observable and will emit updates
|
||||
this._subs = []
|
||||
|
||||
/* The migrator exported on the config-manager
|
||||
* has two methods the user should be concerned with:
|
||||
*
|
||||
* getData(), which returns the app-consumable data object
|
||||
* saveData(), which persists the app-consumable data object.
|
||||
*/
|
||||
this.migrator = new Migrator({
|
||||
|
||||
// Migrations must start at version 1 or later.
|
||||
// They are objects with a `version` number
|
||||
// and a `migrate` function.
|
||||
//
|
||||
// The `migrate` function receives the previous
|
||||
// config data format, and returns the new one.
|
||||
migrations: migrations,
|
||||
|
||||
// How to load initial config.
|
||||
// Includes step on migrating pre-pojo-migrator data.
|
||||
loadData: opts.loadData,
|
||||
|
||||
// How to persist migrated config.
|
||||
setData: opts.setData,
|
||||
})
|
||||
this.store = opts.store
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setConfig = function (config) {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
data.config = config
|
||||
this.setData(data)
|
||||
this._emitUpdates(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getConfig = function () {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
if ('config' in data) {
|
||||
return data.config
|
||||
} else {
|
||||
@ -96,15 +71,15 @@ ConfigManager.prototype.getProvider = function () {
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setData = function (data) {
|
||||
this.migrator.saveData(data)
|
||||
this.store.putState(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getData = function () {
|
||||
return this.migrator.getData()
|
||||
return this.store.getState()
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setWallet = function (wallet) {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
data.wallet = wallet
|
||||
this.setData(data)
|
||||
}
|
||||
@ -121,11 +96,11 @@ ConfigManager.prototype.getVault = function () {
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getKeychains = function () {
|
||||
return this.migrator.getData().keychains || []
|
||||
return this.getData().keychains || []
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setKeychains = function (keychains) {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
data.keychains = keychains
|
||||
this.setData(data)
|
||||
}
|
||||
@ -142,19 +117,19 @@ ConfigManager.prototype.setSelectedAccount = function (address) {
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getWallet = function () {
|
||||
return this.migrator.getData().wallet
|
||||
return this.getData().wallet
|
||||
}
|
||||
|
||||
// Takes a boolean
|
||||
ConfigManager.prototype.setShowSeedWords = function (should) {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
data.showSeedWords = should
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
|
||||
ConfigManager.prototype.getShouldShowSeedWords = function () {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
return data.showSeedWords
|
||||
}
|
||||
|
||||
@ -166,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 () {
|
||||
@ -188,16 +163,12 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
|
||||
}
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setData = function (data) {
|
||||
this.migrator.saveData(data)
|
||||
}
|
||||
|
||||
//
|
||||
// Tx
|
||||
//
|
||||
|
||||
ConfigManager.prototype.getTxList = function () {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
if (data.transactions !== undefined) {
|
||||
return data.transactions
|
||||
} else {
|
||||
@ -206,7 +177,7 @@ ConfigManager.prototype.getTxList = function () {
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setTxList = function (txList) {
|
||||
var data = this.migrator.getData()
|
||||
var data = this.getData()
|
||||
data.transactions = txList
|
||||
this.setData(data)
|
||||
}
|
||||
@ -239,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) {
|
||||
@ -273,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) {
|
||||
@ -284,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) {
|
||||
@ -295,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 () {
|
||||
@ -326,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 () {
|
||||
@ -370,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) {
|
||||
|
@ -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('./remote-store.js').RemoteStore
|
||||
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
|
||||
var multiStream = self.multiStream = ObjectMultiplex()
|
||||
pipe(
|
||||
connectionStream,
|
||||
multiStream,
|
||||
connectionStream,
|
||||
(err) => logStreamDisconnectWarning('MetaMask', err)
|
||||
)
|
||||
|
||||
// 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
|
||||
// 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,18 +112,6 @@ MetamaskInpageProvider.prototype.isMetaMask = true
|
||||
|
||||
// util
|
||||
|
||||
function remoteStoreWithLocalStorageCache (storageKey) {
|
||||
// read local cache
|
||||
var initState = JSON.parse(localStorage[storageKey] || '{}')
|
||||
var store = new RemoteStore(initState)
|
||||
// cache the latest state locally
|
||||
store.subscribe(function (state) {
|
||||
localStorage[storageKey] = JSON.stringify(state)
|
||||
})
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
function eachJsonMessage (payload, transformFn) {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload.map(transformFn)
|
||||
@ -135,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 () {}
|
||||
|
@ -1,5 +0,0 @@
|
||||
module.exports = [
|
||||
require('../migrations/002'),
|
||||
require('../migrations/003'),
|
||||
require('../migrations/004'),
|
||||
]
|
40
app/scripts/lib/migrator/index.js
Normal file
40
app/scripts/lib/migrator/index.js
Normal file
@ -0,0 +1,40 @@
|
||||
const asyncQ = require('async-q')
|
||||
|
||||
class Migrator {
|
||||
|
||||
constructor (opts = {}) {
|
||||
let migrations = opts.migrations || []
|
||||
this.migrations = migrations.sort((a, b) => a.version - b.version)
|
||||
let lastMigration = this.migrations.slice(-1)[0]
|
||||
// use specified defaultVersion or highest migration version
|
||||
this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0
|
||||
}
|
||||
|
||||
// run all pending migrations on meta in place
|
||||
migrateData (versionedData = this.generateInitialState()) {
|
||||
let remaining = this.migrations.filter(migrationIsPending)
|
||||
|
||||
return (
|
||||
asyncQ.eachSeries(remaining, (migration) => migration.migrate(versionedData))
|
||||
.then(() => versionedData)
|
||||
)
|
||||
|
||||
// migration is "pending" if hit has a higher
|
||||
// version number than currentVersion
|
||||
function migrationIsPending(migration) {
|
||||
return migration.version > versionedData.meta.version
|
||||
}
|
||||
}
|
||||
|
||||
generateInitialState (initState) {
|
||||
return {
|
||||
meta: {
|
||||
version: this.defaultVersion,
|
||||
},
|
||||
data: initState,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Migrator
|
@ -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)
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
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')
|
||||
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')
|
||||
@ -15,19 +17,30 @@ const autoFaucet = require('./lib/auto-faucet')
|
||||
const nodeify = require('./lib/nodeify')
|
||||
const IdStoreMigrator = require('./lib/idStore-migrator')
|
||||
const accountImporter = require('./account-import-strategies')
|
||||
|
||||
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,
|
||||
@ -245,29 +258,23 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
initPublicConfigStore () {
|
||||
// get init state
|
||||
var initPublicState = configToPublic(this.configManager.getConfig())
|
||||
var publicConfigStore = new HostStore(initPublicState)
|
||||
const publicConfigStore = new ObservableStore()
|
||||
|
||||
// subscribe to changes
|
||||
this.configManager.subscribe(function (state) {
|
||||
storeSetFromObj(publicConfigStore, configToPublic(state))
|
||||
})
|
||||
// sync publicConfigStore with transform
|
||||
pipe(
|
||||
this.store,
|
||||
storeTransform(selectPublicState),
|
||||
publicConfigStore
|
||||
)
|
||||
|
||||
this.keyringController.on('newAccount', (account) => {
|
||||
autoFaucet(account)
|
||||
})
|
||||
|
||||
// config substate
|
||||
function configToPublic (state) {
|
||||
return {
|
||||
selectedAccount: state.selectedAccount,
|
||||
function selectPublicState(state) {
|
||||
const result = { selectedAccount: undefined }
|
||||
try {
|
||||
result.selectedAccount = state.config.selectedAccount
|
||||
} catch (_) {
|
||||
// thats fine, im sure it will be there next time...
|
||||
}
|
||||
}
|
||||
// dump obj into store
|
||||
function storeSetFromObj (store, obj) {
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
store.set(key, obj[key])
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
return publicConfigStore
|
||||
@ -310,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
|
||||
|
@ -1,13 +1,16 @@
|
||||
module.exports = {
|
||||
version: 2,
|
||||
const version = 2
|
||||
|
||||
migrate: function (data) {
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (versionedData) {
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
if (data.config.provider.type === 'etherscan') {
|
||||
data.config.provider.type = 'rpc'
|
||||
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 data
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
}
|
||||
|
@ -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 (versionedData) {
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
if (data.config.provider.rpcTarget === oldTestRpc) {
|
||||
data.config.provider.rpcTarget = newTestRpc
|
||||
if (versionedData.data.config.provider.rpcTarget === oldTestRpc) {
|
||||
versionedData.data.config.provider.rpcTarget = newTestRpc
|
||||
}
|
||||
} catch (e) {}
|
||||
return data
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
}
|
||||
|
@ -1,22 +1,25 @@
|
||||
module.exports = {
|
||||
version: 4,
|
||||
const version = 4
|
||||
|
||||
migrate: function (data) {
|
||||
module.exports = {
|
||||
version,
|
||||
|
||||
migrate: function (versionedData) {
|
||||
versionedData.meta.version = version
|
||||
try {
|
||||
if (data.config.provider.type !== 'rpc') return data
|
||||
switch (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/':
|
||||
data.config.provider = {
|
||||
versionedData.data.config.provider = {
|
||||
type: 'testnet',
|
||||
}
|
||||
break
|
||||
case 'https://rpc.metamask.io/':
|
||||
data.config.provider = {
|
||||
versionedData.data.config.provider = {
|
||||
type: 'mainnet',
|
||||
}
|
||||
break
|
||||
}
|
||||
} catch (_) {}
|
||||
return data
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
}
|
||||
|
51
app/scripts/migrations/_multi-keyring.js
Normal file
51
app/scripts/migrations/_multi-keyring.js
Normal file
@ -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('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')
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
},
|
||||
}
|
18
app/scripts/migrations/index.js
Normal file
18
app/scripts/migrations/index.js
Normal file
@ -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'),
|
||||
]
|
98
mock-dev.js
98
mock-dev.js
@ -15,97 +15,71 @@
|
||||
const extend = require('xtend')
|
||||
const render = require('react-dom').render
|
||||
const h = require('react-hyperscript')
|
||||
const pipe = require('mississippi').pipe
|
||||
const LocalStorageStore = require('obs-store/lib/localStorage')
|
||||
const Root = require('./ui/app/root')
|
||||
const configureStore = require('./ui/app/store')
|
||||
const actions = require('./ui/app/actions')
|
||||
const states = require('./development/states')
|
||||
const Selector = require('./development/selector')
|
||||
const MetamaskController = require('./app/scripts/metamask-controller')
|
||||
const firstTimeState = require('./app/scripts/first-time-state')
|
||||
const extension = require('./development/mockExtension')
|
||||
const noop = function () {}
|
||||
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
|
||||
//
|
||||
// Query String
|
||||
//
|
||||
|
||||
const qs = require('qs')
|
||||
let queryString = qs.parse(window.location.href.split('#')[1])
|
||||
let selectedView = queryString.view || 'first time'
|
||||
const firstState = states[selectedView]
|
||||
updateQueryParams(selectedView)
|
||||
|
||||
// CSS
|
||||
const MetaMaskUiCss = require('./ui/css')
|
||||
const injectCss = require('inject-css')
|
||||
|
||||
|
||||
function updateQueryParams(newView) {
|
||||
queryString.view = newView
|
||||
const params = qs.stringify(queryString)
|
||||
window.location.href = window.location.href.split('#')[0] + `#${params}`
|
||||
}
|
||||
|
||||
const noop = function () {}
|
||||
//
|
||||
// CSS
|
||||
//
|
||||
|
||||
const MetaMaskUiCss = require('./ui/css')
|
||||
const injectCss = require('inject-css')
|
||||
|
||||
//
|
||||
// MetaMask Controller
|
||||
//
|
||||
|
||||
let dataStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
|
||||
// initial state for first time users
|
||||
if (!dataStore.getState()) {
|
||||
dataStore.putState(firstTimeState)
|
||||
}
|
||||
|
||||
const controller = new MetamaskController({
|
||||
// User confirmation callbacks:
|
||||
showUnconfirmedMessage: noop,
|
||||
unlockAccountMessage: noop,
|
||||
showUnapprovedTx: noop,
|
||||
// Persistence Methods:
|
||||
setData,
|
||||
loadData,
|
||||
// initial state
|
||||
initState: dataStore.getState(),
|
||||
})
|
||||
|
||||
// Stub out localStorage for non-browser environments
|
||||
if (!window.localStorage) {
|
||||
window.localStorage = {}
|
||||
}
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
function loadData () {
|
||||
var oldData = getOldStyleData()
|
||||
var newData
|
||||
try {
|
||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
||||
} catch (e) {}
|
||||
// setup state persistence
|
||||
pipe(
|
||||
controller.store,
|
||||
dataStore
|
||||
)
|
||||
|
||||
var data = extend({
|
||||
meta: {
|
||||
version: 0,
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
}, oldData || null, newData || null)
|
||||
return data
|
||||
}
|
||||
|
||||
function setData (data) {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
|
||||
}
|
||||
|
||||
function getOldStyleData () {
|
||||
var config, wallet, seedWords
|
||||
|
||||
var result = {
|
||||
meta: { version: 0 },
|
||||
data: {},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
//
|
||||
// User Interface
|
||||
//
|
||||
|
||||
actions._setBackgroundConnection(controller.getApi())
|
||||
actions.update = function(stateName) {
|
||||
|
@ -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",
|
||||
@ -69,6 +70,7 @@
|
||||
"mississippi": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"multiplex": "^6.7.0",
|
||||
"obs-store": "^2.2.3",
|
||||
"once": "^1.3.3",
|
||||
"ping-pong-stream": "^1.0.0",
|
||||
"pojo-migrator": "^2.1.0",
|
||||
@ -76,6 +78,7 @@
|
||||
"post-message-stream": "^1.0.0",
|
||||
"promise-filter": "^1.1.0",
|
||||
"promise-to-callback": "^1.0.0",
|
||||
"pump": "^1.0.2",
|
||||
"pumpify": "^1.3.4",
|
||||
"qrcode-npm": "0.0.3",
|
||||
"react": "^15.0.2",
|
||||
|
@ -1,30 +1,23 @@
|
||||
var ConfigManager = require('../../../app/scripts/lib/config-manager')
|
||||
var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
|
||||
var SimpleKeyring = require('../../../app/scripts/keyrings/simple')
|
||||
var normalize = require('../../../app/scripts/lib/sig-util').normalize
|
||||
const ObservableStore = require('obs-store')
|
||||
const ConfigManager = require('../../../app/scripts/lib/config-manager')
|
||||
const IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
|
||||
const SimpleKeyring = require('../../../app/scripts/keyrings/simple')
|
||||
const normalize = require('../../../app/scripts/lib/sig-util').normalize
|
||||
|
||||
var oldStyleVault = require('../mocks/oldVault.json')
|
||||
var badStyleVault = require('../mocks/badVault.json')
|
||||
const oldStyleVault = require('../mocks/oldVault.json').data
|
||||
const badStyleVault = require('../mocks/badVault.json').data
|
||||
|
||||
var STORAGE_KEY = 'metamask-config'
|
||||
var PASSWORD = '12345678'
|
||||
var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
|
||||
var SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner'
|
||||
|
||||
var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
|
||||
const PASSWORD = '12345678'
|
||||
const FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
|
||||
const BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
|
||||
const SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner'
|
||||
|
||||
QUnit.module('Old Style Vaults', {
|
||||
beforeEach: function () {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault)
|
||||
|
||||
this.configManager = new ConfigManager({
|
||||
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
||||
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
||||
})
|
||||
|
||||
this.migrator = new IdStoreMigrator({
|
||||
configManager: this.configManager,
|
||||
})
|
||||
let managers = managersFromInitState(oldStyleVault)
|
||||
|
||||
this.configManager = managers.configManager
|
||||
this.migrator = managers.migrator
|
||||
}
|
||||
})
|
||||
|
||||
@ -37,6 +30,7 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||
|
||||
this.migrator.migratedVaultForPassword(PASSWORD)
|
||||
.then((result) => {
|
||||
assert.ok(result, 'migratedVaultForPassword returned result')
|
||||
const { serialized, lostAccounts } = result
|
||||
assert.equal(serialized.data.mnemonic, SEED, 'seed phrase recovered')
|
||||
assert.equal(lostAccounts.length, 0, 'no lost accounts')
|
||||
@ -46,16 +40,10 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||
|
||||
QUnit.module('Old Style Vaults with bad HD seed', {
|
||||
beforeEach: function () {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault)
|
||||
|
||||
this.configManager = new ConfigManager({
|
||||
loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
|
||||
setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
|
||||
})
|
||||
|
||||
this.migrator = new IdStoreMigrator({
|
||||
configManager: this.configManager,
|
||||
})
|
||||
let managers = managersFromInitState(badStyleVault)
|
||||
|
||||
this.configManager = managers.configManager
|
||||
this.migrator = managers.migrator
|
||||
}
|
||||
})
|
||||
|
||||
@ -64,6 +52,7 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||
|
||||
this.migrator.migratedVaultForPassword(PASSWORD)
|
||||
.then((result) => {
|
||||
assert.ok(result, 'migratedVaultForPassword returned result')
|
||||
const { serialized, lostAccounts } = result
|
||||
|
||||
assert.equal(lostAccounts.length, 1, 'one lost account')
|
||||
@ -89,3 +78,15 @@ QUnit.test('migrator:migratedVaultForPassword', function (assert) {
|
||||
})
|
||||
})
|
||||
|
||||
function managersFromInitState(initState){
|
||||
|
||||
let configManager = new ConfigManager({
|
||||
store: new ObservableStore(initState),
|
||||
})
|
||||
|
||||
let migrator = new IdStoreMigrator({
|
||||
configManager: configManager,
|
||||
})
|
||||
|
||||
return { configManager, migrator }
|
||||
}
|
@ -1,58 +1,10 @@
|
||||
var ConfigManager = require('../../app/scripts/lib/config-manager')
|
||||
const ObservableStore = require('obs-store')
|
||||
const clone = require('clone')
|
||||
const ConfigManager = require('../../app/scripts/lib/config-manager')
|
||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
const extend = require('xtend')
|
||||
|
||||
module.exports = function() {
|
||||
return new ConfigManager({ loadData, setData })
|
||||
}
|
||||
|
||||
function loadData () {
|
||||
var oldData = getOldStyleData()
|
||||
var newData
|
||||
|
||||
try {
|
||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
||||
} catch (e) {}
|
||||
|
||||
var data = extend({
|
||||
meta: {
|
||||
version: 0,
|
||||
},
|
||||
data: {
|
||||
config: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
},
|
||||
},
|
||||
},
|
||||
}, oldData || null, newData || null)
|
||||
return data
|
||||
}
|
||||
|
||||
function getOldStyleData () {
|
||||
var config, wallet, seedWords
|
||||
|
||||
var result = {
|
||||
meta: { version: 0 },
|
||||
data: {},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function setData (data) {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
|
||||
}
|
||||
let store = new ObservableStore(clone(firstTimeState))
|
||||
return new ConfigManager({ store })
|
||||
}
|
@ -1,24 +1,23 @@
|
||||
// polyfill fetch
|
||||
global.fetch = global.fetch || require('isomorphic-fetch')
|
||||
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const rp = require('request-promise')
|
||||
const nock = require('nock')
|
||||
const configManagerGen = require('../lib/mock-config-manager')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
|
||||
describe('config-manager', function() {
|
||||
var configManager
|
||||
|
||||
beforeEach(function() {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
configManager = configManagerGen()
|
||||
})
|
||||
|
||||
describe('currency conversions', function() {
|
||||
|
||||
describe('#getCurrentFiat', function() {
|
||||
it('should return false if no previous key exists', function() {
|
||||
it('should return undefined if no previous key exists', function() {
|
||||
var result = configManager.getCurrentFiat()
|
||||
assert.ok(!result)
|
||||
})
|
||||
@ -26,14 +25,14 @@ describe('config-manager', function() {
|
||||
|
||||
describe('#setCurrentFiat', function() {
|
||||
it('should make getCurrentFiat return true once set', function() {
|
||||
assert.equal(configManager.getCurrentFiat(), false)
|
||||
assert.equal(configManager.getCurrentFiat(), undefined)
|
||||
configManager.setCurrentFiat('USD')
|
||||
var result = configManager.getCurrentFiat()
|
||||
assert.equal(result, 'USD')
|
||||
})
|
||||
|
||||
it('should work with other currencies as well', function() {
|
||||
assert.equal(configManager.getCurrentFiat(), false)
|
||||
assert.equal(configManager.getCurrentFiat(), undefined)
|
||||
configManager.setCurrentFiat('JPY')
|
||||
var result = configManager.getCurrentFiat()
|
||||
assert.equal(result, 'JPY')
|
||||
@ -41,7 +40,7 @@ describe('config-manager', function() {
|
||||
})
|
||||
|
||||
describe('#getConversionRate', function() {
|
||||
it('should return false if non-existent', function() {
|
||||
it('should return undefined if non-existent', function() {
|
||||
var result = configManager.getConversionRate()
|
||||
assert.ok(!result)
|
||||
})
|
||||
@ -54,7 +53,7 @@ describe('config-manager', function() {
|
||||
.get('/api/ticker/eth-USD')
|
||||
.reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
|
||||
|
||||
assert.equal(configManager.getConversionRate(), false)
|
||||
assert.equal(configManager.getConversionRate(), 0)
|
||||
var promise = new Promise(
|
||||
function (resolve, reject) {
|
||||
configManager.setCurrentFiat('USD')
|
||||
@ -75,7 +74,7 @@ describe('config-manager', function() {
|
||||
|
||||
it('should work for JPY as well.', function() {
|
||||
this.timeout(15000)
|
||||
assert.equal(configManager.getConversionRate(), false)
|
||||
assert.equal(configManager.getConversionRate(), 0)
|
||||
|
||||
var jpyMock = nock('https://www.cryptonator.com')
|
||||
.get('/api/ticker/eth-JPY')
|
||||
@ -103,7 +102,7 @@ describe('config-manager', function() {
|
||||
describe('confirmation', function() {
|
||||
|
||||
describe('#getConfirmedDisclaimer', function() {
|
||||
it('should return false if no previous key exists', function() {
|
||||
it('should return undefined if no previous key exists', function() {
|
||||
var result = configManager.getConfirmedDisclaimer()
|
||||
assert.ok(!result)
|
||||
})
|
||||
@ -111,16 +110,16 @@ describe('config-manager', function() {
|
||||
|
||||
describe('#setConfirmedDisclaimer', function() {
|
||||
it('should make getConfirmedDisclaimer return true once set', function() {
|
||||
assert.equal(configManager.getConfirmedDisclaimer(), false)
|
||||
assert.equal(configManager.getConfirmedDisclaimer(), undefined)
|
||||
configManager.setConfirmedDisclaimer(true)
|
||||
var result = configManager.getConfirmedDisclaimer()
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should be able to set false', function() {
|
||||
configManager.setConfirmedDisclaimer(false)
|
||||
it('should be able to set undefined', function() {
|
||||
configManager.setConfirmedDisclaimer(undefined)
|
||||
var result = configManager.getConfirmedDisclaimer()
|
||||
assert.equal(result, false)
|
||||
assert.equal(result, undefined)
|
||||
})
|
||||
|
||||
it('should persist to local storage', function() {
|
||||
@ -132,7 +131,6 @@ describe('config-manager', function() {
|
||||
})
|
||||
|
||||
describe('#setConfig', function() {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
|
||||
it('should set the config key', function () {
|
||||
var testConfig = {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const async = require('async')
|
||||
const assert = require('assert')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const ConfigManager = require('../../app/scripts/lib/config-manager')
|
||||
@ -42,10 +43,9 @@ describe('IdentityStore to KeyringController migration', function() {
|
||||
beforeEach(function(done) {
|
||||
this.sinon = sinon.sandbox.create()
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
configManager = new ConfigManager({
|
||||
loadData,
|
||||
setData: (d) => { window.localStorage = d }
|
||||
})
|
||||
let store = new ObservableStore(loadData())
|
||||
store.subscribe(setData)
|
||||
configManager = new ConfigManager({ store })
|
||||
|
||||
|
||||
idStore = new IdentityStore({
|
||||
|
@ -10,9 +10,11 @@ describe('MetaMaskController', function() {
|
||||
showUnconfirmedMessage: noop,
|
||||
unlockAccountMessage: noop,
|
||||
showUnapprovedTx: noop,
|
||||
setData,
|
||||
loadData,
|
||||
// initial state
|
||||
initState: loadData(),
|
||||
})
|
||||
// setup state persistence
|
||||
controller.store.subscribe(setData)
|
||||
|
||||
beforeEach(function() {
|
||||
// sinon allows stubbing methods that are easily verified
|
||||
|
@ -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'))
|
||||
|
||||
const oldTestRpc = 'https://rawtestrpc.metamask.io/'
|
||||
const newTestRpc = 'https://testrpc.metamask.io/'
|
||||
|
||||
describe('wallet1 is migrated successfully', function() {
|
||||
|
||||
it('should convert providers', function(done) {
|
||||
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')
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user