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