From 7347a66eb0f4d5ab7f7d95e3657179408f4319f9 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 15 Jan 2016 02:03:42 -0800 Subject: integrate metamask-ui with id mgmt --- app/scripts/background.js | 175 ++++++++++++++++++++++++++++++++--- app/scripts/contentscript.js | 2 +- app/scripts/lib/metamask-provider.js | 23 ++--- app/scripts/popup.js | 25 ++++- package.json | 2 + 5 files changed, 199 insertions(+), 28 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 7ddd258e2..48f14172e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,33 +1,184 @@ +const Dnode = require('dnode') +const KeyStore = require('eth-lightwallet').keystore +const PortStream = require('./lib/port-stream.js') const MetaMaskProvider = require('./lib/metamask-provider') -// const PortStream = require('./lib/port-stream.js') -const identitiesUrl = 'https://alpha.metamask.io/identities/' -// var unsignedTxs = {} +console.log('ready to roll') -var zeroClient = MetaMaskProvider() +// setup provider +var zeroClient = MetaMaskProvider({ + rpcUrl: 'https://testrpc.metamask.io/', + getAccounts: getAccounts, + sendTransaction: confirmTransaction, +}) // setup messaging chrome.runtime.onConnect.addListener(connectRemote) -// chrome.runtime.onConnectExternal.addListener(connectRemote) function connectRemote(remotePort){ + var isMetaMaskInternalProcess = (remotePort.name === 'popup') + if (isMetaMaskInternalProcess) { + // communication with popup + handleInternalCommunication(remotePort) + } else { + // communication with page + handleExternalCommunication(remotePort) + } +} + +function handleInternalCommunication(remotePort){ + var duplex = new PortStream(remotePort) + var remote = Dnode({ + getState: getState, + setLocked: setLocked, + submitPassword: submitPassword, + setSelectedAddress: setSelectedAddress, + signTransaction: signTransaction, + }) + duplex.pipe(remote).pipe(duplex) +} + +function handleExternalCommunication(remotePort){ remotePort.onMessage.addListener(onRpcRequest.bind(null, remotePort)) } +// handle rpc requests function onRpcRequest(remotePort, payload){ + // console.log('MetaMaskPlugin - incoming payload:', payload) zeroClient.sendAsync(payload, function onPayloadHandled(err, response){ if (err) throw err console.log('MetaMaskPlugin - RPC complete:', payload, '->', response) - // if (response.result === true) debugger - // if (typeof response !== 'object') { - // if (!response) { - // console.warn('-------------------------------') - // console.warn(payload, '->', response) - // console.warn('-------------------------------') - // } remotePort.postMessage(response) }) } +// id mgmt +var selectedAddress = null + +function getState(cb){ + var result = _getState() + cb(null, result) +} + +function _getState(cb){ + var unlocked = isUnlocked() + var result = { + isUnlocked: unlocked, + identities: unlocked ? getIdentities() : {}, + selectedAddress: selectedAddress, + } + return result +} + +function isUnlocked(){ + var password = window.sessionStorage['password'] + var result = Boolean(password) + return result +} + +function setLocked(){ + delete window.sessionStorage['password'] +} + +function setSelectedAddress(address, cb){ + selectedAddress = address + cb(null, _getState()) +} + +function submitPassword(password, cb){ + console.log('submitPassword:', password) + tryPassword(password, function(err){ + if (err) console.log('bad password:', password, err) + if (err) return cb(err) + console.log('good password:', password) + window.sessionStorage['password'] = password + cb(null, _getState()) + }) +} + +function getAccounts(cb){ + var identities = getIdentities() + var result = selectedAddress ? [selectedAddress] : [] + cb(null, result) +} + +function getIdentities(cb){ + var keyStore = getKeyStore() + var addresses = keyStore.getAddresses() + var accountStore = {} + addresses.map(function(address){ + address = '0x'+address + accountStore[address] = { + name: 'Wally', + img: 'QmW6hcwYzXrNkuHrpvo58YeZvbZxUddv69ATSHY3BHpPdd', + address: address, + balance: 10.005, + txCount: 16, + } + }) + return accountStore +} + +function tryPassword(password, cb){ + var keyStore = getKeyStore(password) + var address = keyStore.getAddresses()[0] + if (!address) return cb(new Error('KeyStore - No address to check.')) + var hdPathString = keyStore.defaultHdPathString + try { + var encKey = keyStore.generateEncKey(password) + var encPrivKey = keyStore.ksData[hdPathString].encPrivKeys[address] + var privKey = KeyStore._decryptKey(encPrivKey, encKey) + var addrFromPrivKey = KeyStore._computeAddressFromPrivKey(privKey) + } catch (err) { + return cb(err) + } + if (addrFromPrivKey !== address) return cb(new Error('KeyStore - Decrypting private key failed!')) + cb() +} + +function confirmTransaction(txParams, cb){ + console.log('confirmTransaction:', txParams) +} + +function signTransaction(txParams, cb){ + console.log('signTransaction:', txParams) +} + +var keyStore = null +function getKeyStore(password){ + if (keyStore) return keyStore + password = password || getPassword() + var serializedKeystore = window.localStorage['lightwallet'] + // returning user + if (serializedKeystore) { + keyStore = KeyStore.deserialize(serializedKeystore) + // first time here + } else { + var defaultPassword = 'test' + console.log('creating new keystore with default password:', defaultPassword) + var secretSeed = KeyStore.generateRandomSeed() + keyStore = new KeyStore(secretSeed, defaultPassword) + keyStore.generateNewAddress(defaultPassword, 3) + saveKeystore() + } + keyStore.passwordProvider = unlockKeystore + return keyStore +} + +function saveKeystore(){ + window.localStorage['lightwallet'] = keyStore.serialize() +} + +function getPassword(){ + var password = window.sessionStorage['password'] + if (!password) throw new Error('No password found...') +} + +function unlockKeystore(cb){ + var password = getPassword() + console.warn('unlocking keystore...') + cb(null, password) +} + // // load from storage // chrome.storage.sync.get(function(data){ // for (var key in data) { diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 065c9f7d0..1b0de3375 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -14,7 +14,7 @@ var pageStream = new LocalMessageDuplexStream({ name: 'contentscript', target: 'inpage', }) -var pluginPort = chrome.runtime.connect({name: 'metamask'}) +var pluginPort = chrome.runtime.connect({name: 'contentscript'}) var pluginStream = new PortStream(pluginPort) // forward communication across diff --git a/app/scripts/lib/metamask-provider.js b/app/scripts/lib/metamask-provider.js index fea6d1f9f..d7d06d3f1 100644 --- a/app/scripts/lib/metamask-provider.js +++ b/app/scripts/lib/metamask-provider.js @@ -3,7 +3,7 @@ const CacheSubprovider = require('web3-provider-engine/subproviders/cache.js') const StaticSubprovider = require('web3-provider-engine/subproviders/static.js') const FilterSubprovider = require('web3-provider-engine/subproviders/filters.js') const VmSubprovider = require('web3-provider-engine/subproviders/vm.js') -const LightWalletSubprovider = require('web3-provider-engine/subproviders/lightwallet.js') +const HookedWalletSubprovider = require('web3-provider-engine/subproviders/hooked-wallet.js') const RpcSubprovider = require('web3-provider-engine/subproviders/rpc.js') module.exports = metamaskProvider @@ -22,7 +22,7 @@ function metamaskProvider(opts){ eth_hashrate: '0x0', eth_mining: false, eth_syncing: true, - }) + })) // filters engine.addProvider(new FilterSubprovider()) @@ -31,21 +31,22 @@ function metamaskProvider(opts){ engine.addProvider(new VmSubprovider()) // id mgmt - engine.addProvider(new LightWalletSubprovider()) + engine.addProvider(new HookedWalletSubprovider({ + getAccounts: opts.getAccounts, + sendTransaction: opts.sendTransaction, + })) // data source engine.addProvider(new RpcSubprovider({ - rpcUrl: 'https://testrpc.metamask.io/', + rpcUrl: opts.rpcUrl, })) // log new blocks - engine.on('block', function(block){ - // lazy hack - move caching and current block to engine - engine.currentBlock = block - console.log('================================') - console.log('BLOCK CHANGED:', '#'+block.number.toString('hex'), '0x'+block.hash.toString('hex')) - console.log('================================') - }) + // engine.on('block', function(block){ + // console.log('================================') + // console.log('BLOCK CHANGED:', '#'+block.number.toString('hex'), '0x'+block.hash.toString('hex')) + // console.log('================================') + // }) // start polling for blocks engine.start() diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 03530ce66..a7e33e7ff 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -1,13 +1,30 @@ +const Dnode = require('dnode') const MetaMaskUi = require('metamask-ui') const MetaMaskUiCss = require('metamask-ui/css') const injectCss = require('inject-css') +const PortStream = require('./lib/port-stream.js') -var container = document.getElementById('app-content') +// setup communication with background +var pluginPort = chrome.runtime.connect({name: 'popup'}) +var duplex = new PortStream(pluginPort) +var background = Dnode({ + // setUnconfirmedTxs: setUnconfirmedTxs, +}) +duplex.pipe(background).pipe(duplex) +background.once('remote', setupApp) +// setup app var css = MetaMaskUiCss() injectCss(css) -var app = MetaMaskUi({ - container: container, -}) +function setupApp(accountManager){ + + var container = document.getElementById('app-content') + + var app = MetaMaskUi({ + container: container, + accountManager: accountManager, + }) + +} \ No newline at end of file diff --git a/package.json b/package.json index 237cedf00..d40353cbc 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ }, "dependencies": { "async": "^1.4.0", + "dnode": "^1.2.2", + "eth-lightwallet": "^1.0.1", "ethereumjs-tx": "^0.6.7", "ethereumjs-util": "^1.3.5", "inject-css": "^0.1.1", -- cgit v1.2.3