zoukankan      html  css  js  c++  java
  • 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.0 start /Users/user/zero-client
    > ./example.sh
    
    ./example.sh: line 3: beefy: command not found
    ./example.sh: line 4: 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.0 start: `./example.sh`
    npm ERR! spawn ENOENT
    npm ERR! 
    npm ERR! Failed at the metamask-zeroclient@2.0.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/2018-11-12T09_26_56_269Z-debug.log

    出错,没有安装模块beefy:

    什么是beefy及其使用

    userdeMacBook-Pro:zero-client user$ npm install beefy --save

    再运行又出错:

    userdeMacBook-Pro:zero-client user$ npm start
    
    > metamask-zeroclient@2.0.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
    200   58ms       261B  /
    200   70ms     1.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:46:17
        at process (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js:173:43)
        at ondir (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js:188:17)
        at load (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js:69:43)
        at onex (/Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js:92:31)
        at /Users/user/zero-client/node_modules/browser-resolve/node_modules/resolve/lib/async.js:22:47
        at FSReqCallback.oncomplete (fs.js:161:21)

    发现是在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:51:31
        at processDirs (/Users/user/zero-client/node_modules/resolve/lib/async.js:185:39)
        at ondir (/Users/user/zero-client/node_modules/resolve/lib/async.js:200:13)
        at load (/Users/user/zero-client/node_modules/resolve/lib/async.js:83:43)
        at onex (/Users/user/zero-client/node_modules/resolve/lib/async.js:108:17)
        at /Users/user/zero-client/node_modules/resolve/lib/async.js:12:69
        at FSReqCallback.oncomplete (fs.js:161:21)

    是因为下载下来的metamask-crx模块忘记运行npm install

    然后再运行就成功了:

    userdeMacBook-Pro:zero-client user$ npm start
    
    > metamask-zeroclient@2.0.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
    200   50ms       261B  /
    200   63ms     1.95KB  /bundle.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/.index.js -d
    200 2422ms     3.12MB  /zero.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/index.js -d
    200   32ms       427B  /
    404    1ms        12B  /background.js
    200 1497ms     2.33MB  /bundle.js ➞ ./node_modules/browserify ./frame.js -d
    200   25ms       261B  /
    200  116ms     1.95KB  /bundle.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/.index.js -d
    200 1720ms     3.12MB  /zero.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/index.js -d
    200    1ms       189B  /favicon.ico (generated)
    200   13ms       261B  /
    200   12ms     1.95KB  /bundle.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/.index.js -d
    200 1377ms     3.12MB  /zero.js ➞ /Users/user/zero-client/node_modules/browserify /Users/user/zero-client/index.js -d
    200   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 += '
    ' + 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

     从这里我们可以看出基本上就是本文档的复制粘贴,部署了一个服务器,并提供了9000作为端口

     docker-compose.yml

    zeroClient:
      image: kumavis/zeroclient
      restart: always
      environment:
        VIRTUAL_HOST: "zero.metamask.io"
        VIRTUAL_PORT: "9000"
        PORT: "9000"
      ports:
        - "9000"

     因为该项目的作者已经将该镜像上传,所以我们可以根据 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
    816152842605: 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.0 start /www
    zeroClient_1  | > node server.js
    zeroClient_1  | 
    zeroClient_1  | MetaMask ZeroClient iframe server listening on 9000

    然后这时候将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 = 0 // count active connections
    
    self.addEventListener("connect", function (e) {//2 进行连接var port = e.ports[0]
      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.0 example /Users/user/zero-client
    > beefy shared-worker/client.js:index.js shared-worker/server.js:worker.js 9003 --live --open 
    
    beefy (v2.1.8) is listening on http://127.0.0.1:9003
    200   70ms       552B  /
    200  793ms     2.36KB  /index.js ➞ ./node_modules/browserify ./shared-worker/client.js -d
    200   30ms       552B  /
    200   17ms     2.36KB  /index.js ➞ ./node_modules/browserify ./shared-worker/client.js -d
    200   36ms       552B  /
    200   10ms     2.36KB  /index.js ➞ ./node_modules/browserify ./shared-worker/client.js -d
    200   19ms       552B  /
    200    8ms     2.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 9]>
      <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,是第五次访问

     

     
  • 相关阅读:
    tesserocr与pytesseract模块的使用
    python pillow模块用法
    tesseract-ocr,tesseract,pytesseract在windows下怎么安装
    Python pillow库安装报错
    Python 让输入的密码不在屏幕上显示
    Linux 中CPU 和 GPU 的行为监控
    Linux之RedHat7如何更换yum源
    RHEL6搭建网络yum源仓库
    一文读懂内网、公网和NAT
    将Android手机无线连接到Ubuntu实现唱跳Rap
  • 原文地址:https://www.cnblogs.com/wanghui-garcia/p/9890803.html
Copyright © 2011-2022 走看看