前言
前面一节介绍了,插件钱包的代码的基本结构,路由配置以及状态管理。本节将展示插件的消息通信
通信的 API
chrome.runtime.onConnect.addListenerAPI
介绍
当使用扩展进程或内容脚本建立连接时触发。也就是说我内容脚本执行了这个方法browser.runtime.connect({name:"port-from-cs"}); 当前 API 便可以监听到连接事件。
同时也有如下的方法:
browser.runtime.onConnect.addListener(listener) // 向该事件添加监听器。
browser.runtime.onConnect.removeListener(listener) // 停止监听此事件。
browser.runtime.onConnect.hasListener(listener) // 检查 listener 是否已注册此事件。如果正在监听,则返回 true ,否则返回 false 。
browser.runtime.onConnect.addListener(listener) 这个方法监听的 listener,拿到的就是下面的chrome.runtime.Port API
chrome.runtime.Port API
介绍
官方解释: 该对象代表两个特定上下文之间连接的一端,可用于交换消息。一侧使用 connect() API 发起连接。这将返回一个 Port 对象。另一端使用 onConnect 侦听器侦听连接尝试。这是传递一个相应的 Port 对象。一旦双方都有 Port 对象,就可以使用 Port.postMessage() 和 Port.onMessage 交换消息。当它们完成时,任一端都可以使用 Port.disconnect() 断开连接,这将在另一端生成 Port.onDisconnect 事件,使另一端能够执行所需的任何清理工作。
翻译成中文~~(人话) ~~就是,我们通过 connect 来进行连接通信,使用同样的端口名称,我们就可以用 Port.postMessage 和 Port.onMessage 来进行通信。如果 2 端都使用 postMessage 和 onMessage 就可以实现,互相通信
-
name: string,端口的名称
-
disconnect:function 断开连接
-
error: object 端口错误
-
onDisconnect: 监听断开连接
-
onMessage: 监听信息
-
postMessage: 发送信息
实现一个简单通信例子
内容脚本创建一个连接 Port,发送一条信息。
同时监听 background 的 Port 时间。
// content-script.js
let myPort = browser.runtime.connect({name:"port-from-cs"});
myPort.postMessage({greeting: "hello from content script"});
myPort.onMessage.addListener((m) => {
console.log("In content script, received message from background script: ");
console.log(m.greeting);
});
document.body.addEventListener("click", () => {
myPort.postMessage({greeting: "they clicked the page!"});
});
background 使用browser.runtime.onConnect.addListenerapi,获取所有的 port 连接
// background-script.js
let portFromCS;
function connected(p) {
portFromCS = p; // 这个 p 就是一个 Port
portFromCS.postMessage({greeting: "hi there content script!"});
portFromCS.onMessage.addListener((m) => {
console.log("In background script, received message from content script")
console.log(m.greeting);
});
}
browser.runtime.onConnect.addListener(connected); //
browser.browserAction.onClicked.addListener(() => {
portFromCS.postMessage({greeting: "they clicked the button!"});
});
如果有多个的话
// background-script.js
let ports = []
function connected(p) {
ports[p.sender.tab.id] = p
// …
}
browser.runtime.onConnect.addListener(connected)
browser.browserAction.onClicked.addListener(() => {
ports.forEach((p) => {
p.postMessage({greeting: "they clicked the button!"})
})
});
以上就是插件通信的方法介绍,接下来我们需要将这个通信实现在插件钱包里。
插件钱包通信
在 background 创建一个 index.js。
const init = () => {
chrome.runtime.onConnect.addListener((port) => {
console.log(port);
})
}
init();//执行这个方法
上文介绍到多个连接的处理,这里我们需要进一步优化它。可以肯定的是,我们会有多个连接。如果后期的 Dapp 连接的通信等。因此我们需要实现一个方法,用来管理这些个 Port 连接。
创建一个 PortMessage 来进行管理 Port,使用一个 class类来管理
export class PortMessage {
port: chrome.runtime.Port = null
private requestIdPool = [...Array(1000).keys()]
protected EVENT_PREFIX = "ETH_WALLET_"
protected listenCallback: any
private waitingMap = new Map<
number,
{
data: any
resolve: (arg: any) => any
reject: (arg: any) => any
}
>() // 这里需要使用一个 map,同时返回一个 resolve 和 reject,通信使用 Promise 来管理,类似与请求 API 一样。
listen = (listenCallback: any) => {
if (!this.port) return
this.listenCallback = listenCallback
this.port.onMessage.addListener(({ _type_, data }) => {
if (_type_ === `${this.EVENT_PREFIX}request`) {
this.onRequest(data)
}
})
return this
}
}
修改 background/index.ts 文件
import walletMethods from "./walletService"
/* background 初始化 */
const init = () => {
chrome.runtime.onConnect.addListener((port) => {
if (port.name === "popup") { // 这里使用的端口 name 为 popup 用来标识是 popup 创建的连接
const pm = new PortMessage(port) // 使用PortMessage来优化 port
pm.listen((data) => { //监听事件同时,约定 method,用于调用 background 中的方法
if (data.method) {
return walletMethods[data.method].apply(null, data.params) // 执行方法
}
return null
})
port.onDisconnect.addListener(() => {
// 断开连接的处理。
})
return
}
})
}
init()
walletMethods 实现
export class WalletService {
constructor() {
}
helloWorld(){
console.log('hello world');
}
}
export default new WalletService()
popup 创建连接,创建连接
import type { WalletService } from "~background/walletService"
import { getUIType } from "~utils/getUIType";
import { PortMessage } from "~utils/message"
const portMessageChannel = new PortMessage()
portMessageChannel.connect(getUIType());
export const wallet = new Proxy(
{},
{
get(_, k) {
return async (...params: any) => {
try {
// 获取钱包服务
const res = await portMessageChannel.request({
type: "controller",
method: k,
params
})
return res
} catch (error) {
Promise.reject(error)
return {};
}
}
}
}
) as WalletService
在 React 组件中使用
import { useRequest } from "ahooks"
export const Demo = () => {
useRequest(
async () => {
const info = await wallet.helloWorld()
return info
},
)
return <div>hello world</div>
}
以上就实现了插件通信相关的代码,后续将进行钱包相关的开发,比如创建钱包、导入钱包、钱包账户系统等。