MetaMask/zero-client
https://github.com/MetaMask/zero-client
MetaMask ZeroClient and backing iframe service
architecture
here is a comparison of the extension-based and iframe-based architecture
metamask extension:
dapp inpage.js <-> forwarder contentscript.js <-> extension background.js
ui popup.js <-> extension background.js
就是当用户通过浏览器通过web3去调用json rpc时,即通过dapp inpage.js,再传给metamask contentscript,再传给metamask background,background将会将要进行的操作传给UI,使其能够在与rpc进行交互之前再一次得到用户的确认,用户确认后就会返回background去与rpc进行交互,然后再将得到的数据通过metamask contentscript-dapp inpage.js传给浏览器
current metamask iframe:
dapp inpage.js <-> iframe background.js
ui app.metamask.io <-> iframe background.js
ideal metamask iframe:
dapp inpage.js <-> iframe forwarder <-> sharedworker background.js
ui app.metamask.io <-> iframe forwarder <-> sharedworker background.js
ideal unified metamask extension+iframe:
dapp inpage.js <-> iframe forwarder <-> sharedworker background.js
ui popup.js <-> iframe forwarder <-> sharedworker background.js
example
npm start
运行:
首先:
userdeMacBook-Pro:zero-client user$ npm install
然后运行npm start:
userdeMacBook-Pro:zero-client user$ npm start > metamask-zeroclient@2.0. start /Users/user/zero-client
> ./example.sh ./example.sh: line : beefy: command not found
./example.sh: line : beefy: command not found
npm ERR! file sh
npm ERR! code ELIFECYCLE
npm ERR! errno ENOENT
npm ERR! syscall spawn
npm ERR! metamask-zeroclient@2.0. start: `./example.sh`
npm ERR! spawn ENOENT
npm ERR!
npm ERR! Failed at the metamask-zeroclient@2.0. start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in:
npm ERR! /Users/user/.npm/_logs/--12T09_26_56_269Z-debug.log
出错,没有安装模块beefy:
userdeMacBook-Pro:zero-client user$ npm install beefy --save
再运行又出错:
userdeMacBook-Pro:zero-client user$ npm start > metamask-zeroclient@2.0. start /Users/user/zero-client
> ./example.sh beefy (v2.1.8) is listening on http://127.0.0.1:9001
beefy (v2.1.8) is listening on http://127.0.0.1:9002
58ms 261B /
70ms .95KB /bundle.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/.index.js -d
Error: Cannot find module 'metamask-crx/app/scripts/lib/inpage-provider.js' from '/Users/user/zero-client/lib'
at /Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js::
at process (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js::)
at ondir (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js::)
at load (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js::)
at onex (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js::)
at /Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js::
at FSReqCallback.oncomplete (fs.js::)
发现是在lib/setup-provider.js中调用了metamask-crx模块(metamask chrome extension),但node_modules中没有
从该网址下载https://github.com/ATNIO/metamask-crx:
const MetamaskInpageProvider = require('metamask-crx/app/scripts/lib/inpage-provider.js')
再运行,还是报错:
Error: Cannot find module 'brfs' from '/Users/user/zero-client/node_modules/metamask-crx'
at /Users/user/zero-client/node_modules/resolve/lib/async.js::
at processDirs (/Users/user/zero-client/node_modules/resolve/lib/async.js::)
at ondir (/Users/user/zero-client/node_modules/resolve/lib/async.js::)
at load (/Users/user/zero-client/node_modules/resolve/lib/async.js::)
at onex (/Users/user/zero-client/node_modules/resolve/lib/async.js::)
at /Users/user/zero-client/node_modules/resolve/lib/async.js::
at FSReqCallback.oncomplete (fs.js::)
是因为下载下来的metamask-crx模块忘记运行npm install
然后再运行就成功了:
userdeMacBook-Pro:zero-client user$ npm start > metamask-zeroclient@2.0. start /Users/user/zero-client
> ./example.sh beefy (v2.1.8) is listening on http://127.0.0.1:9001
beefy (v2.1.8) is listening on http://127.0.0.1:9002
50ms 261B /
63ms .95KB /bundle.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/.index.js -d
2422ms .12MB /zero.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/index.js -d
32ms 427B /
1ms 12B /background.js
1497ms .33MB /bundle.js ➞ ./node_modules/browserify ./frame.js -d
25ms 261B /
116ms .95KB /bundle.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/.index.js -d
1720ms .12MB /zero.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/index.js -d
1ms 189B /favicon.ico (generated)
13ms 261B /
12ms .95KB /bundle.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/.index.js -d
1377ms .12MB /zero.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/index.js -d
16ms 427B /
在没有安装metamask插件的浏览器上的结果是:
在安装了metamask插件的浏览器上的结果是:
看到这个运行的结果后,我们可以看看其的代码:
他的代码与之前的mascara的差不多,如果想要看更详细的解释,可看mascara-2(MetaMask/mascara本地实现)-连接线上钱包
index.js
const Web3 = require('web3')
const setupProvider = require('./lib/setup-provider.js') //
// setup web3
// var provider = setupProvider()//1 设置provider
var web3 = new Web3(provider)
web3.currentProvider = provider
web3.setProvider = function(){
console.log('MetaMask - overrode web3.setProvider')
} //
// export web3
//
console.log(web3.currentProvider)
// console.log(web3.version.network)
// console.log(web3.eth.blockNumber)
global.web3 = web3
zero-client/lib/setup-provider.js
const setupIframe = require('./setup-iframe.js')
const MetamaskInpageProvider = require('metamask-crx/app/scripts/lib/inpage-provider.js') module.exports = getProvider function getProvider(){//1 if (global.web3) {//2如果有安装插件,那么就使用插件的web3
console.log('MetaMask ZeroClient - using environmental web3 provider')
return global.web3.currentProvider
}
//3如果没有就自己设置
console.log('MetaMask ZeroClient - injecting zero-client iframe!')
var iframeStream = setupIframe({ //4设置iframe
zeroClientProvider: 'http://localhost:9001', //4服务器端为9001
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'],
}) var inpageProvider = new MetamaskInpageProvider(iframeStream) //6根据iframe流去设置MetamaskInpageProvider
return inpageProvider }
zero-client/lib/setup-iframe.js
const Iframe = require('iframe')
const IframeStream = require('iframe-stream').IframeStream module.exports = setupIframe function setupIframe(opts) {//4
opts = opts || {}
var frame = Iframe({
src: opts.zeroClientProvider || 'https://zero.metamask.io/', //5就是如果没有设置服务器,那么就连接zero.metamask.io(docker设置,下面有讲)
container: document.head,
sandboxAttributes: opts.sandboxAttributes || ['allow-scripts', 'allow-popups'],
})
var iframe = frame.iframe
var iframeStream = new IframeStream(iframe)//5 然后创建iframe流 return iframeStream
}
metamask-crx/app/scripts/lib/inpage-provider.js
const pipe = require('pump')
const StreamProvider = require('web3-stream-provider')
const LocalStorageStore = require('obs-store')
const ObjectMultiplex = require('./obj-multiplex')
const createRandomId = require('./random-id') module.exports = MetamaskInpageProvider function MetamaskInpageProvider (connectionStream) {//7
const self = this // setup connectionStream multiplexing
var multiStream = self.multiStream = ObjectMultiplex()//8生成多路复用流
pipe(//8将多路复用流与iframe流连接
connectionStream,
multiStream,
connectionStream,
(err) => logStreamDisconnectWarning('MetaMask', err)
) // subscribe to metamask public config (one-way)
self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' })//8设置本地存储
pipe(//8在多路复用流中添加publicConfig流用以接收本地存储信息
multiStream.createStream('publicConfig'),
self.publicConfigStore,
(err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err)
) // connect to async provider
const asyncProvider = self.asyncProvider = new StreamProvider()//8设置rpc provider流
pipe(
asyncProvider,
multiStream.createStream('provider'),
asyncProvider,
(err) => logStreamDisconnectWarning('MetaMask RpcProvider', err)
) self.idMap = {}
// handle sendAsync requests via asyncProvider
self.sendAsync = function (payload, cb) {
// rewrite request ids
var request = eachJsonMessage(payload, (message) => {//9 10 transformFn即这里的回调参数,payload为message
var newId = createRandomId()//11这里是将json rpc中的id随机生成一个更复杂的id值去指明该笔request
self.idMap[newId] = message.id //11保留旧的id,因为之后返回时返回的还是旧ID,新id只在过程中使用
message.id = newId
return message
})
// forward to asyncProvider
asyncProvider.sendAsync(request, function (err, res) {
if (err) return cb(err)
// transform messages to original ids
eachJsonMessage(res, (message) => {
var oldId = self.idMap[message.id]
delete self.idMap[message.id]
message.id = oldId
return message
})
cb(null, res)
})
}
} MetamaskInpageProvider.prototype.send = function (payload) {
const self = this let selectedAddress
let result = null
switch (payload.method) { case 'eth_accounts':
// read from localStorage
selectedAddress = self.publicConfigStore.getState().selectedAddress
result = selectedAddress ? [selectedAddress] : []
break case 'eth_coinbase':
// read from localStorage
selectedAddress = self.publicConfigStore.getState().selectedAddress
result = selectedAddress
break case 'eth_uninstallFilter':
self.sendAsync(payload, noop)
result = true
break case 'net_version':
let networkVersion = self.publicConfigStore.getState().networkVersion
result = networkVersion
break // throw not-supported Error
default:
var link = 'https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client'
var message = `The MetaMask Web3 object does not support synchronous methods like ${payload.method} without a callback parameter. See ${link} for details.`
throw new Error(message) } // return the result
return {
id: payload.id,
jsonrpc: payload.jsonrpc,
result: result,
}
} MetamaskInpageProvider.prototype.sendAsync = function () {
throw new Error('MetamaskInpageProvider - sendAsync not overwritten')
} MetamaskInpageProvider.prototype.isConnected = function () {
return true
} MetamaskInpageProvider.prototype.isMetaMask = true // util function eachJsonMessage (payload, transformFn) {//9
if (Array.isArray(payload)) {//9如果是数组,说明一次要执行多个request
return payload.map(transformFn)//9所以将数组中的request一个个传入transformFn,最后传出结果的数组
} else {
return transformFn(payload)//9如果不是,则说明是一个request,直接做transformFn的参数即可
}
} function logStreamDisconnectWarning(remoteLabel, err){
let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}`
if (err) warningMsg += '\n' + err.stack
console.warn(warningMsg)
} function noop () {}
从上面的代码中我们可以看见,如果没有设置provider的话(zeroClientProvider: 'http://localhost:9001'),那么就会默认为https://zero.metamask.io/
这是通过docker进行设置的,如下:
Dockerfile
FROM node:0.12
MAINTAINER kumavis # setup app dir
RUN mkdir -p /www/
WORKDIR /www/ # install dependencies
COPY ./package.json /www/package.json
RUN npm install # copy over app dir
COPY ./ /www/ # run tests
RUN npm test # start server
CMD npm start # expose server
EXPOSE
从这里我们可以看出基本上就是本文档的复制粘贴,部署了一个服务器,并提供了9000作为端口
docker-compose.yml
zeroClient:
image: kumavis/zeroclient
restart: always
environment:
VIRTUAL_HOST: "zero.metamask.io"
VIRTUAL_PORT: ""
PORT: ""
ports:
- ""
因为该项目的作者已经将该镜像上传,所以我们可以根据 docker-compose.yml的设置直接拉取镜像进行使用:
userdeMBP:zero-client user$ docker-compose up
Pulling zeroClient (kumavis/zeroclient:)...
latest: Pulling from kumavis/zeroclient
d4bce7fd68df: Pull complete
a3ed95caeb02: Pull complete
: Pull complete
5dcab2c7e430: Pull complete
dc54ada22a60: Pull complete
f4dbf915606d: Pull complete
2e6a016344c7: Pull complete
310ce789aae0: Pull complete
d9f04797303f: Pull complete
e69d1d8ba6cd: Pull complete
508a0e5f7217: Pull complete
Digest: sha256:6a7936b08541ced92fc35f63033046942df09e6a48a56f199de315eac002be51
Status: Downloaded newer image for kumavis/zeroclient:latest
Creating zero-client_zeroClient_1 ... done
Attaching to zero-client_zeroClient_1
zeroClient_1 |
zeroClient_1 | > metamask-zeroclient@1.0. start /www
zeroClient_1 | > node server.js
zeroClient_1 |
zeroClient_1 | MetaMask ZeroClient iframe server listening on
然后这时候将zero-client/lib/setup-provider.js中的http://localhost:9001去掉,让其连接https://zero.metamask.io/:
得到结果:
然后在shared-worker文件夹中我们能够看到有:
zero-client/shared-worker/client.js
var worker = new SharedWorker("worker.js"); var id = Number.MAX_SAFE_INTEGER*Math.random()//3 随机生成id值 worker.port.addEventListener("message", function(e) {
console.log(e)
console.log(id)
console.log("message")
console.log(e.data);
}, false); worker.port.start();//1 激活端口 // post a message to the shared web worker
worker.port.postMessage(id)//3 发送id,触发server.js的"message"事件
扩展,参考https://www.jb51.net/html5/551063.html:
SharedWorker类(html5)
SharedWorker的实质在于share,不同的线程可以共享一个线程,他们的数据也是共享的。
本例子是通过addEventListener()方法监听message事件,需要worker.port.start()方法激活端口。
也有下面这样的写法:
worker.port.onmessage = function(e) {
console.log(e)
console.log(id)
console.log("message")
console.log(e.data);
}
使用事件句柄的方式将听message事件,不需要调用worker.port.start()
不同于worker,当有人和他通信时触发connect事件(如下),然后他的message事件是绑定在messagePort对象上的
zero-client/shared-worker/server.js
var connections = // count active connections self.addEventListener("connect", function (e) {//2 进行连接var port = e.ports[]
connections++ port.addEventListener("message", function (e) {//4 收到消息id = e.data
port.postMessage("Hello " + e.data + " (port #" + connections + ")")//5 post回消息,然后触发client.js的"message"事件
}, false) port.start()//2 打开端口 }, false)
run example
beefy client.js:index.js server.js:worker.js --live --open
然后我们为了将这个例子运行起来,需要对package.json进行更改,添加"example":
"scripts": {
"test": "echo \"Error: no tests yet\" && exit 0",
"start": "./example.sh",
"example": "beefy shared-worker/client.js:index.js shared-worker/server.js:worker.js 9003 --live --open "
},
这里没有设置端口,会随机使用一个端口,但是你也可以自己设置,这里我们设置为9003,然后运行:
userdeMacBook-Pro:zero-client user$ npm run example > metamask-zeroclient@2.0. example /Users/user/zero-client
> beefy shared-worker/client.js:index.js shared-worker/server.js:worker.js --live --open beefy (v2.1.8) is listening on http://127.0.0.1:9003
70ms 552B /
793ms .36KB /index.js ➞ ./node_modules/browserify ./shared-worker/client.js -d
30ms 552B /
17ms .36KB /index.js ➞ ./node_modules/browserify ./shared-worker/client.js -d
36ms 552B /
10ms .36KB /index.js ➞ ./node_modules/browserify ./shared-worker/client.js -d
19ms 552B /
8ms .36KB /index.js ➞ ./node_modules/browserify ./shared-worker/client.js -d
并且将index.html上的bundle.js改为index.js:
<!doctype html> <html lang="en">
<head>
<meta charset="utf-8"> <title>MetaMask ZeroClient Iframe</title>
<span style="white-space:pre"></span>
<link href="http://www.lituanmin.com/favicon.ico" rel="icon" type="image/x-icon" />
<meta name="description" content="MetaMask ZeroClient">
<meta name="author" content="MetaMask"> <!--[if lt IE ]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head> <body>
Hello! I am the MetaMask iframe.
<script src="/index.js"></script>
</body>
</html>
返回:

刷新一次可以看见port 4变成了port 5,是第五次访问
MetaMask/zero-client的更多相关文章
- vmware里面的名词 vSphere、vCenter Server、ESXI、vSphere Client
vmware里面的名词 vSphere.vCenter Server.ESXI.vSphere Client vSphere.vCenter Server.ESXI.vSphere Client VS ...
- Apache2.4:AH01630 client denied by server configuration
问题说明:Apache服务总共有4个,是为了防止单点故障和负载均衡,负载均衡控制由局方的F5提供. 访问的内容在NAS存储上,现象是直接访问每个apache的服务内容都是没有问题,但是从负载地址过来的 ...
- [异常解决] windows用SSH和linux同步文件&linux开启SSH&ssh client 报 algorithm negotiation failed的解决方法之一
1.安装.配置与启动 SSH分客户端openssh-client和openssh-server 如果你只是想登陆别的机器的SSH只需要安装openssh-client(ubuntu有默认安装,如果没有 ...
- xamarin IOS 报错处理: an error occurred on client Build420719 while
xamarin IOS 开发时如果报错如下: an error occurred on client Build420719 while...... 出现如下问题时,可能是1.丢失文件2.没有包括在项 ...
- ASP.NET OAuth:access token的加密解密,client secret与refresh token的生成
在 ASP.NET OWIN OAuth(Microsoft.Owin.Security.OAuth)中,access token 的默认加密方法是: 1) System.Security.Crypt ...
- 在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token
OAuth真是一个复杂的东东,即使你把OAuth规范倒背如流,在具体实现时也会无从下手.因此,Microsoft.Owin.Security.OAuth应运而生(它的实现代码在Katana项目中),帮 ...
- [OAuth]基于DotNetOpenAuth实现Client Credentials Grant
Client Credentials Grant是指直接由Client向Authorization Server请求access token,无需用户(Resource Owner)的授权.比如我们提 ...
- OData Client Code Generator
转发. [Tutorial & Sample] How to use OData Client Code Generator to generate client-side proxy cla ...
- offset、client、scroll开头的属性归纳总结
HTML元素有几个offset.client.scroll开头的属性,总是让人摸不着头脑.在书中看到记下来,分享给需要的小伙伴.主要是以下几个属性: 第一组:offsetWidth,offsetHei ...
随机推荐
- 撩课-Web大前端每天5道面试题-Day28
1.用setTimeout()方法来模拟setInterval()与setInterval()之间的什么区别? 首先来看setInterval的缺陷,使用setInterval()创建的定时器确保了定 ...
- 深入理解 Java Object
Java中的Object对象为所有对象的直接或间接父对象,里面定义的几个方法容易被忽略却非常重要.以下来自Effective Java 对Object中几个关键方法的应用说明. public clas ...
- Java图片验证码乱码问题
有时部署到linux服务器上的web项目的图形验证码可能会出现乱码问题 这不是编码格式出错了,而是可能服务器上没有图形验证码中限定的那种字体 比如生成图形验证码的代码: Font font = new ...
- C# HmacSha512 与 java HmacSha512 加密
C# HmacSha512 与 java HmacSha512 加密. /// <summary> /// HmacSha512 加密 /// </summary> /// & ...
- JConsole连接远程linux服务器配置
1.在远程机的tomcat的catalina.sh中加入配置 (catalina.sh路径在tomcat/bin下面 如/usr/local/tomcat/bin) if [ "$1&quo ...
- 【代码笔记】iOS-自定义alertView
一,效果图. 二,代码. ViewController.h #import <UIKit/UIKit.h> @interface ViewController : UIViewContro ...
- Django使用多个数据库
一.定义数据库 使用Django的多个数据库的第一步是告诉Django将使用的数据库服务器. 这是使用DATABASES设置完成的. 此设置将数据库别名映射到该特定连接的设置字典,该数据库别名是一种在 ...
- 如何将在线电子书保存为pdf格式
网上有很多免费的在线电子书籍,没有pdf格式,不方便离线阅读,也不方便做记录,所以找了几个将在线内容制作成pdf文件的方法. 一.如果网站上的书籍内容没有分页,所有内容都直接显示出来了,最简单,直接将 ...
- ActiveReports 报表控件V12新特性 -- RPX报表转换为RDL报表
ActiveReports是一款专注于 .NET 平台的报表控件,全面满足 HTML5 / WinForms / ASP.NET / ASP.NET MVC / WPF 等平台下报表设计和开发工作需求 ...
- tinymce4.x 上传本地图片(自己写个插件)
tinymce是一款挺不错的html文本编辑器.但是添加图片是直接添加链接,不能直接选择本地图片. 下面我写了一个插件用于直接上传本地图片. 在tinymce的plugins目录下新建一个upload ...