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')
const LocalMessageDuplexStream = require('post-message-stream')
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 \nhttps://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 += '\n' + 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进行一系列处理,但是最后输出的给用户看的表示结果还是一样的

猜你喜欢

转载自www.cnblogs.com/wanghui-garcia/p/9713842.html