Merge pull request #651 from MetaMask/library
MetaMask "Mascara" Library - initial PRfeature/default_network_editable
commit
b13eaaa0cd
@ -0,0 +1,63 @@ |
|||||||
|
const EventEmitter = require('events').EventEmitter |
||||||
|
const Dnode = require('dnode') |
||||||
|
const Web3 = require('web3') |
||||||
|
const MetaMaskUi = require('../../ui') |
||||||
|
const StreamProvider = require('web3-stream-provider') |
||||||
|
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex |
||||||
|
|
||||||
|
|
||||||
|
module.exports = initializePopup |
||||||
|
|
||||||
|
|
||||||
|
function initializePopup(connectionStream){ |
||||||
|
// setup app
|
||||||
|
connectToAccountManager(connectionStream, setupApp) |
||||||
|
} |
||||||
|
|
||||||
|
function connectToAccountManager (connectionStream, cb) { |
||||||
|
// setup communication with background
|
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(connectionStream) |
||||||
|
// connect features
|
||||||
|
setupControllerConnection(mx.createStream('controller'), cb) |
||||||
|
setupWeb3Connection(mx.createStream('provider')) |
||||||
|
} |
||||||
|
|
||||||
|
function setupWeb3Connection (connectionStream) { |
||||||
|
var providerStream = new StreamProvider() |
||||||
|
providerStream.pipe(connectionStream).pipe(providerStream) |
||||||
|
connectionStream.on('error', console.error.bind(console)) |
||||||
|
providerStream.on('error', console.error.bind(console)) |
||||||
|
global.web3 = new Web3(providerStream) |
||||||
|
} |
||||||
|
|
||||||
|
function setupControllerConnection (connectionStream, cb) { |
||||||
|
// this is a really sneaky way of adding EventEmitter api
|
||||||
|
// to a bi-directional dnode instance
|
||||||
|
var eventEmitter = new EventEmitter() |
||||||
|
var accountManagerDnode = Dnode({ |
||||||
|
sendUpdate: function (state) { |
||||||
|
eventEmitter.emit('update', state) |
||||||
|
}, |
||||||
|
}) |
||||||
|
connectionStream.pipe(accountManagerDnode).pipe(connectionStream) |
||||||
|
accountManagerDnode.once('remote', function (accountManager) { |
||||||
|
// setup push events
|
||||||
|
accountManager.on = eventEmitter.on.bind(eventEmitter) |
||||||
|
cb(null, accountManager) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function setupApp (err, accountManager) { |
||||||
|
if (err) { |
||||||
|
alert(err.stack) |
||||||
|
throw err |
||||||
|
} |
||||||
|
|
||||||
|
var container = document.getElementById('app-content') |
||||||
|
|
||||||
|
MetaMaskUi({ |
||||||
|
container: container, |
||||||
|
accountManager: accountManager, |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
start the dual servers (dapp + mascara) |
||||||
|
``` |
||||||
|
node server.js |
||||||
|
``` |
||||||
|
|
||||||
|
open the example dapp at `http://localhost:9002/` |
@ -0,0 +1,68 @@ |
|||||||
|
const ZeroClientProvider = require('web3-provider-engine/zero') |
||||||
|
const ParentStream = require('iframe-stream').ParentStream |
||||||
|
const handleRequestsFromStream = require('web3-stream-provider/handler') |
||||||
|
const Streams = require('mississippi') |
||||||
|
const ObjectMultiplex = require('../app/scripts/lib/obj-multiplex') |
||||||
|
|
||||||
|
|
||||||
|
initializeZeroClient() |
||||||
|
|
||||||
|
function initializeZeroClient() { |
||||||
|
|
||||||
|
var provider = ZeroClientProvider({ |
||||||
|
// rpcUrl: configManager.getCurrentRpcAddress(),
|
||||||
|
rpcUrl: 'https://morden.infura.io/', |
||||||
|
// account mgmt
|
||||||
|
// getAccounts: function(cb){
|
||||||
|
// var selectedAddress = idStore.getSelectedAddress()
|
||||||
|
// var result = selectedAddress ? [selectedAddress] : []
|
||||||
|
// cb(null, result)
|
||||||
|
// },
|
||||||
|
getAccounts: function(cb){ |
||||||
|
cb(null, ['0x8F331A98aC5C9431d04A5d6Bf8Fa84ed7Ed439f3'.toLowerCase()]) |
||||||
|
}, |
||||||
|
// tx signing
|
||||||
|
// approveTransaction: addUnconfirmedTx,
|
||||||
|
// signTransaction: idStore.signTransaction.bind(idStore),
|
||||||
|
signTransaction: function(txParams, cb){ |
||||||
|
var privKey = new Buffer('7ef33e339ba5a5af0e57fa900ad0ae53deaa978c21ef30a0947532135eb639a8', 'hex') |
||||||
|
var Transaction = require('ethereumjs-tx') |
||||||
|
console.log('signing tx:', txParams) |
||||||
|
txParams.gasLimit = txParams.gas |
||||||
|
var tx = new Transaction(txParams) |
||||||
|
tx.sign(privKey) |
||||||
|
var serialiedTx = '0x'+tx.serialize().toString('hex') |
||||||
|
cb(null, serialiedTx) |
||||||
|
}, |
||||||
|
// msg signing
|
||||||
|
// approveMessage: addUnconfirmedMsg,
|
||||||
|
// signMessage: idStore.signMessage.bind(idStore),
|
||||||
|
}) |
||||||
|
|
||||||
|
provider.on('block', function(block){ |
||||||
|
console.log('BLOCK CHANGED:', '#'+block.number.toString('hex'), '0x'+block.hash.toString('hex')) |
||||||
|
}) |
||||||
|
|
||||||
|
var connectionStream = new ParentStream() |
||||||
|
// setup connectionStream multiplexing
|
||||||
|
var multiStream = ObjectMultiplex() |
||||||
|
Streams.pipe(connectionStream, multiStream, connectionStream, function(err){ |
||||||
|
console.warn('MetamaskIframe - lost connection to Dapp') |
||||||
|
if (err) throw err |
||||||
|
}) |
||||||
|
|
||||||
|
// connectionStream.on('data', function(chunk){ console.log('connectionStream chuck', chunk) })
|
||||||
|
// multiStream.on('data', function(chunk){ console.log('multiStream chuck', chunk) })
|
||||||
|
|
||||||
|
var providerStream = multiStream.createStream('provider') |
||||||
|
handleRequestsFromStream(providerStream, provider, logger) |
||||||
|
|
||||||
|
function logger(err, request, response){ |
||||||
|
if (err) return console.error(err.stack) |
||||||
|
if (!request.isMetamaskInternal) { |
||||||
|
console.log('MetaMaskIframe - RPC complete:', request, '->', response) |
||||||
|
if (response.error) console.error('Error in RPC response:\n'+response.error.message) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
<!doctype html> |
||||||
|
|
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
|
||||||
|
<title>MetaMask ZeroClient Example</title> |
||||||
|
|
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
<button class="action-button-1">SYNC TX</button> |
||||||
|
<button class="action-button-2">ASYNC TX</button> |
||||||
|
<script src="./zero.js"></script> |
||||||
|
<script src="./app.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,54 @@ |
|||||||
|
|
||||||
|
window.addEventListener('load', web3Detect) |
||||||
|
|
||||||
|
function web3Detect() { |
||||||
|
if (global.web3) { |
||||||
|
logToDom('web3 detected!') |
||||||
|
startApp() |
||||||
|
} else { |
||||||
|
logToDom('no web3 detected!') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function startApp(){ |
||||||
|
console.log('app started') |
||||||
|
|
||||||
|
var primaryAccount = null |
||||||
|
console.log('getting main account...') |
||||||
|
web3.eth.getAccounts(function(err, addresses){ |
||||||
|
if (err) throw err |
||||||
|
console.log('set address') |
||||||
|
primaryAccount = addresses[0] |
||||||
|
}) |
||||||
|
|
||||||
|
document.querySelector('.action-button-1').addEventListener('click', function(){ |
||||||
|
console.log('saw click') |
||||||
|
console.log('sending tx') |
||||||
|
web3.eth.sendTransaction({ |
||||||
|
from: primaryAccount, |
||||||
|
value: 0, |
||||||
|
}, function(err, txHash){ |
||||||
|
if (err) throw err |
||||||
|
console.log('sendTransaction result:', err || txHash) |
||||||
|
}) |
||||||
|
}) |
||||||
|
document.querySelector('.action-button-2').addEventListener('click', function(){ |
||||||
|
console.log('saw click') |
||||||
|
setTimeout(function(){ |
||||||
|
console.log('sending tx') |
||||||
|
web3.eth.sendTransaction({ |
||||||
|
from: primaryAccount, |
||||||
|
value: 0, |
||||||
|
}, function(err, txHash){ |
||||||
|
if (err) throw err |
||||||
|
console.log('sendTransaction result:', err || txHash) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function logToDom(message){ |
||||||
|
document.body.appendChild(document.createTextNode(message)) |
||||||
|
console.log(message) |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
const Web3 = require('web3') |
||||||
|
const setupProvider = require('./lib/setup-provider.js') |
||||||
|
|
||||||
|
//
|
||||||
|
// setup web3
|
||||||
|
//
|
||||||
|
|
||||||
|
var provider = setupProvider() |
||||||
|
hijackProvider(provider) |
||||||
|
var web3 = new Web3(provider) |
||||||
|
web3.setProvider = function(){ |
||||||
|
console.log('MetaMask - overrode web3.setProvider') |
||||||
|
} |
||||||
|
console.log('metamask lib hijacked provider') |
||||||
|
|
||||||
|
//
|
||||||
|
// export web3
|
||||||
|
//
|
||||||
|
|
||||||
|
global.web3 = web3 |
||||||
|
|
||||||
|
//
|
||||||
|
// ui stuff
|
||||||
|
//
|
||||||
|
|
||||||
|
var shouldPop = false |
||||||
|
window.addEventListener('click', function(){ |
||||||
|
if (!shouldPop) return |
||||||
|
shouldPop = false |
||||||
|
window.open('http://localhost:9001/popup/popup.html', '', 'width=1000') |
||||||
|
console.log('opening window...') |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
function hijackProvider(provider){ |
||||||
|
var _super = provider.sendAsync.bind(provider) |
||||||
|
provider.sendAsync = function(payload, cb){ |
||||||
|
if (payload.method === 'eth_sendTransaction') { |
||||||
|
console.log('saw send') |
||||||
|
shouldPop = true |
||||||
|
} |
||||||
|
_super(payload, cb) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
const Iframe = require('iframe') |
||||||
|
const IframeStream = require('iframe-stream').IframeStream |
||||||
|
|
||||||
|
module.exports = setupIframe |
||||||
|
|
||||||
|
|
||||||
|
function setupIframe(opts) { |
||||||
|
opts = opts || {} |
||||||
|
var frame = Iframe({ |
||||||
|
src: opts.zeroClientProvider || 'https://zero.metamask.io/', |
||||||
|
container: opts.container || document.head, |
||||||
|
sandboxAttributes: opts.sandboxAttributes || ['allow-scripts', 'allow-popups'], |
||||||
|
}) |
||||||
|
var iframe = frame.iframe |
||||||
|
iframe.style.setProperty('display', 'none') |
||||||
|
var iframeStream = new IframeStream(iframe) |
||||||
|
|
||||||
|
return iframeStream |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
const setupIframe = require('./setup-iframe.js') |
||||||
|
const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') |
||||||
|
|
||||||
|
module.exports = getProvider |
||||||
|
|
||||||
|
|
||||||
|
function getProvider(){ |
||||||
|
|
||||||
|
if (global.web3) { |
||||||
|
console.log('MetaMask ZeroClient - using environmental web3 provider') |
||||||
|
return global.web3.currentProvider |
||||||
|
} |
||||||
|
|
||||||
|
console.log('MetaMask ZeroClient - injecting zero-client iframe!') |
||||||
|
var iframeStream = setupIframe({ |
||||||
|
zeroClientProvider: 'http://127.0.0.1:9001', |
||||||
|
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], |
||||||
|
container: document.body, |
||||||
|
}) |
||||||
|
|
||||||
|
var inpageProvider = new MetamaskInpageProvider(iframeStream) |
||||||
|
return inpageProvider |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
const injectCss = require('inject-css') |
||||||
|
const MetaMaskUiCss = require('../ui/css') |
||||||
|
const startPopup = require('../app/scripts/popup-core') |
||||||
|
const setupIframe = require('./lib/setup-iframe.js') |
||||||
|
|
||||||
|
|
||||||
|
var css = MetaMaskUiCss() |
||||||
|
injectCss(css) |
||||||
|
|
||||||
|
var name = 'popup' |
||||||
|
window.METAMASK_UI_TYPE = name |
||||||
|
|
||||||
|
var iframeStream = setupIframe({ |
||||||
|
zeroClientProvider: 'http://127.0.0.1:9001', |
||||||
|
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], |
||||||
|
container: document.body, |
||||||
|
}) |
||||||
|
|
||||||
|
startPopup(iframeStream) |
@ -0,0 +1,105 @@ |
|||||||
|
const express = require('express') |
||||||
|
const browserify = require('browserify') |
||||||
|
const watchify = require('watchify') |
||||||
|
const babelify = require('babelify') |
||||||
|
|
||||||
|
const zeroBundle = createBundle('./index.js') |
||||||
|
const controllerBundle = createBundle('./controller.js') |
||||||
|
const popupBundle = createBundle('./popup.js') |
||||||
|
const appBundle = createBundle('./example/index.js') |
||||||
|
|
||||||
|
//
|
||||||
|
// Iframe Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const iframeServer = express() |
||||||
|
|
||||||
|
// serve popup window
|
||||||
|
iframeServer.get('/popup/scripts/popup.js', function(req, res){ |
||||||
|
res.send(popupBundle.latest) |
||||||
|
}) |
||||||
|
iframeServer.use('/popup', express.static('../dist/chrome')) |
||||||
|
|
||||||
|
// serve controller bundle
|
||||||
|
iframeServer.get('/controller.js', function(req, res){ |
||||||
|
res.send(controllerBundle.latest) |
||||||
|
}) |
||||||
|
|
||||||
|
// serve background controller
|
||||||
|
iframeServer.use(express.static('./server')) |
||||||
|
|
||||||
|
// start the server
|
||||||
|
const mascaraPort = 9001 |
||||||
|
iframeServer.listen(mascaraPort) |
||||||
|
console.log(`Mascara service listening on port ${mascaraPort}`) |
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Dapp Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const dappServer = express() |
||||||
|
|
||||||
|
// serve metamask-lib bundle
|
||||||
|
dappServer.get('/zero.js', function(req, res){ |
||||||
|
res.send(zeroBundle.latest) |
||||||
|
}) |
||||||
|
|
||||||
|
// serve dapp bundle
|
||||||
|
dappServer.get('/app.js', function(req, res){ |
||||||
|
res.send(appBundle.latest) |
||||||
|
}) |
||||||
|
|
||||||
|
// serve static
|
||||||
|
dappServer.use(express.static('./example')) |
||||||
|
|
||||||
|
// start the server
|
||||||
|
const dappPort = '9002' |
||||||
|
dappServer.listen(dappPort) |
||||||
|
console.log(`Dapp listening on port ${dappPort}`) |
||||||
|
|
||||||
|
//
|
||||||
|
// util
|
||||||
|
//
|
||||||
|
|
||||||
|
function serveBundle(entryPoint){ |
||||||
|
const bundle = createBundle(entryPoint) |
||||||
|
return function(req, res){ |
||||||
|
res.send(bundle.latest) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function createBundle(entryPoint){ |
||||||
|
|
||||||
|
var bundleContainer = {} |
||||||
|
|
||||||
|
var bundler = browserify({ |
||||||
|
entries: [entryPoint], |
||||||
|
cache: {}, |
||||||
|
packageCache: {}, |
||||||
|
plugin: [watchify], |
||||||
|
}) |
||||||
|
|
||||||
|
// global transpile
|
||||||
|
var bablePreset = require.resolve('babel-preset-es2015') |
||||||
|
|
||||||
|
bundler.transform(babelify, { |
||||||
|
global: true, |
||||||
|
presets: [bablePreset], |
||||||
|
babelrc: false, |
||||||
|
}) |
||||||
|
|
||||||
|
bundler.on('update', bundle) |
||||||
|
bundle() |
||||||
|
|
||||||
|
return bundleContainer |
||||||
|
|
||||||
|
function bundle() { |
||||||
|
bundler.bundle(function(err, result){ |
||||||
|
if (err) throw err |
||||||
|
console.log(`Bundle updated! (${entryPoint})`) |
||||||
|
bundleContainer.latest = result.toString() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
<!doctype html> |
||||||
|
|
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
|
||||||
|
<title>MetaMask ZeroClient Iframe</title> |
||||||
|
<meta name="description" content="MetaMask ZeroClient"> |
||||||
|
<meta name="author" content="MetaMask"> |
||||||
|
|
||||||
|
<!--[if lt IE 9]> |
||||||
|
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> |
||||||
|
<![endif]--> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
Hello! I am the MetaMask iframe. |
||||||
|
<script src="/controller.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
Loading…
Reference in new issue