zoukankan      html  css  js  c++  java
  • metamask源码学习-inpage.js

    The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the InpageProvider in the inpage.js script, you will be able to understand how the port-stream is just a thin wrapper around the postMessage API, and a similar stream API can be wrapped around any communication channel to communicate with the MetaMaskController via its setupUntrustedCommunication(stream, domain) method.

    将MetaMask移植到新平台上最令人困惑的部分是我们通过上下文之间的一系列流提供Web3 API的方式。当你了解了我们如何在inpage.js脚本中创建InpageProvider之后,您将能够理解端口流为何只是一个围绕postMessage API的thin包装器以及为何一个类似的流API可以被包装在任何通信通道上,通过它的setupUntrustedCommunication(流,域)方法与MetaMaskController进行通信。

    postMessage API:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

    window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机  (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

    在本地测试一下,通过下面UI code,调用web3:

    <script type="text/javascript">
            //这些注释的地方都是之前
        
            window.addEventListener('load', function() {
                console.log(window.web3);  //调用web3
            });
    </script>

    得到结果:

    metamask-extension/app/scripts/inpage.js

    /*global Web3*/
    cleanContextForImports()
    require('web3/dist/web3.min.js')
    const log = require('loglevel')//在本博客loglevel-metamask有介绍
    const LocalMessageDuplexStream = require('post-message-stream')//本博客post-message-stream的学习-metamask
    const setupDappAutoReload = require('./lib/auto-reload.js')
    const MetamaskInpageProvider = require('./lib/inpage-provider.js')
    restoreContextAfterImports()
    
    log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
    
    //
    // setup plugin communication
    //
    
    // setup background connection
    var metamaskStream = new LocalMessageDuplexStream({//为页面inpage与contentscript建立双向流连接
      name: 'inpage',  //如上面的例子所示
      target: 'contentscript',
    })
    
    // compose the inpage provider ,然后组成inpageProvider
    var inpageProvider = new MetamaskInpageProvider(metamaskStream)
    
    //
    // setup web3
    //
    
    if (typeof window.web3 !== 'undefined') { //查看页面是否连接上了除了metamask以外的web3,有则删除;因为此时metamask还没有构建好自己本身的web3
      throw new Error(`MetaMask detected another web3.
         MetaMask will not work reliably with another web3 extension.
         This usually happens if you have two MetaMasks installed,
         or MetaMask and another web3 extension. Please remove one
         and try again.`)
    }
    var web3 = new Web3(inpageProvider) //然后将上面构建的inpageProvider部署到其自身的web3中
    web3.setProvider = function () {
      log.debug('MetaMask - overrode web3.setProvider')
    }
    log.debug('MetaMask - injected web3')//到这里metamask的injected web3就部署好了
    
    setupDappAutoReload(web3, inpageProvider.publicConfigStore) //这样但dapp安装metamask就能够直接使用web3了,因为它会自动下载好
                                        //在上面例子的测试结果中也能看见publicConfigStore的信息
    // export global web3, with usage-detection and deprecation warning
    
    /* TODO: Uncomment this area once auto-reload.js has been deprecated:
    let hasBeenWarned = false
    global.web3 = new Proxy(web3, {
      get: (_web3, key) => {
        // show warning once on web3 access
        if (!hasBeenWarned && key !== 'currentProvider') {
          console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider 
    https://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
          hasBeenWarned = true
        }
        // return value normally
        return _web3[key]
      },
      set: (_web3, key, value) => {
        // set value normally
        _web3[key] = value
      },
    })
    */
    
    // set web3 defaultAccount
    inpageProvider.publicConfigStore.subscribe(function (state) {//通过subscribe得到整个publicConfigStore存储的state信息,然后再从中得到selectedAddress
      web3.eth.defaultAccount = state.selectedAddress
    })
    
    // need to make sure we aren't affected by overlapping namespaces
    // and that we dont affect the app with our namespace
    // mostly a fix for web3's BigNumber if AMD's "define" is defined...
    var __define
    
    /**
     * Caches reference to global define object and deletes it to
     * avoid conflicts with other global define objects, such as
     * AMD's define function
     */
    function cleanContextForImports () {
      __define = global.define
      try {
        global.define = undefined
      } catch (_) {
        console.warn('MetaMask - global.define could not be deleted.')
      }
    }
    
    /**
     * Restores global define object from cached reference
     */
    function restoreContextAfterImports () {
      try {
        global.define = __define
      } catch (_) {
        console.warn('MetaMask - global.define could not be overwritten.')
      }
    }

    上面代码调用的一些其他代码的解释:

    pump = require('pump')

    pump简介

    https://github.com/terinjokes/gulp-uglify/blob/master/docs/why-use-pump/README.md#why-use-pump

    当使用来自Node.js的管道时,错误不会通过管道流向前传播,如果目标流关闭,源流也不会关闭。pump模块将这些问题规范化,并在回调中传递错误。

    pump可以使我们更容易找到代码出错位置。

    更详细的内容看被博客的 pump模块的学习-metamask

     

    metamask-inpage-provider/index.js

    https://github.com/MetaMask/metamask-inpage-provider/blob/master/index.js

    const pump = require('pump')
    const RpcEngine = require('json-rpc-engine') //ethereum的方法是通过json-rpc进行调用的,这就是a tool for processing JSON RPC,看下面
    const createErrorMiddleware = require('./createErrorMiddleware')
    const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') //设置了相应的push,看下面
    const createStreamMiddleware = require('json-rpc-middleware-stream')
    const LocalStorageStore = require('obs-store')
    const asStream = require('obs-store/lib/asStream') //建立有关obs-store的双向流
    const ObjectMultiplex = require('obj-multiplex')
    const util = require('util')
    const EventEmitter = require('events')
    
    module.exports = MetamaskInpageProvider
    
    util.inherits(MetamaskInpageProvider, EventEmitter)
    
    function MetamaskInpageProvider (connectionStream) {
      const self = this
    
      // setup connectionStream multiplexing 建立多路复用的连接流
      const mux = self.mux = new ObjectMultiplex() 
      pump( 
        connectionStream,
        mux,
        connectionStream,
        logStreamDisconnectWarning.bind(this, 'MetaMask')
      )
    
      // subscribe to metamask public config (one-way)
      self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' })
    
      pump(
        mux.createStream('publicConfig'),
        asStream(self.publicConfigStore),
        logStreamDisconnectWarning.bind(this, 'MetaMask PublicConfigStore')
      )
    
      // ignore phishing warning message (handled elsewhere)
      mux.ignoreStream('phishing') //忽略钓鱼网站
    
      // connect to async provider
      const streamMiddleware = createStreamMiddleware()
      pump(
        streamMiddleware.stream,
        mux.createStream('provider'),
        streamMiddleware.stream,
        logStreamDisconnectWarning.bind(this, 'MetaMask RpcProvider')
      )
    
      // handle sendAsync requests via dapp-side rpc engine
      const rpcEngine = new RpcEngine()
      rpcEngine.push(createIdRemapMiddleware()) //作用看下面
      rpcEngine.push(createErrorMiddleware())//用于得到操作的错误并显示相应信息
      rpcEngine.push(streamMiddleware)
      self.rpcEngine = rpcEngine
    }
    
    // handle sendAsync requests via asyncProvider
    // also remap ids inbound and outbound
    MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) {//如果网页上调用web3使用的是那些需要异步等待返回结果的方法的时候其实就是来这里调用MetamaskInpageProvider.prototype.sendAsync这个方法
      const self = this
    
      if (payload.method === 'eth_signTypedData') {//这个方法在下一个版本就过时了,不用了
        console.warn('MetaMask: This experimental version of eth_signTypedData will be deprecated in the next release in favor of the standard as defined in EIP-712. See https://git.io/fNzPl for more information on the new standard.')
      }
    
      self.rpcEngine.handle(payload, cb)
    }
    
    
    MetamaskInpageProvider.prototype.send = function (payload) {//如果网页上调用这些不用异步就能够直接得到结果的方法的时候,其实就是调用了MetamaskInpageProvider.prototype.send这个函数
      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 || null
          break
    
        case 'eth_uninstallFilter':
          self.sendAsync(payload, noop)
          result = true
          break
    
        case 'net_version':
          const networkVersion = self.publicConfigStore.getState().networkVersion
          result = networkVersion || null
          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.isConnected = function () {
      return true
    }
    
    MetamaskInpageProvider.prototype.isMetaMask = true
    
    // util
    
    function logStreamDisconnectWarning (remoteLabel, err) {
      let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}`
      if (err) warningMsg += '
    ' + err.stack
      console.warn(warningMsg)
      const listeners = this.listenerCount('error')
      if (listeners > 0) {
        this.emit('error', warningMsg)
      }
    }
    
    function noop () {}

    RpcEngine——/json-rpc-engine

    https://github.com/MetaMask/json-rpc-engine

    a tool for processing JSON RPC

    usage

    const RpcEngine = require('json-rpc-engine')
    let engine = new RpcEngine()

    Build a stack of json rpc processors by pushing in RpcEngine middleware.通过push RpcEngine中间件构建一个json rpc处理器堆栈,处理步骤为先进后出,handle时得到的结果是与push时作出的处理相关的

    engine.push(function(req, res, next, end){
      res.result = 42
      end()
    })

    JSON RPC are handled asynchronously, stepping down the stack until complete.异步处理request,直到返回结果

    let request = { id: 1, jsonrpc: '2.0', method: 'hello' }
    
    engine.handle(request, function(err, res){
      // do something with res.result,res.result即为push中设置的true
    })

    RpcEngine middleware has direct access to the request and response objects. It can let processing continue down the stack with next() or complete the request with end().RpcEngine中间件可以直接访问请求和响应对象。它可以使用next()继续处理堆栈,也可以使用end()完成请求

    engine.push(function(req, res, next, end){
      if (req.skipCache) return next()
      res.result = getResultFromCache(req)
      end()
    })

    By passing a 'return handler' to the next function, you can get a peek at the result before it returns.通过将“返回处理程序”传递给下一个函数,您可以在结果返回之前看到它

    engine.push(function(req, res, next, end){
      next(function(cb){//就是先压入堆栈中,不进行处理,等到所以push都解决完后再返回处理
        insertIntoCache(res, cb)
      })
    })
     

    RpcEngines can be nested by converting them to middleware asMiddleware(engine)。rpcengine可以通过将它们转换为中间件(中间件)来嵌套

    const asMiddleware = require('json-rpc-engine/lib/asMiddleware')
    
    let engine = new RpcEngine()
    let subengine = new RpcEngine()
    engine.push(asMiddleware(subengine))

    gotchas陷阱

    Handle errors via end(err), NOT next(err).解决error使用的是end(),而不是next()

    /* INCORRECT */
    engine.push(function(req, res, next, end){
      next(new Error())
    })
    
    /* CORRECT */
    engine.push(function(req, res, next, end){
      end(new Error())
    })

    json-rpc-engine/test/basic.spec.js

    举例说明:

    /* eslint-env mocha */
    'use strict'
    
    const assert = require('assert')
    const RpcEngine = require('../src/index.js')
    
    describe('basic tests', function () {
    
      it('basic middleware test', function (done) {
        let engine = new RpcEngine()
    
        engine.push(function (req, res, next, end) {
          req.method = 'banana'
          res.result = 42
          end()
        })
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' }
    
        engine.handle(payload, function (err, res) {
          assert.ifError(err, 'did not error')
          assert(res, 'has res')
          assert.equal(res.result, 42, 'has expected result')
          assert.equal(payload.method, 'hello', 'original request object is not mutated by middleware') //payload.method仍然是'hello',而不会被改成'banana'
          done()
        })
      })
    
      it('interacting middleware test', function (done) { //两个push交互
        let engine = new RpcEngine()
    
        engine.push(function (req, res, next, end) {
          req.resultShouldBe = 42
          next()
        })
    
        engine.push(function (req, res, next, end) {
          res.result = req.resultShouldBe
          end()
        })
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' }
    
        engine.handle(payload, function (err, res) {
          assert.ifError(err, 'did not error')
          assert(res, 'has res')
          assert.equal(res.result, 42, 'has expected result')
          done()
        })
      })
    
      it('erroring middleware test', function (done) {
        let engine = new RpcEngine()
    
        engine.push(function (req, res, next, end) {
          end(new Error('no bueno'))
        })
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' }
    
        engine.handle(payload, function (err, res) {
          assert(err, 'did error')
          assert(res, 'does have response')
          assert(res.error, 'does have error on response')
          done()
        })
      })
    
      it('empty middleware test', function (done) {
        let engine = new RpcEngine()
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' } //如果没有push。handle将报错
    
        engine.handle(payload, function (err, res) {
          assert(err, 'did error')
          done()
        })
      })
    
      it('handle batch payloads', function (done) {
        let engine = new RpcEngine()
    
        engine.push(function (req, res, next, end) {
          res.result = req.id
          end()
        })
    
        let payloadA = { id: 1, jsonrpc: '2.0', method: 'hello' }
        let payloadB = { id: 2, jsonrpc: '2.0', method: 'hello' }
        let payload = [payloadA, payloadB] //可以一下子handle多个push
    
        engine.handle(payload, function (err, res) {
          assert.ifError(err, 'did not error')
          assert(res, 'has res')
          assert(Array.isArray(res), 'res is array')
          assert.equal(res[0].result, 1, 'has expected result')
          assert.equal(res[1].result, 2, 'has expected result')
          done()
        })
      })
    
      it('return handlers test', function (done) {
        let engine = new RpcEngine()
    
        engine.push(function (req, res, next, end) {
          next(function (cb) {
            res.sawReturnHandler = true
            cb()
          })
        })
    
        engine.push(function (req, res, next, end) {
          res.result = true
          end()
        })
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' }
    
        engine.handle(payload, function (err, res) {
          assert.ifError(err, 'did not error')
          assert(res, 'has res')
          assert(res.sawReturnHandler, 'saw return handler')
          done()
        })
      })
    
      it('return order of events', function (done) {
        let engine = new RpcEngine()
    
        let events = []
    
        engine.push(function (req, res, next, end) {
          events.push('1-next')
          next(function (cb) {
            events.push('1-return')
            cb()
          })
        })
    
        engine.push(function (req, res, next, end) {
          events.push('2-next')
          next(function (cb) {
            events.push('2-return')
            cb()
          })
        })
    
        engine.push(function (req, res, next, end) {
          events.push('3-end')
          res.result = true
          end()
        })
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' }
    
        engine.handle(payload, function (err, res) {
          assert.ifError(err, 'did not error') //说明next只是将处理程序先压入堆栈中,结果返回前再按先进后出的顺序处理
          assert.equal(events[0], '1-next', '(event 0) was "1-next"')
          assert.equal(events[1], '2-next', '(event 1) was "2-next"')
          assert.equal(events[2], '3-end', '(event 2) was "3-end"')
          assert.equal(events[3], '2-return', '(event 3) was "2-return"')
          assert.equal(events[4], '1-return', '(event 4) was "1-return"')
          done()
        })
      })
    })

    json-rpc-engine/test/asMiddleware.spec.js

    /* eslint-env mocha */
    'use strict'
    
    const assert = require('assert')
    const RpcEngine = require('../src/index.js')
    const asMiddleware = require('../src/asMiddleware.js')
    
    describe('asMiddleware', function () { //嵌套
      it('basic', function (done) {
        let engine = new RpcEngine()
        let subengine = new RpcEngine()
        let originalReq
    
        subengine.push(function (req, res, next, end) {
          originalReq = req
          res.result = 'saw subengine'
          end()
        })
    
        engine.push(asMiddleware(subengine))
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' }
    
        engine.handle(payload, function (err, res) {
          assert.ifError(err, 'did not error')
          assert(res, 'has res')
          assert.equal(originalReq.id, res.id, 'id matches')
          assert.equal(originalReq.jsonrpc, res.jsonrpc, 'jsonrpc version matches')
          assert.equal(res.result, 'saw subengine', 'response was handled by nested engine')
          done()
        })
      })
    })

    json-rpc-engine/src/idRemapMiddleware.js

    const getUniqueId = require('./getUniqueId')
    
    module.exports = createIdRemapMiddleware
    
    function createIdRemapMiddleware() {
      return (req, res, next, end) => {
        const originalId = req.id  
        const newId = getUniqueId()
        req.id = newId
        res.id = newId
        next((done) => {
          req.id = originalId
          res.id = originalId 
          done()
        })
      }
    }

    测试:

    json-rpc-engine/test/idRemapMiddleware.spec.js

    /* eslint-env mocha */
    'use strict'
    
    const assert = require('assert')
    const RpcEngine = require('../src/index.js')
    const createIdRemapMiddleware = require('../src/idRemapMiddleware.js')
    
    describe('idRemapMiddleware tests', function () {
      it('basic middleware test', function (done) {
        let engine = new RpcEngine()
    
        const observedIds = {
          before: {},
          after: {},
        }
    
        engine.push(function (req, res, next, end) {
          observedIds.before.req = req.id
          observedIds.before.res = res.id //设置使得handle时 res.id = req.id,两者结果相同
          next()
        })
        engine.push(createIdRemapMiddleware())
        engine.push(function (req, res, next, end) {
          observedIds.after.req = req.id
          observedIds.after.res = res.id 
          // set result so it doesnt error
          res.result = true
          end()
        })
    
        let payload = { id: 1, jsonrpc: '2.0', method: 'hello' }
        const payloadCopy = Object.assign({}, payload)
    
        engine.handle(payload, function (err, res) {
          assert.ifError(err, 'did not error')
          assert(res, 'has res')
          // collected data
          assert(observedIds.before.req, 'captured ids')
          assert(observedIds.before.res, 'captured ids')
          assert(observedIds.after.req, 'captured ids')
          assert(observedIds.after.res, 'captured ids')
          // data matches expectations
          assert.equal(observedIds.before.req, observedIds.before.res, 'ids match') //一开始两个时相同的
          assert.equal(observedIds.after.req, observedIds.after.res, 'ids match') //之后两个的结果也是相同的,但是变成了newId
          // correct behavior 
          assert.notEqual(observedIds.before.req, observedIds.after.req, 'ids are different') //前后的req.id不同了
          assert.equal(observedIds.before.req, res.id, 'result id matches original') //但是before和最后输出的结果res.id还是一样的
          assert.equal(payload.id, res.id, 'result id matches original')
          assert.equal(payloadCopy.id, res.id, 'result id matches original')
          done()
        })
      })
    })

    这里可以知道idRemapMiddleware的作用时在过程中间得到一个新的、比较复杂的Id进行一系列处理,但是最后输出的给用户看的表示结果还是一样的

  • 相关阅读:
    43、生鲜电商平台-你应该保留的一些学习态度与学习方法
    44、生鲜电商平台-Java后端生成Token架构与设计详解
    42、生鲜电商平台-商品的spu和sku数据结构设计与架构
    Bag of features:图像检索
    立体视觉—计算视差图
    三维重建——对极几何与基础矩阵
    计算机视觉——相机参数标定
    图像的拼接融合
    Unity Hub安装异常的解决方案
    针对“需要管理权限才能删除文件夹”的解决方案
  • 原文地址:https://www.cnblogs.com/wanghui-garcia/p/9713842.html
Copyright © 2011-2022 走看看