Web3-Wallet 谷歌插件钱包开发(二)

931 阅读3分钟

前言

前面一节介绍了,插件钱包的代码的基本结构,路由配置以及状态管理。本节将展示插件的消息通信

通信的 API

chrome.runtime.onConnect.addListenerAPI

文档介绍:runebook.dev/zh/docs/web…

介绍

当使用扩展进程或内容脚本建立连接时触发。也就是说我内容脚本执行了这个方法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

文档介绍:runebook.dev/zh/docs/web…

介绍

官方解释: 该对象代表两个特定上下文之间连接的一端,可用于交换消息。一侧使用 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>
 }

以上就实现了插件通信相关的代码,后续将进行钱包相关的开发,比如创建钱包、导入钱包、钱包账户系统等。