diff --git a/app/scripts/background.js b/app/scripts/background.js index ddb867219..c439558c8 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,7 +1,10 @@ +const ZeroClientProvider = require('web3-provider-skeleton') +// const PortStream = require('./lib/port-stream.js') const identitiesUrl = 'https://alpha.metamask.io/identities/' -const messagingChannelName = 'metamask' -var unsignedTxs = {} +// var unsignedTxs = {} + +var zeroClient = ZeroClientProvider() // setup badge click handler chrome.browserAction.onClicked.addListener(function(activeTab) { @@ -10,118 +13,131 @@ chrome.browserAction.onClicked.addListener(function(activeTab) { // setup messaging chrome.runtime.onConnect.addListener(connectRemote) -chrome.runtime.onConnectExternal.addListener(connectRemote) -function connectRemote(remote){ - remote.onMessage.addListener(handleMessage) - exportUnsignedTxs(remote) +// chrome.runtime.onConnectExternal.addListener(connectRemote) +function connectRemote(remotePort){ + remotePort.onMessage.addListener(onRpcRequest.bind(null, remotePort)) } -// load from storage -chrome.storage.sync.get(function(data){ - for (var key in data) { - var serialized = data[key] - var tx = deserializeTx(serialized) - var hash = simpleHash(serialized) - unsignedTxs[hash] = tx - } - updateBadge() -}) +function onRpcRequest(remotePort, payload){ + zeroClient.sendAsync(payload, function onPayloadHandled(err, response){ + if (err) throw err + // console.log('MetaMaskPlugin - RPC complete:', payload, '->', response) + // if (typeof response !== 'object') { + // if (!response) { + // console.warn('-------------------------------') + // console.warn(payload, '->', response) + // console.warn('-------------------------------') + // } + remotePort.postMessage(response) + }) +} -// listen to storage changes -chrome.storage.onChanged.addListener(function(changes, namespace) { - for (key in changes) { - var storageChange = changes[key] - if (storageChange.oldValue && !storageChange.newValue) { - // was removed - removeTransaction(storageChange.oldValue) - } else if (!storageChange.oldValue && storageChange.newValue) { - // was added - addTransaction(deserializeTx(storageChange.newValue)) - } - } -}) +// // load from storage +// chrome.storage.sync.get(function(data){ +// for (var key in data) { +// var serialized = data[key] +// var tx = deserializeTx(serialized) +// var hash = simpleHash(serialized) +// unsignedTxs[hash] = tx +// } +// updateBadge() +// }) + +// // listen to storage changes +// chrome.storage.onChanged.addListener(function(changes, namespace) { +// for (key in changes) { +// var storageChange = changes[key] +// if (storageChange.oldValue && !storageChange.newValue) { +// // was removed +// removeTransaction(storageChange.oldValue) +// } else if (!storageChange.oldValue && storageChange.newValue) { +// // was added +// addTransaction(deserializeTx(storageChange.newValue)) +// } +// } +// }) // setup badge text -updateBadge() +// updateBadge() -function handleMessage(msg){ - console.log('got message!', msg.type) - switch(msg.type){ +// function updateBadge(){ +// var label = '' +// var count = Object.keys(unsignedTxs).length +// if (count) { +// label = String(count) +// } +// chrome.browserAction.setBadgeText({text: label}) +// chrome.browserAction.setBadgeBackgroundColor({color: '#506F8B'}) +// } + +// function handleMessage(msg){ +// console.log('got message!', msg.type) +// switch(msg.type){ - case 'addUnsignedTx': - addTransaction(msg.payload) - return +// case 'addUnsignedTx': +// addTransaction(msg.payload) +// return - case 'removeUnsignedTx': - removeTransaction(msg.payload) - return +// case 'removeUnsignedTx': +// removeTransaction(msg.payload) +// return - } -} +// } +// } -function addTransaction(tx){ - var serialized = serializeTx(tx) - var hash = simpleHash(serialized) - unsignedTxs[hash] = tx - var data = {} - data[hash] = serialized - chrome.storage.sync.set(data) - // trigger ui changes - updateBadge() -} +// function addTransaction(tx){ +// var serialized = serializeTx(tx) +// var hash = simpleHash(serialized) +// unsignedTxs[hash] = tx +// var data = {} +// data[hash] = serialized +// chrome.storage.sync.set(data) +// // trigger ui changes +// updateBadge() +// } -function removeTransaction(serialized){ - var hash = simpleHash(serialized) - delete unsignedTxs[hash] - var data = {} - data[hash] = undefined - chrome.storage.sync.set(data) - // trigger ui changes - updateBadge() -} +// function removeTransaction(serialized){ +// var hash = simpleHash(serialized) +// delete unsignedTxs[hash] +// var data = {} +// data[hash] = undefined +// chrome.storage.sync.set(data) +// // trigger ui changes +// updateBadge() +// } -function exportUnsignedTxs(remote){ - console.log('exporting txs!', unsignedTxs) - var data = { - type: 'importUnsignedTxs', - payload: getValues(unsignedTxs), - } - remote.postMessage(data) -} +// function exportUnsignedTxs(remote){ +// console.log('exporting txs!', unsignedTxs) +// var data = { +// type: 'importUnsignedTxs', +// payload: getValues(unsignedTxs), +// } +// remote.postMessage(data) +// } -function updateBadge(){ - var label = '' - var count = Object.keys(unsignedTxs).length - if (count) { - label = String(count) - } - chrome.browserAction.setBadgeText({text: label}) - chrome.browserAction.setBadgeBackgroundColor({color: '#506F8B'}) -} +// function simpleHash(input) { +// var hash = 0, i, chr, len +// if (input.length == 0) return hash +// for (i = 0, len = input.length; i < len; i++) { +// chr = input.charCodeAt(i) +// hash = ((hash << 5) - hash) + chr +// hash |= 0 // Convert to 32bit integer +// } +// return hash +// } -function simpleHash(input) { - var hash = 0, i, chr, len - if (input.length == 0) return hash - for (i = 0, len = input.length; i < len; i++) { - chr = input.charCodeAt(i) - hash = ((hash << 5) - hash) + chr - hash |= 0 // Convert to 32bit integer - } - return hash -} +// function serializeTx(tx){ +// return JSON.stringify(tx) +// } -function serializeTx(tx){ - return JSON.stringify(tx) -} +// function deserializeTx(tx){ +// return JSON.parse(tx) +// } -function deserializeTx(tx){ - return JSON.parse(tx) -} - -function getValues(obj){ - var output = [] - for (var key in obj) { - output.push(obj[key]) - } - return output -} \ No newline at end of file +// function getValues(obj){ +// var output = [] +// for (var key in obj) { +// output.push(obj[key]) +// } +// return output +// } \ No newline at end of file diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7a180d9a9..cce5c1274 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,6 +1,7 @@ -const allowedMessageTarget = 'metamask' -const allowedMessageType = 'addUnsignedTx' +const LocalMessageDuplexStream = require('./lib/local-message-stream.js') +const PortStream = require('./lib/port-stream.js') +console.log('content script!') // inject in-page script var scriptTag = document.createElement('script') @@ -9,17 +10,14 @@ scriptTag.onload = function() { this.parentNode.removeChild(this) } var container = document.head || document.documentElement container.appendChild(scriptTag) -// setup connection with background -var metamaskPlugin = chrome.runtime.connect({name: 'metamask'}) +// setup communication to page and plugin +var pageStream = new LocalMessageDuplexStream({ + name: 'contentscript', + target: 'inpage', +}) +var pluginPort = chrome.runtime.connect({name: 'metamask'}) +var pluginStream = new PortStream(pluginPort) -// forward messages from inpage to background -window.addEventListener('message', receiveMessage, false); -function receiveMessage(event){ - var msg = event.data - // validate message type - if (typeof msg !== 'object') return - if (msg.to !== allowedMessageTarget) return - if (msg.type !== allowedMessageType) return - // forward message - metamaskPlugin.postMessage(msg) -} \ No newline at end of file +// forward communication across +pageStream.pipe(pluginStream) +pluginStream.pipe(pageStream) \ No newline at end of file diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 5149bd37e..d45dde593 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,59 +1,18 @@ -const web3 = require('web3') -const BlockAppsWeb3Provider = require('blockapps-web3') -const Transaction = require('ethereumjs-tx') -require('object.entries').shim() - -// const rpcUrl = 'https://rpc.metamask.io' - -// var provider = new MetamaskProvider(forwardPayload, rpcUrl) -var provider = new BlockAppsWeb3Provider({ - host: 'http://hacknet.blockapps.net', - // host: 'http://api.blockapps.net', - transaction_signer: { - // Can be any object that implements the following methods: - hasAddress: function(address, callback) { - console.log('metamask provider - asked for address ownership', address) - callback(null, true) - }, - signTransaction: function(txParams, callback) { - txParams.gasLimit = txParams.gas - var tx = new Transaction(txParams) - tx.sign(new Buffer('0d0ba14043088cd629a978b49c8691deca5926f0271432bc0064e4745bac0a9f', 'hex')) - callback(null, '0x'+tx.serialize().toString('hex')) - }, - }, - coinbase: '0x00000000000', - accounts: ['0x985095ef977ba75fb2bb79cd5c4b84c81392dff6'], - // host: function(){ debugger }, -}); - -const documentOrigin = window.location.origin -const allowedMessageTarget = 'metamask' -const allowedMessageType = 'addUnsignedTx' - -web3.setProvider(provider) -// disable setProvider -web3.setProvider = function(){} - -// injecting web3 -console.log('Metamask injected web3') +const Web3 = require('web3') +const StreamProvider = require('./lib/stream-provider.js') +const LocalMessageDuplexStream = require('./lib/local-message-stream.js') -// log all the stuff! -// provider.verbosity = 1 - -// web3.currentProvider.vm.onStep = function(data, cb){ -// console.log(data) -// cb() -// } +// setup plugin communication +var pluginStream = new LocalMessageDuplexStream({ + name: 'inpage', + target: 'contentscript', +}) +var remoteProvider = new StreamProvider() +remoteProvider.pipe(pluginStream).pipe(remoteProvider) +// create web3 +var web3 = new Web3(remoteProvider) window.web3 = web3 - - -function forwardPayload(payload){ - window.postMessage({ - to: allowedMessageTarget, - type: allowedMessageType, - payload: payload, - }, documentOrigin) -} \ No newline at end of file +web3.setProvider = function(){} +console.log('Metamask injected web3') \ No newline at end of file diff --git a/app/scripts/lib/local-message-stream.js b/app/scripts/lib/local-message-stream.js new file mode 100644 index 000000000..42d193e04 --- /dev/null +++ b/app/scripts/lib/local-message-stream.js @@ -0,0 +1,53 @@ +const Duplex = require('readable-stream').Duplex +const inherits = require('util').inherits + +module.exports = LocalMessageDuplexStream + + +inherits(LocalMessageDuplexStream, Duplex) + +function LocalMessageDuplexStream(opts){ + Duplex.call(this, { + objectMode: true, + }) + + // this._origin = opts.origin + this._name = opts.name + this._target = opts.target + + // console.log('LocalMessageDuplexStream ('+this._name+') - initialized...') + window.addEventListener('message', this._onMessage.bind(this), false) +} + +// private + +LocalMessageDuplexStream.prototype._onMessage = function(event){ + var msg = event.data + // console.log('LocalMessageDuplexStream ('+this._name+') - heard message...') + // validate message + if (event.origin !== location.origin) return //console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (event.origin !== location.origin) ') + if (typeof msg !== 'object') return //console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (typeof msg !== "object") ') + if (msg.target !== this._name) return //console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (msg.target !== this._name) ', msg.target, this._name) + if (!msg.data) return //console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (!msg.data) ') + // console.log('LocalMessageDuplexStream ('+this._name+') - accepted', msg.data) + // forward message + this.push(msg.data) +} + +// stream plumbing + +LocalMessageDuplexStream.prototype._read = noop + +LocalMessageDuplexStream.prototype._write = function(data, encoding, cb){ + // console.log('LocalMessageDuplexStream ('+this._name+') - sending message...') + var message = { + target: this._target, + data: data, + } + window.postMessage(message, location.origin) + cb() +} + +// util + +function noop(){} \ No newline at end of file diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js new file mode 100644 index 000000000..d256efc9a --- /dev/null +++ b/app/scripts/lib/port-stream.js @@ -0,0 +1,36 @@ +const Duplex = require('readable-stream').Duplex +const inherits = require('util').inherits + +module.exports = PortDuplexStream + + +inherits(PortDuplexStream, Duplex) + +function PortDuplexStream(port){ + Duplex.call(this, { + objectMode: true, + }) + this._port = port + port.onMessage.addListener(this._onMessage.bind(this)) +} + +// private + +PortDuplexStream.prototype._onMessage = function(msg){ + // console.log('PortDuplexStream - saw message', msg) + this.push(msg) +} + +// stream plumbing + +PortDuplexStream.prototype._read = noop + +PortDuplexStream.prototype._write = function(msg, encoding, cb){ + // console.log('PortDuplexStream - sent message', msg) + this._port.postMessage(msg) + cb() +} + +// util + +function noop(){} \ No newline at end of file diff --git a/app/scripts/lib/stream-provider.js b/app/scripts/lib/stream-provider.js new file mode 100644 index 000000000..3e7f443c8 --- /dev/null +++ b/app/scripts/lib/stream-provider.js @@ -0,0 +1,50 @@ +const Duplex = require('readable-stream').Duplex +const inherits = require('util').inherits + +module.exports = StreamProvider + + +inherits(StreamProvider, Duplex) + +function StreamProvider(){ + Duplex.call(this, { + objectMode: true, + }) + + this._handlers = {} +} + +// public + +StreamProvider.prototype.send = function(payload){ + throw new Error('StreamProvider - does not support synchronous RPC calls') +} + +StreamProvider.prototype.sendAsync = function(payload, callback){ +// console.log('StreamProvider - sending payload', payload) + this._handlers[payload.id] = callback + this.push(payload) +} + +// private + +StreamProvider.prototype._onResponse = function(payload){ +// console.log('StreamProvider - got response', payload) + var callback = this._handlers[payload.id] + if (!callback) throw new Error('StreamProvider - Unknown response id') + delete this._handlers[payload.id] + callback(null, payload) +} + +// stream plumbing + +StreamProvider.prototype._read = noop + +StreamProvider.prototype._write = function(msg, encoding, cb){ + this._onResponse(msg) + cb() +} + +// util + +function noop(){} \ No newline at end of file diff --git a/package.json b/package.json index ce897c4f4..5578052fb 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "ethereumjs-tx": "^0.6.7", "ethereumjs-util": "^1.3.5", "object.entries": "^1.0.2", - "web3": "^0.9.2" + "readable-stream": "^2.0.5", + "web3": "^0.15.1", + "web3-provider-engine": "^1.0.0" }, "devDependencies": { "grunt": "~0.4.1",