mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #81 from MetaMask/MigratableConfig
Migratable config
This commit is contained in:
commit
f451da67a7
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,4 +8,5 @@ app/bower_components
|
||||
test/bower_components
|
||||
package
|
||||
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
builds/
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"manifest_version": 2,
|
||||
"description": "__MSG_appDescription__",
|
||||
"icons": {
|
||||
|
@ -9,8 +9,7 @@ const PortStream = require('./lib/port-stream.js')
|
||||
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
||||
const IdentityStore = require('./lib/idStore')
|
||||
const createTxNotification = require('./lib/tx-notification.js')
|
||||
|
||||
console.log('ready to roll')
|
||||
const configManager = require('./lib/config-manager-singleton')
|
||||
|
||||
//
|
||||
// connect to other contexts
|
||||
@ -37,10 +36,9 @@ function handleEthRpcRequestStream(stream){
|
||||
// state and network
|
||||
//
|
||||
|
||||
var config = getConfig()
|
||||
var idStore = new IdentityStore()
|
||||
var zeroClient = MetaMaskProvider({
|
||||
rpcUrl: config.rpcTarget,
|
||||
rpcUrl: configManager.getCurrentRpcAddress(),
|
||||
getAccounts: function(cb){
|
||||
var selectedAddress = idStore.getSelectedAddress()
|
||||
var result = selectedAddress ? [selectedAddress] : []
|
||||
@ -62,7 +60,7 @@ function getState(){
|
||||
var state = extend(
|
||||
ethStore.getState(),
|
||||
idStore.getState(),
|
||||
getConfig()
|
||||
configManager.getConfig()
|
||||
)
|
||||
return state
|
||||
}
|
||||
@ -177,22 +175,10 @@ function addUnconfirmedTx(txParams, cb){
|
||||
|
||||
// called from popup
|
||||
function setRpcTarget(rpcTarget){
|
||||
var config = getConfig()
|
||||
config.rpcTarget = rpcTarget
|
||||
setConfig(config)
|
||||
configManager.setRpcTarget(rpcTarget)
|
||||
chrome.runtime.reload()
|
||||
}
|
||||
|
||||
function getConfig(){
|
||||
return extend({
|
||||
rpcTarget: 'https://rawtestrpc.metamask.io/',
|
||||
}, JSON.parse(localStorage['config'] || '{}'))
|
||||
}
|
||||
|
||||
function setConfig(state){
|
||||
localStorage['config'] = JSON.stringify(state)
|
||||
}
|
||||
|
||||
// util
|
||||
|
||||
function jsonParseStream(){
|
||||
|
3
app/scripts/lib/config-manager-singleton.js
Normal file
3
app/scripts/lib/config-manager-singleton.js
Normal file
@ -0,0 +1,3 @@
|
||||
var ConfigManager = require('./config-manager')
|
||||
|
||||
module.exports = new ConfigManager()
|
157
app/scripts/lib/config-manager.js
Normal file
157
app/scripts/lib/config-manager.js
Normal file
@ -0,0 +1,157 @@
|
||||
const Migrator = require('pojo-migrator')
|
||||
const extend = require('xtend')
|
||||
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
var DEFAULT_RPC = 'https://rawtestrpc.metamask.io/'
|
||||
|
||||
/* The config-manager is a convenience object
|
||||
* wrapping a pojo-migrator.
|
||||
*
|
||||
* It exists mostly to allow the creation of
|
||||
* convenience methods to access and persist
|
||||
* particular portions of the state.
|
||||
*/
|
||||
module.exports = ConfigManager
|
||||
function ConfigManager() {
|
||||
|
||||
/* 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: [],
|
||||
|
||||
// How to load initial config.
|
||||
// Includes step on migrating pre-pojo-migrator data.
|
||||
loadData: loadData,
|
||||
|
||||
// How to persist migrated config.
|
||||
setData: function(data) {
|
||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setConfig = function(config) {
|
||||
var data = this.migrator.getData()
|
||||
data.config = config
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setRpcTarget = function(rpcUrl) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'rpc',
|
||||
rpcTarget: rpcUrl,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getConfig = function() {
|
||||
var data = this.migrator.getData()
|
||||
if ('config' in data) {
|
||||
return data.config
|
||||
} else {
|
||||
return {
|
||||
provider: {
|
||||
type: 'rpc',
|
||||
rpcTarget: DEFAULT_RPC,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setData = function(data) {
|
||||
this.migrator.saveData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getData = function() {
|
||||
return this.migrator.getData()
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setWallet = function(wallet) {
|
||||
var data = this.migrator.getData()
|
||||
data.wallet = wallet
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getWallet = function() {
|
||||
return this.migrator.getData().wallet
|
||||
}
|
||||
|
||||
// Takes a boolean
|
||||
ConfigManager.prototype.setShowSeedWords = function(should) {
|
||||
var data = this.migrator.getData()
|
||||
data.showSeedWords = should
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getShouldShowSeedWords = function() {
|
||||
var data = this.migrator.getData()
|
||||
return data.showSeedWords
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getCurrentRpcAddress = function() {
|
||||
var config = this.getConfig()
|
||||
if (!config) return null
|
||||
return config.provider && config.provider.rpcTarget ? config.provider.rpcTarget : DEFAULT_RPC
|
||||
}
|
||||
|
||||
ConfigManager.prototype.clearWallet = function() {
|
||||
var data = this.getConfig()
|
||||
delete data.wallet
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
|
||||
var oldData = getOldStyleData()
|
||||
var newData
|
||||
try {
|
||||
newData = JSON.parse(window.localStorage[STORAGE_KEY])
|
||||
} catch (e) {}
|
||||
|
||||
var data = extend({
|
||||
version: 0,
|
||||
data: {
|
||||
config: {
|
||||
rpcTarget: DEFAULT_RPC,
|
||||
}
|
||||
}
|
||||
}, oldData ? oldData : null, newData ? 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
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ const clone = require('clone')
|
||||
const extend = require('xtend')
|
||||
const createId = require('web3-provider-engine/util/random-id')
|
||||
const autoFaucet = require('./auto-faucet')
|
||||
const configManager = require('./config-manager-singleton')
|
||||
|
||||
|
||||
module.exports = IdentityStore
|
||||
@ -41,14 +42,16 @@ function IdentityStore(ethStore) {
|
||||
|
||||
IdentityStore.prototype.createNewVault = function(password, entropy, cb){
|
||||
delete this._keyStore
|
||||
delete window.localStorage['lightwallet']
|
||||
configManager.clearWallet()
|
||||
this._createIdmgmt(password, null, entropy, (err) => {
|
||||
if (err) return cb(err)
|
||||
var seedWords = this._idmgmt.getSeed()
|
||||
this._cacheSeedWordsUntilConfirmed(seedWords)
|
||||
|
||||
this._loadIdentities()
|
||||
this._didUpdate()
|
||||
this._autoFaucet()
|
||||
|
||||
configManager.setShowSeedWords(true)
|
||||
var seedWords = this._idmgmt.getSeed()
|
||||
cb(null, seedWords)
|
||||
})
|
||||
}
|
||||
@ -68,19 +71,28 @@ IdentityStore.prototype.setStore = function(store){
|
||||
}
|
||||
|
||||
IdentityStore.prototype.clearSeedWordCache = function(cb) {
|
||||
delete window.localStorage['seedWords']
|
||||
configManager.setShowSeedWords(false)
|
||||
cb()
|
||||
}
|
||||
|
||||
IdentityStore.prototype.getState = function(){
|
||||
const cachedSeeds = window.localStorage['seedWords']
|
||||
var seedWords = this.getSeedIfUnlocked()
|
||||
var wallet = configManager.getWallet()
|
||||
return clone(extend(this._currentState, {
|
||||
isInitialized: !!window.localStorage['lightwallet'] && !cachedSeeds,
|
||||
isInitialized: !!configManager.getWallet() && !seedWords,
|
||||
isUnlocked: this._isUnlocked(),
|
||||
seedWords: cachedSeeds,
|
||||
seedWords: seedWords,
|
||||
}))
|
||||
}
|
||||
|
||||
IdentityStore.prototype.getSeedIfUnlocked = function() {
|
||||
var showSeed = configManager.getShouldShowSeedWords()
|
||||
var idmgmt = this._idmgmt
|
||||
var shouldShow = showSeed && !!idmgmt
|
||||
var seedWords = shouldShow ? idmgmt.getSeed() : null
|
||||
return seedWords
|
||||
}
|
||||
|
||||
IdentityStore.prototype.getSelectedAddress = function(){
|
||||
return this._currentState.selectedAddress
|
||||
}
|
||||
@ -181,10 +193,6 @@ IdentityStore.prototype._isUnlocked = function(){
|
||||
return result
|
||||
}
|
||||
|
||||
IdentityStore.prototype._cacheSeedWordsUntilConfirmed = function(seedWords) {
|
||||
window.localStorage['seedWords'] = seedWords
|
||||
}
|
||||
|
||||
// load identities from keyStoreet
|
||||
IdentityStore.prototype._loadIdentities = function(){
|
||||
if (!this._isUnlocked()) throw new Error('not unlocked')
|
||||
@ -216,14 +224,14 @@ IdentityStore.prototype._createIdmgmt = function(password, seed, entropy, cb){
|
||||
var keyStore = null
|
||||
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
|
||||
if (err) return cb(err)
|
||||
var serializedKeystore = window.localStorage['lightwallet']
|
||||
var serializedKeystore = configManager.getWallet()
|
||||
|
||||
if (seed) {
|
||||
keyStore = this._restoreFromSeed(password, seed, derivedKey)
|
||||
|
||||
// returning user, recovering from localStorage
|
||||
// returning user, recovering from storage
|
||||
} else if (serializedKeystore) {
|
||||
keyStore = this._loadFromLocalStorage(serializedKeystore, derivedKey, cb)
|
||||
keyStore = LightwalletKeyStore.deserialize(serializedKeystore)
|
||||
var isCorrect = keyStore.isDerivedKeyCorrect(derivedKey)
|
||||
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect'))
|
||||
|
||||
@ -249,15 +257,11 @@ IdentityStore.prototype._restoreFromSeed = function(password, seed, derivedKey)
|
||||
keyStore.setDefaultHdDerivationPath(this.hdPathString)
|
||||
|
||||
keyStore.generateNewAddress(derivedKey, 3)
|
||||
window.localStorage['lightwallet'] = keyStore.serialize()
|
||||
console.log('restored from seed. saved to keystore localStorage')
|
||||
configManager.setWallet(keyStore.serialize())
|
||||
console.log('restored from seed. saved to keystore')
|
||||
return keyStore
|
||||
}
|
||||
|
||||
IdentityStore.prototype._loadFromLocalStorage = function(serializedKeystore, derivedKey) {
|
||||
return LightwalletKeyStore.deserialize(serializedKeystore)
|
||||
}
|
||||
|
||||
IdentityStore.prototype._createFirstWallet = function(entropy, derivedKey) {
|
||||
var secretSeed = LightwalletKeyStore.generateRandomSeed(entropy)
|
||||
var keyStore = new LightwalletKeyStore(secretSeed, derivedKey, this.hdPathString)
|
||||
@ -265,8 +269,8 @@ IdentityStore.prototype._createFirstWallet = function(entropy, derivedKey) {
|
||||
keyStore.setDefaultHdDerivationPath(this.hdPathString)
|
||||
|
||||
keyStore.generateNewAddress(derivedKey, 3)
|
||||
window.localStorage['lightwallet'] = keyStore.serialize()
|
||||
console.log('saved to keystore localStorage')
|
||||
configManager.setWallet(keyStore.serialize())
|
||||
console.log('saved to keystore')
|
||||
return keyStore
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "gulp dev",
|
||||
"test": "mocha --compilers js:babel-register --recursive"
|
||||
"test": "mocha --require test/helper.js --compilers js:babel-register --recursive",
|
||||
"watch": "mocha watch --compilers js:babel-register --recursive"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.5.2",
|
||||
@ -21,6 +22,7 @@
|
||||
"inject-css": "^0.1.1",
|
||||
"metamask-ui": "^1.5.0",
|
||||
"multiplex": "^6.7.0",
|
||||
"pojo-migrator": "^2.1.0",
|
||||
"pumpify": "^1.3.4",
|
||||
"readable-stream": "^2.0.5",
|
||||
"through2": "^2.0.1",
|
||||
@ -39,10 +41,10 @@
|
||||
"gulp-util": "^3.0.7",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"jsdom": "^8.1.0",
|
||||
"jsdom-global": "^1.7.0",
|
||||
"jshint-stylish": "~0.1.5",
|
||||
"lodash.assign": "^4.0.6",
|
||||
"mocha": "^2.4.5",
|
||||
"mocha-jsdom": "^1.1.0",
|
||||
"mocha-sinon": "^1.1.5",
|
||||
"sinon": "^1.17.3",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
|
@ -1,4 +1,2 @@
|
||||
require('mocha-sinon')()
|
||||
var jsdom = require('mocha-jsdom')
|
||||
jsdom()
|
||||
|
||||
require('jsdom-global')()
|
||||
window.localStorage = {}
|
||||
|
71
test/unit/config-manager-test.js
Normal file
71
test/unit/config-manager-test.js
Normal file
@ -0,0 +1,71 @@
|
||||
var assert = require('assert')
|
||||
var ConfigManager = require('../../app/scripts/lib/config-manager')
|
||||
var configManager
|
||||
|
||||
describe('config-manager', function() {
|
||||
|
||||
before(function() {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
configManager = new ConfigManager()
|
||||
})
|
||||
|
||||
describe('#setConfig', function() {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
|
||||
it('should set the config key', function () {
|
||||
var testConfig = {
|
||||
provider: {
|
||||
type: 'rpc',
|
||||
rpcTarget: 'foobar'
|
||||
}
|
||||
}
|
||||
configManager.setConfig(testConfig)
|
||||
var result = configManager.getData()
|
||||
|
||||
assert.equal(result.config.provider.type, testConfig.provider.type)
|
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget)
|
||||
})
|
||||
|
||||
it('setting wallet should not overwrite config', function() {
|
||||
var testConfig = {
|
||||
provider: {
|
||||
type: 'rpc',
|
||||
rpcTarget: 'foobar'
|
||||
}
|
||||
}
|
||||
configManager.setConfig(testConfig)
|
||||
|
||||
var testWallet = {
|
||||
name: 'this is my fake wallet'
|
||||
}
|
||||
configManager.setWallet(testWallet)
|
||||
|
||||
var result = configManager.getData()
|
||||
assert.equal(result.wallet.name, testWallet.name, 'wallet name is set')
|
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget)
|
||||
|
||||
testConfig.provider.type = 'something else!'
|
||||
configManager.setConfig(testConfig)
|
||||
|
||||
result = configManager.getData()
|
||||
assert.equal(result.wallet.name, testWallet.name, 'wallet name is set')
|
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget)
|
||||
assert.equal(result.config.provider.type, testConfig.provider.type)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rpc manipulations', function() {
|
||||
it('changing rpc should return a different rpc', function() {
|
||||
var firstRpc = 'first'
|
||||
var secondRpc = 'second'
|
||||
|
||||
configManager.setRpcTarget(firstRpc)
|
||||
var firstResult = configManager.getCurrentRpcAddress()
|
||||
assert.equal(firstResult, firstRpc)
|
||||
|
||||
configManager.setRpcTarget(secondRpc)
|
||||
var secondResult = configManager.getCurrentRpcAddress()
|
||||
assert.equal(secondResult, secondRpc)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user