前言
上一节我们介绍了钱包中主要的stream通信组件,这一节我们看他们具体是如何在钱包通信模块大显身手的。 本章涉及到的源码文件地址:
github.com/MetaMask/me… github.com/MetaMask/me… github.com/MetaMask/me… github.com/MetaMask/me… github.com/MetaMask/me…
与钱包通信的几种方式
DApp通过window.ethereum接口通信:
- 网页应用通过注入的Provider API与钱包交互
- 使用EIP-1193标准API请求签名、发送交易等
通过MetaMask UI界面通信:
- 用户通过弹出窗口(popup)、全屏界面或通知直接与钱包交互
- 内部使用trusted communication通道,权限最高
外部连接通信:
- 其他扩展或网页通过chrome.runtime.connect直接连接MetaMask
本章主要讲解DApp通过window.ethereum接口通信,也是最复杂的一种方式,其他通信方式可以大致看成它的子集。
步骤梳理与分析(DAPP到background.js)
这部分是从DAPP通过window.ethereum.request发起请求开始到请求进入background.js,请求在Stream中的流向。注意这不是完整的请求响应过程,把这部分单独拿出来为了让大家参考流程然后对照源码先熟悉一下,后面会给出完整的请求响应流程:
发起请求后的消息流向
1. DApp 发送请求
- DApp 调用
window.ethereum.request({ method: 'eth_requestAccounts' }) - 实际调用的是
MetaMaskInpageProvider(继承自StreamProvider)的request方法 - 该方法最终调用
_rpcEngine.handle(payload, callback)
2. 进入 JSON-RPC Engine
_rpcEngine.handle触发中间件链- 其中一个中间件是
StreamProvider构造时注入的_jsonRpcConnection.middleware(由createStreamMiddleware创建)
3. 通过中间件写入流
_jsonRpcConnection.middleware通过stream.push(req)将请求写入_jsonRpcConnection.stream(一个 Duplex 流)
4. pipeline 触发下游流
_jsonRpcConnection.stream通过 pipeline 连接到connectionStream(即Substream(METAMASK_EIP_1193_PROVIDER))- pipeline 机制会自动调用
Substream._write(),将请求写入 Substream
5. Substream 写入父 ObjectMultiplex
Substream._write()实现为:this._parent.push({ name: this._name, data: chunk })- 这里的 parent 是
ObjectMultiplex(如 inpage.js 里的mux) - 数据被 push 到
ObjectMultiplex的缓冲区
6. pipeline 触发 WindowPostMessageStream
ObjectMultiplex通过 pipeline 连接到WindowPostMessageStream(inpage.js 里的metamaskStream)- pipeline 机制会自动调用
WindowPostMessageStream._write(),将数据写入
7. WindowPostMessageStream 通过 postMessage 发送
WindowPostMessageStream._write()调用_postMessage(),实际通过window.postMessage发送数据到目标窗口(内容脚本)
8. contentscript 端 WindowPostMessageStream 接收
- contentscript 端的
WindowPostMessageStream(如pageStream)监听message事件,收到数据后调用_onData,数据进入流
9. pipeline 触发 ObjectMultiplex
pageStream通过 pipeline 连接到ObjectMultiplex(pageMux)- pipeline 机制会自动调用
ObjectMultiplex._write(),将数据分发到对应的 Substream(pageChannel)
10. pipeline 触发 extensionEip1193Channel
pageChannel通过 pipeline 连接到extensionEip1193Channel- 数据被写入
extensionEip1193Channel,再 push 到其父extensionMux
11. pipeline 触发 PortStream
extensionMux通过 pipeline 连接到PortStream(extensionStream)PortStream.write()通过Port.postMessage(req)发送数据到 background.js
12. background.js 端接收
- background.js 端的
Port监听onMessage,收到请求后进入后台处理
流程图表示
flowchart TD
subgraph 网页 [DApp]
A1["window.ethereum.request({ method: 'eth_requestAccounts' })"]
A2["MetaMaskInpageProvider.request()"]
A3["_rpcEngine.handle()"]
A4["_jsonRpcConnection.middleware(req)"]
A5["stream.push(req)"]
end
subgraph inpage.js
B1["Substream(METAMASK_EIP_1193_PROVIDER)"]
B2["ObjectMultiplex (mux)"]
B3["WindowPostMessageStream (metamaskStream)"]
end
subgraph contentscript.js
C1["WindowPostMessageStream (pageStream)"]
C2["ObjectMultiplex (pageMux)"]
C3["Substream(pageChannel)"]
C4["Substream(extensionEip1193Channel)"]
C5["ObjectMultiplex (extensionMux)"]
C6["PortStream (extensionStream)"]
end
subgraph background.js
D1["Port (browser.runtime.onConnect)"]
D2["MetaMask Controller"]
end
%% DApp 到 inpage.js
A1 --> A2 --> A3 --> A4 --> A5
A5 -->|pipeline| B1
B1 -->|push到父| B2
B2 -->|pipeline| B3
B3 -- window.postMessage --> C1
%% contentscript.js 内部流转
C1 -->|pipeline| C2
C2 -->|分发到| C3
C3 -->|pipeline| C4
C4 -->|push到父| C5
C5 -->|pipeline| C6
%% 到 background
C6 -- port.postMessage --> D1
D1 --> D2
%% 说明
classDef stream fill:#f9f,stroke:#333,stroke-width:2px;
classDef logic fill:#bbf,stroke:#333,stroke-width:2px;
class A1,A2,A3,A4,A5 logic;
class B1,B2,B3,C1,C2,C3,C4,C5,C6,D1 stream;
步骤梳理与分析(DAPP请求响应全流程)
请求方向(DApp → background.js)
- DAPP 发送请求(如 eth_requestAccounts)
_rpcEngine.handle(eth_requestAccounts)- 交给
Duplex::_jsonRpcConnection.middleware处理 middleware内部通过stream.push(req)写入Duplex::_jsonRpcConnection.stream缓冲区- pipeline 触发
Substream(METAMASK_EIP_1193_PROVIDER)::connectionStream._write() connectionStream._write()把请求 push 到父ObjectMultiplex::mux缓冲区- pipeline 触发
WindowPostMessageStream::metamaskStream._write() metamaskStream._write()通过window.postMessage()发送到 contentscript- contentscript 的
WindowPostMessageStream::pageStream接收 - pipeline 触发
ObjectMultiplex::pageMux._write(),分发到Substream(METAMASK_EIP_1193_PROVIDER)::pageChannel - pipeline 触发
Substream(METAMASK_EIP_1193_PROVIDER)::extensionEip1193Channel._write() extensionEip1193Channel.write()push 到父ObjectMultiplex::extensionMux- pipeline 触发
PortStream::extensionStream._write() extensionStream.write()通过Port.postMessage(req)发送到 background- background 端
PortStream::portStream._onMessage()接收 - 请求传入
metamask-controller.js.setupUntrustedCommunicationEip1193() - pipeline 触发
ObjectMultiplex::mux.write(),push 到Substream(METAMASK_EIP_1193_PROVIDER)::outStream - pipeline 触发
Duplex::providerStream.write() engine.handle最终处理请求,生成响应
响应方向(background.js → DApp)
- 响应 push 到
providerStream自身缓冲区 - pipeline 触发
Substream(METAMASK_EIP_1193_PROVIDER)::outStream.write(),写入父ObjectMultiplex::mux - pipeline 触发
PortStream::portStream._write(),响应发送回 contentscript - contentscript 端
PortStream::extensionStream._onMessage()接收 - pipeline 触发
ObjectMultiplex::extensionMux.write(),分发到Substream(METAMASK_EIP_1193_PROVIDER)::extensionEip1193Channel - pipeline 触发
Substream(METAMASK_EIP_1193_PROVIDER)::pageChannel.write(),写入父ObjectMultiplex::pageMux - pipeline 触发
WindowPostMessageStream::pageStream._write(),通过window.postMessage()发送到 inpage - inpage 端
WindowPostMessageStream::metamaskStream接收 - pipeline 触发
ObjectMultiplex::mux.write(),push 到Substream(METAMASK_EIP_1193_PROVIDER)::connectionStream - pipeline 触发
Duplex::_jsonRpcConnection.stream.write(),调用processMessage() processMessage()调用processResponse(),DAPP 拿到响应结果
流程图表示
flowchart TD
%% DApp -> background 请求链路
subgraph 网页 [DApp]
A1["window.ethereum.request(...)"]
A2["_rpcEngine.handle()"]
A3["_jsonRpcConnection.middleware(req)"]
A4["stream.push(req)"]
end
subgraph inpage.js
B1["Substream(METAMASK_EIP_1193_PROVIDER)::connectionStream._write()"]
B2["ObjectMultiplex::mux"]
B3["WindowPostMessageStream::metamaskStream._write()"]
end
subgraph contentscript.js
C1["WindowPostMessageStream::pageStream"]
C2["ObjectMultiplex::pageMux"]
C3["Substream::pageChannel"]
C4["Substream::extensionEip1193Channel"]
C5["ObjectMultiplex::extensionMux"]
C6["PortStream::extensionStream"]
end
subgraph background.js
D1["PortStream::portStream._onMessage()"]
D2["setupUntrustedCommunicationEip1193()"]
D3["ObjectMultiplex::mux"]
D4["Substream::outStream"]
D5["Duplex::providerStream.write()"]
D6["engine.handle()"]
end
%% background -> DApp 响应链路
subgraph background.js
E1["engine.handle() 生成响应"]
E2["providerStream.push(res)"]
E3["Substream::outStream.write()"]
E4["ObjectMultiplex::mux"]
E5["PortStream::portStream._write()"]
end
subgraph contentscript.js
F1["PortStream::extensionStream._onMessage()"]
F2["ObjectMultiplex::extensionMux"]
F3["Substream::extensionEip1193Channel"]
F4["Substream::pageChannel"]
F5["ObjectMultiplex::pageMux"]
F6["WindowPostMessageStream::pageStream._write()"]
end
subgraph inpage.js
G1["WindowPostMessageStream::metamaskStream"]
G2["ObjectMultiplex::mux"]
G3["Substream::connectionStream"]
G4["Duplex::_jsonRpcConnection.stream.write()"]
G5["processMessage() -> processResponse()"]
G6["DApp 拿到响应"]
end
%% 请求链路
A1 --> A2 --> A3 --> A4
A4 -->|pipeline| B1
B1 -->|push到父| B2
B2 -->|pipeline| B3
B3 -- window.postMessage --> C1
C1 -->|pipeline| C2
C2 -->|分发到| C3
C3 -->|pipeline| C4
C4 -->|push到父| C5
C5 -->|pipeline| C6
C6 -- port.postMessage --> D1
D1 --> D2 --> D3 --> D4 --> D5 --> D6
%% 响应链路
D6 --> E1 --> E2 --> E3 --> E4 --> E5
E5 -- port.postMessage --> F1
F1 --> F2 --> F3 --> F4 --> F5 --> F6
F6 -- window.postMessage --> G1
G1 --> G2 --> G3 --> G4 --> G5 --> G6
%% 说明
classDef stream fill:#f9f,stroke:#333,stroke-width:2px;
classDef logic fill:#bbf,stroke:#333,stroke-width:2px;
class A1,A2,A3,A4,G6 logic;
class B1,B2,B3,C1,C2,C3,C4,C5,C6,D1,D2,D3,D4,D5,D6,E1,E2,E3,E4,E5,F1,F2,F3,F4,F5,F6,G1,G2,G3,G4,G5 stream;
时序图表示
如果不习惯看流程图,可以参考时序图:
sequenceDiagram
participant DApp as DApp
participant Inpage as inpage.js
participant Content as contentscript.js
participant BG as background.js
%% 请求方向
DApp->>DApp: window.ethereum.request(...)
DApp->>DApp: _rpcEngine.handle()
DApp->>Inpage: _jsonRpcConnection.middleware(req)
Inpage->>Inpage: stream.push(req)
Inpage->>Inpage: Substream(METAMASK_EIP_1193_PROVIDER)._write()
Inpage->>Inpage: ObjectMultiplex::mux
Inpage->>Inpage: WindowPostMessageStream::metamaskStream._write()
Inpage->>Content: window.postMessage(req)
Content->>Content: WindowPostMessageStream::pageStream
Content->>Content: ObjectMultiplex::pageMux
Content->>Content: Substream::pageChannel
Content->>Content: Substream::extensionEip1193Channel
Content->>Content: ObjectMultiplex::extensionMux
Content->>Content: PortStream::extensionStream
Content->>BG: Port.postMessage(req)
BG->>BG: PortStream::portStream._onMessage()
BG->>BG: setupUntrustedCommunicationEip1193()
BG->>BG: ObjectMultiplex::mux
BG->>BG: Substream::outStream
BG->>BG: Duplex::providerStream.write()
BG->>BG: engine.handle()
%% 响应方向
BG->>BG: engine.handle() 生成响应
BG->>BG: providerStream.push(res)
BG->>BG: Substream::outStream.write()
BG->>BG: ObjectMultiplex::mux
BG->>BG: PortStream::portStream._write()
BG->>Content: Port.postMessage(res)
Content->>Content: PortStream::extensionStream._onMessage()
Content->>Content: ObjectMultiplex::extensionMux
Content->>Content: Substream::extensionEip1193Channel
Content->>Content: Substream::pageChannel
Content->>Content: ObjectMultiplex::pageMux
Content->>Content: WindowPostMessageStream::pageStream._write()
Content->>Inpage: window.postMessage(res)
Inpage->>Inpage: WindowPostMessageStream::metamaskStream
Inpage->>Inpage: ObjectMultiplex::mux
Inpage->>Inpage: Substream::connectionStream
Inpage->>Inpage: _jsonRpcConnection.stream.write()
Inpage->>Inpage: processMessage() -> processResponse()
Inpage->>DApp: DApp 拿到响应结果
总结
本章有一定的复杂性,需要对照源码去理解,如果还是不理解可以参考对应的视频讲解。
这其实还只是请求响应流的传递过程,实际中间还包括很多RPC中间件的处理过程,控制器间的通信过程,元数据的存取过程,前后端的状态实时同步等等,可见一个好的钱包框架是非常复杂的,不过不用担心,我们后面会一一详细讲解。
学习交流请添加vx: gh313061
下期预告:构建json RPC框架