From 931ae5f64a233b472c3dada8aa6af77e0bffad5e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 20 Dec 2016 13:53:14 -0800 Subject: Make notices confirmation configurable - Confirm button will now dismiss the lost accounts array. --- app/scripts/keyring-controller.js | 1 - app/scripts/metamask-controller.js | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 2888e58a9..61ee56638 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -114,7 +114,6 @@ module.exports = class KeyringController extends EventEmitter { conversionDate: this.configManager.getConversionDate(), keyringTypes: this.keyringTypes.map(krt => krt.type), identities: this.identities, - lostAccounts: this.configManager.getLostAccounts(), } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 4b8fa4323..edb25d300 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -52,7 +52,9 @@ module.exports = class MetamaskController { this.ethStore.getState(), this.configManager.getConfig(), this.keyringController.getState(), - this.noticeController.getState() + this.noticeController.getState(), { + lostAccounts: this.configManager.getLostAccounts(), + } ) } @@ -71,6 +73,7 @@ module.exports = class MetamaskController { setTOSHash: this.setTOSHash.bind(this), checkTOSChange: this.checkTOSChange.bind(this), setGasMultiplier: this.setGasMultiplier.bind(this), + markAccountsFound: this.markAccountsFound.bind(this), // forward directly to keyringController createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController), @@ -410,4 +413,16 @@ module.exports = class MetamaskController { getStateNetwork () { return this.state.network } + + markAccountsFound(cb) { + this.configManager.setLostAccounts([]) + this.keyringController.getAccounts() + .then((accounts) => { + return this.keyringController.setSelectedAccount(accounts[0]) + }) + .then(() => { + this.sendUpdate() + cb(null, this.getState()) + }) + } } -- cgit From 48f2ae2154b6e804ee60cfc1235025c128a6cfa8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 21 Dec 2016 11:01:04 -0800 Subject: Move old keystore migration code to metamask controller Allows keyring controller to be more generic, less opinionated, and who knows, maybe sooner publishable as its own thing. --- app/scripts/keyring-controller.js | 46 +---------------------------------- app/scripts/metamask-controller.js | 50 +++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 46 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 61ee56638..1786f7c24 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -9,7 +9,6 @@ const encryptor = require('browser-passworder') const normalize = require('./lib/sig-util').normalize const messageManager = require('./lib/message-manager') -const IdStoreMigrator = require('./lib/idStore-migrator') const BN = ethUtil.BN // Keyrings: @@ -45,11 +44,6 @@ module.exports = class KeyringController extends EventEmitter { this._unconfMsgCbs = {} this.getNetwork = opts.getNetwork - - // TEMPORARY UNTIL FULL DEPRECATION: - this.idStoreMigrator = new IdStoreMigrator({ - configManager: this.configManager, - }) } // Set Store @@ -221,10 +215,7 @@ module.exports = class KeyringController extends EventEmitter { // Temporarily also migrates any old-style vaults first, as well. // (Pre MetaMask 3.0.0) submitPassword (password) { - return this.migrateOldVaultIfAny(password) - .then(() => { - return this.unlockKeyrings(password) - }) + return this.unlockKeyrings(password) .then((keyrings) => { this.keyrings = keyrings return this.fullUpdate() @@ -610,41 +601,6 @@ module.exports = class KeyringController extends EventEmitter { // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER // AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS. - // Migrate Old Vault If Any - // @string password - // - // returns Promise() - // - // Temporary step used when logging in. - // Checks if old style (pre-3.0.0) Metamask Vault exists. - // If so, persists that vault in the new vault format - // with the provided password, so the other unlock steps - // may be completed without interruption. - migrateOldVaultIfAny (password) { - const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault() - if (!shouldMigrate) { - return Promise.resolve() - } - - return this.idStoreMigrator.migratedVaultForPassword(password) - .then((result) => { - this.password = password - - if (result && shouldMigrate) { - const { serialized, lostAccounts } = result - this.configManager.setLostAccounts(lostAccounts) - return this.restoreKeyring(serialized) - .then(keyring => keyring.getAccounts()) - .then((accounts) => { - this.configManager.setSelectedAccount(accounts[0]) - return this.persistAllKeyrings() - }) - } else { - return Promise.resolve() - } - }) - } - // Create First Key Tree // returns @Promise // diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index edb25d300..0c772536d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -10,6 +10,7 @@ const ConfigManager = require('./lib/config-manager') const extension = require('./lib/extension') const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') +const IdStoreMigrator = require('./lib/idStore-migrator') module.exports = class MetamaskController { @@ -44,6 +45,11 @@ module.exports = class MetamaskController { this.checkTOSChange() this.scheduleConversionInterval() + + // TEMPORARY UNTIL FULL DEPRECATION: + this.idStoreMigrator = new IdStoreMigrator({ + configManager: this.configManager, + }) } getState () { @@ -61,6 +67,7 @@ module.exports = class MetamaskController { getApi () { const keyringController = this.keyringController const noticeController = this.noticeController + const submitPassword = keyringController.submitPassword.bind(keyringController) return { getState: (cb) => { cb(null, this.getState()) }, @@ -81,7 +88,12 @@ module.exports = class MetamaskController { placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController), clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController), setLocked: nodeify(keyringController.setLocked).bind(keyringController), - submitPassword: nodeify(keyringController.submitPassword).bind(keyringController), + submitPassword: (password, cb) => { + this.migrateOldVaultIfAny() + .then(submitPassword) + .then((newState) => { cb(null, newState) }) + .catch((reason) => { cb(reason) }) + }, addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController), @@ -425,4 +437,40 @@ module.exports = class MetamaskController { cb(null, this.getState()) }) } + + // Migrate Old Vault If Any + // @string password + // + // returns Promise() + // + // Temporary step used when logging in. + // Checks if old style (pre-3.0.0) Metamask Vault exists. + // If so, persists that vault in the new vault format + // with the provided password, so the other unlock steps + // may be completed without interruption. + migrateOldVaultIfAny (password) { + const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault() + if (!shouldMigrate) { + return Promise.resolve(password) + } + + return this.idStoreMigrator.migratedVaultForPassword(password) + .then((result) => { + if (result && shouldMigrate) { + const { serialized, lostAccounts } = result + this.configManager.setLostAccounts(lostAccounts) + return this.keyringController.restoreKeyring(serialized) + .then(keyring => keyring.getAccounts()) + .then((accounts) => { + this.configManager.setSelectedAccount(accounts[0]) + return this.keyringController.persistAllKeyrings() + .then(() => password) + }) + } else { + return Promise.resolve(password) + } + }) + } + + } -- cgit From 05ce7086f78e344aee23bb95de56d28317074a7d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 21 Dec 2016 17:19:53 -0800 Subject: Added error when trying to unlock uninitialized vault --- app/scripts/keyring-controller.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 1786f7c24..4e9193ab2 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -721,6 +721,10 @@ module.exports = class KeyringController extends EventEmitter { // initializing the persisted keyrings to RAM. unlockKeyrings (password) { const encryptedVault = this.configManager.getVault() + if (!encryptedVault) { + throw new Error('Cannot unlock without a previous vault.') + } + return this.encryptor.decrypt(password, encryptedVault) .then((vault) => { this.password = password -- cgit From a3a64afdd5b2ed6bbed8c8dba5cc0f14d4a1ad99 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 21 Dec 2016 17:20:14 -0800 Subject: Return undefined when vault is uninitialized --- app/scripts/lib/config-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index d36ccf0db..ede877b76 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -121,7 +121,7 @@ ConfigManager.prototype.setVault = function (encryptedString) { ConfigManager.prototype.getVault = function () { var data = this.getData() - return ('vault' in data) && data.vault + return data.vault } ConfigManager.prototype.getKeychains = function () { -- cgit From ebeaf3b3d6cf79e2e0f251b9b2da765f0fe6d0e1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 21 Dec 2016 17:21:10 -0800 Subject: Restructured migration Migrator now returns a lostAccount array that includes objects these objects include keys of address and privateKey, this allows the MetamaskController to restore the lost accounts even without customizing the idStore or the KeyringController. Also includes a patch that allows idStore to synchronously export private keys. --- app/scripts/lib/idStore-migrator.js | 7 ++++- app/scripts/lib/idStore.js | 3 +- app/scripts/metamask-controller.js | 62 +++++++++++++++++++++++++------------ 3 files changed, 51 insertions(+), 21 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js index 2e9418376..655aed0af 100644 --- a/app/scripts/lib/idStore-migrator.js +++ b/app/scripts/lib/idStore-migrator.js @@ -63,7 +63,12 @@ module.exports = class IdentityStoreMigrator { return { serialized, - lostAccounts, + lostAccounts: lostAccounts.map((address) => { + return { + address, + privateKey: this.idStore.exportAccount(address), + } + }), } }) } diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index cf4353e48..66e5d966c 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -202,7 +202,8 @@ IdentityStore.prototype.submitPassword = function (password, cb) { IdentityStore.prototype.exportAccount = function (address, cb) { var privateKey = this._idmgmt.exportPrivateKey(address) - cb(null, privateKey) + if (cb) cb(null, privateKey) + return privateKey } // diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0c772536d..493ee661a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -67,7 +67,6 @@ module.exports = class MetamaskController { getApi () { const keyringController = this.keyringController const noticeController = this.noticeController - const submitPassword = keyringController.submitPassword.bind(keyringController) return { getState: (cb) => { cb(null, this.getState()) }, @@ -89,10 +88,10 @@ module.exports = class MetamaskController { clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController), setLocked: nodeify(keyringController.setLocked).bind(keyringController), submitPassword: (password, cb) => { - this.migrateOldVaultIfAny() - .then(submitPassword) - .then((newState) => { cb(null, newState) }) - .catch((reason) => { cb(reason) }) + this.migrateOldVaultIfAny(password) + .then(keyringController.submitPassword.bind(keyringController)) + .then((newState) => { console.log('succeeded submitting!'); cb(null, newState) }) + .catch((reason) => { console.error(reason); cb(reason) }) }, addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), @@ -456,21 +455,46 @@ module.exports = class MetamaskController { return this.idStoreMigrator.migratedVaultForPassword(password) .then((result) => { - if (result && shouldMigrate) { - const { serialized, lostAccounts } = result - this.configManager.setLostAccounts(lostAccounts) - return this.keyringController.restoreKeyring(serialized) - .then(keyring => keyring.getAccounts()) - .then((accounts) => { - this.configManager.setSelectedAccount(accounts[0]) - return this.keyringController.persistAllKeyrings() - .then(() => password) - }) - } else { - return Promise.resolve(password) + + this.keyringController.password = password + const { serialized, lostAccounts } = result + + // Restore the correct accounts first: + return this.keyringController.restoreKeyring(serialized) + .then(keyring => keyring.getAccounts()) + .then((accounts) => { + this.configManager.setSelectedAccount(accounts[0]) + return result + }) + + }).then((result) => { + + // Now we restore any lost accounts: + const { serialized, lostAccounts } = result + if (result && lostAccounts) { + this.configManager.setLostAccounts(lostAccounts.map((acct) => acct.address)) + return this.importLostAccounts(result) } - }) - } + return Promise.resolve(result) + }).then(() => { + + // Persist all these newly restored items to disk: + return this.keyringController.persistAllKeyrings() + // Ultimately pass the password back for normal unlocking: + }).then((result) => password) + } + // IMPORT LOST ACCOUNTS + // @Object with key lostAccounts: @Array accounts <{ address, privateKey }> + // Uses the array's private keys to create a new Simple Key Pair keychain + // and add it to the keyring controller. + importLostAccounts (result) { + const { serialized, lostAccounts } = result + const privKeys = lostAccounts.map(acct => acct.privateKey) + return this.keyringController.restoreKeyring({ + type: 'Simple Key Pair', + data: privKeys, + }) + } } -- cgit From 0df656850de606b82b4299fffdc812f45a252b69 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 21 Dec 2016 17:30:10 -0800 Subject: Linted --- app/scripts/metamask-controller.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 493ee661a..2dac61a04 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -457,7 +457,7 @@ module.exports = class MetamaskController { .then((result) => { this.keyringController.password = password - const { serialized, lostAccounts } = result + const { serialized } = result // Restore the correct accounts first: return this.keyringController.restoreKeyring(serialized) @@ -470,7 +470,7 @@ module.exports = class MetamaskController { }).then((result) => { // Now we restore any lost accounts: - const { serialized, lostAccounts } = result + const { lostAccounts } = result if (result && lostAccounts) { this.configManager.setLostAccounts(lostAccounts.map((acct) => acct.address)) return this.importLostAccounts(result) @@ -489,8 +489,7 @@ module.exports = class MetamaskController { // @Object with key lostAccounts: @Array accounts <{ address, privateKey }> // Uses the array's private keys to create a new Simple Key Pair keychain // and add it to the keyring controller. - importLostAccounts (result) { - const { serialized, lostAccounts } = result + importLostAccounts ({ lostAccounts }) { const privKeys = lostAccounts.map(acct => acct.privateKey) return this.keyringController.restoreKeyring({ type: 'Simple Key Pair', -- cgit From cf3951c9dfcc42387b165ae76e0a2abaece06b6c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 22 Dec 2016 13:40:12 -0800 Subject: Remove logs --- app/scripts/metamask-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2dac61a04..8b7713df4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -90,8 +90,8 @@ module.exports = class MetamaskController { submitPassword: (password, cb) => { this.migrateOldVaultIfAny(password) .then(keyringController.submitPassword.bind(keyringController)) - .then((newState) => { console.log('succeeded submitting!'); cb(null, newState) }) - .catch((reason) => { console.error(reason); cb(reason) }) + .then((newState) => { cb(null, newState) }) + .catch((reason) => { cb(reason) }) }, addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), -- cgit From 291403c13f0c8f61f29585cc9a80ed618718f60f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 22 Dec 2016 13:41:31 -0800 Subject: Don't bother changing selected accounts since accounts are recovered --- app/scripts/metamask-controller.js | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8b7713df4..ab4cb8ed8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -427,14 +427,8 @@ module.exports = class MetamaskController { markAccountsFound(cb) { this.configManager.setLostAccounts([]) - this.keyringController.getAccounts() - .then((accounts) => { - return this.keyringController.setSelectedAccount(accounts[0]) - }) - .then(() => { - this.sendUpdate() - cb(null, this.getState()) - }) + this.sendUpdate() + cb(null, this.getState()) } // Migrate Old Vault If Any @@ -461,11 +455,7 @@ module.exports = class MetamaskController { // Restore the correct accounts first: return this.keyringController.restoreKeyring(serialized) - .then(keyring => keyring.getAccounts()) - .then((accounts) => { - this.configManager.setSelectedAccount(accounts[0]) - return result - }) + .then(() => result) }).then((result) => { -- cgit From 9e54e3baa0799e5083a9f9152f128077a2ee0527 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 22 Dec 2016 13:56:45 -0800 Subject: Break up migration function --- app/scripts/metamask-controller.js | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ab4cb8ed8..983a590d7 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -442,37 +442,37 @@ module.exports = class MetamaskController { // with the provided password, so the other unlock steps // may be completed without interruption. migrateOldVaultIfAny (password) { - const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault() - if (!shouldMigrate) { + + if (!this.checkIfShouldMigrate()) { return Promise.resolve(password) } - return this.idStoreMigrator.migratedVaultForPassword(password) - .then((result) => { - - this.keyringController.password = password - const { serialized } = result - - // Restore the correct accounts first: - return this.keyringController.restoreKeyring(serialized) - .then(() => result) + const keyringController = this.keyringController - }).then((result) => { + return this.idStoreMigrator.migratedVaultForPassword(password) + .then(this.restoreOldVaultAccounts.bind(this)) + .then(this.restoreOldLostAccounts.bind(this)) + .then(keyringController.persistAllKeyrings.bind(keyringController)) + .then(() => password) + } - // Now we restore any lost accounts: - const { lostAccounts } = result - if (result && lostAccounts) { - this.configManager.setLostAccounts(lostAccounts.map((acct) => acct.address)) - return this.importLostAccounts(result) - } - return Promise.resolve(result) - }).then(() => { + checkIfShouldMigrate() { + return !!this.configManager.getWallet() && !this.configManager.getVault() + } - // Persist all these newly restored items to disk: - return this.keyringController.persistAllKeyrings() + restoreOldVaultAccounts(migratorOutput) { + const { serialized } = migratorOutput + return this.keyringController.restoreKeyring(serialized) + .then(() => migratorOutput) + } - // Ultimately pass the password back for normal unlocking: - }).then((result) => password) + restoreOldLostAccounts(migratorOutput) { + const { lostAccounts } = migratorOutput + if (lostAccounts) { + this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) + return this.importLostAccounts(migratorOutput) + } + return Promise.resolve(migratorOutput) } // IMPORT LOST ACCOUNTS -- cgit From 98527c1c254fe2d438191c73053dcf3223062ef3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 22 Dec 2016 14:43:00 -0800 Subject: Fix account injection bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It seems `selectedAddress` was removed from the keyring-controller’s state, and is used to populate the injected current account. I couldn't help myself, I dug around, I found a PR named [changed all instances of selectedAddress to selectedAccount](https://github.com/MetaMask/metamask-plugin/commit/f5b0795ac5582dd53de728479cf47c43eabfe67c) by @Zanibas. Sorry, Kevin! Had you actually changed all instances, this bug would not have happened. Fixes #908 --- app/scripts/lib/inpage-provider.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index 30fcbcb66..ccb592693 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -72,13 +72,13 @@ MetamaskInpageProvider.prototype.send = function (payload) { case 'eth_accounts': // read from localStorage - selectedAccount = self.publicConfigStore.get('selectedAddress') + selectedAccount = self.publicConfigStore.get('selectedAccount') result = selectedAccount ? [selectedAccount] : [] break case 'eth_coinbase': // read from localStorage - selectedAccount = self.publicConfigStore.get('selectedAddress') + selectedAccount = self.publicConfigStore.get('selectedAccount') result = selectedAccount || '0x0000000000000000000000000000000000000000' break @@ -119,6 +119,7 @@ function remoteStoreWithLocalStorageCache (storageKey) { var store = new RemoteStore(initState) // cache the latest state locally store.subscribe(function (state) { + console.log('received state update %s of %s', storageKey, state) localStorage[storageKey] = JSON.stringify(state) }) -- cgit From 1861bf8e2b29552cf9b4a04107c5392f13a8ddce Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 22 Dec 2016 14:46:14 -0800 Subject: Remove log --- app/scripts/lib/inpage-provider.js | 1 - 1 file changed, 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index ccb592693..a64c745ce 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -119,7 +119,6 @@ function remoteStoreWithLocalStorageCache (storageKey) { var store = new RemoteStore(initState) // cache the latest state locally store.subscribe(function (state) { - console.log('received state update %s of %s', storageKey, state) localStorage[storageKey] = JSON.stringify(state) }) -- cgit