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 \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 > ) {
this.emit('error', warningMsg)
}
} function noop () {}
RpcEngine——MetaMask/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 =
end()
})
JSON RPC are handled asynchronously, stepping down the stack until complete.异步处理request,直到返回结果
let request = { id: , 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 =
end()
}) let payload = { id: , 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, , '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 =
next()
}) engine.push(function (req, res, next, end) {
res.result = req.resultShouldBe
end()
}) let payload = { id: , 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, , '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: , 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: , 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: , jsonrpc: '2.0', method: 'hello' }
let payloadB = { id: , 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[].result, , 'has expected result')
assert.equal(res[].result, , '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: , 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: , jsonrpc: '2.0', method: 'hello' } engine.handle(payload, function (err, res) {
assert.ifError(err, 'did not error') //说明next只是将处理程序先压入堆栈中,结果返回前再按先进后出的顺序处理
assert.equal(events[], '1-next', '(event 0) was "1-next"')
assert.equal(events[], '2-next', '(event 1) was "2-next"')
assert.equal(events[], '3-end', '(event 2) was "3-end"')
assert.equal(events[], '2-return', '(event 3) was "2-return"')
assert.equal(events[], '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: , 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: , 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进行一系列处理,但是最后输出的给用户看的表示结果还是一样的
metamask源码学习-inpage.js的更多相关文章
-
metamask源码学习-contentscript.js
When a new site is visited, the WebExtension creates a new ContentScript in that page's context, whi ...
-
metamask源码学习-background.js
这个就是浏览器后台所进行操作的地方了,它就是页面也区块链进行交互的中间部分. metamask-background描述了为web扩展单例的文件app/scripts/background.js.该上 ...
-
metamask源码学习-ui/index.js
The UI-即上图左下角metamask-ui部分,即其图形化界面 The MetaMask UI is essentially just a website that can be configu ...
-
metamask源码学习导论
()MetaMask Browser Extension https://github.com/MetaMask/metamask-extension 这就是整个metamask的源码所在之处,好好看 ...
-
metamask源码学习-controller-transaction
()metamask-extension/app/scripts/controllers/transactions Transaction Controller is an aggregate of ...
-
metamask源码学习-metamask-controller.js
The MetaMask Controller——The central metamask controller. Aggregates other controllers and exports a ...
-
metamask源码学习-controllers-network
https://github.com/MetaMask/metamask-extension/tree/master/app/scripts/controllers/network metamask- ...
-
【 js 基础 】【 源码学习 】源码设计 (持续更新)
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析第二部分:undersc ...
-
Underscore.js 源码学习笔记(下)
上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...
随机推荐
-
arch+xfce4系统配置
音量控制快捷键插件: sudo pacman -S xfce4-volumed
-
伪分布模式下执行wordcount实例时报错解决办法
问题1.不能分配内存,错误提示如下: FAILEDjava.lang.RuntimeException: Error while running command to get file permiss ...
-
1.Mariadb(mysql)基本操作
1.:安装与初始化 1)安装 yum install -y mariadb\* 2)初始化 systemctl restart mariadb systemctl enable mariadb my ...
-
ecshop开发日志之虚拟商品发送邮件通知
购买虚拟商品,系统会在支付后自动发送邮件到用户填写的邮件地址中,追踪过程如下首先在订单列表中可以获得到处理订单的php文件为flow.php,之后在最后一步url地址显示为http://localho ...
-
Enable OWIN Cross-origin Request
微软出了一套解决方式能够解决 "同意WebAPI的 CORS 请求" http://www.asp.net/web-api/overview/security/enabling-c ...
-
Linux---江湖
Linux江湖13:我该如何备份系统 Posted on 2014-12-18 10:39 京山游侠 阅读(497) 评论(3) 编辑 收藏 在前面的一些文章中,我反复提到经常会把系统搞崩溃,所以备份 ...
-
php键值相同的项数值相加
php 合并一个二维数组相同项,数量则相加 $arr = array( array( 'user_id' => 100, 'goods_id' => 10, 'number' => ...
-
【并发编程】【JDK源码】JDK的(J.U.C)java.util.concurrent包结构
本文从JDK源码包中截取出concurrent包的所有类,对该包整体结构进行一个概述. 在JDK1.5之前,Java中要进行并发编程时,通常需要由程序员独立完成代码实现.当然也有一些开源的框架提供了这 ...
-
HBase1.0.1.1 API与原来有所不同
package com.felix.hbaseapi_test; /* 这是旧版的 API操作 */ public class hbaseapifelix { public static final ...
-
codeforces9A
Die Roll CodeForces - 9A Yakko,Wakko和Dot,世界著名的狂欢三宝,哈哈,不知道你是否看过这个动画片. 某一天,过年了,他们决定暂定卡通表演,并去某些地方旅游一下.Y ...