背景
之前项目多次遇到隔离环境需要通信,比如window.top
和iframe
。Chrome Extension
环境之间通信。主线程与web worker
通信等。原生的通信方式会遇到以下问题
原生通信方式不支持
Promise
,比如window.postMessage
;Electron.WebContents.send
;
无法直接通信,需要转发
Chrome Extension
中devtool
与前台页面通信需要content script
转发
每次遇到这个问题都会封装一个支持Promise
的工具方法,遇到的次数多了,封装一个统一api的bridge库。
使用
整个使用过程类似调用后端接口
on
方法监听API
bridge.on(path: string, async function(params: any) {
const response = { ret: 0, data: 'Hello' }
return response
});
说明:
path: 接口路径
- 为区分多个环境,path需要以环境的key
plat
开头 - 与事件监听有所不同,一个
path
只能对应一个方法
- 为区分多个环境,path需要以环境的key
- params: 接口参数
- response: 接口返回值
使用 request
方法请求接口
const response = await bridge.request(path, { username: 'yh' });
说明:
- path: 需要和
on
的path保持一致
示例:chrome-extension
通信
Chrome Extension 环境
- web: 前台页面
- content script
- popup
- devtool
- service worker:之前的
background script
devtool
或者其他环境与web
通信需要经过content-script
中转,如图
Chrome Extension 使用 bridge
const Plat = {
web: 'testDevtoolsWeb'
};
const api = {
getPinia: `${Plat.web}/getPiniaInfo`
}
// content script
// must be required, if you want to request `web`
import { ContentBridge } from '@yuhufe/browser-bridge'
export const contentBridge = new ContentBridge({ platWeb: Plat.web })
// web.js
import { WebBridge } from '@yuhufe/browser-bridge'
export const webBridge = new WebBridge({ plat: Plat.web });
webBridge.on(api.getPinia, async function({ key }) {
console.log(key); // 'board'
return Promise.resolve({ a: 1 });
});
// devtool.js
import { DevtoolBridge } from '@yuhufe/browser-bridge'
export const devtoolBridge = new DevtoolBridge() // must be required, if you want to request `web`
const piniaInfo = await devtoolBridge.request(api.getPinia, { key: 'board' });
console.log(piniaInfo); // { a: 1 }
Chrome Extension Bridges 介绍
WebBridge
一个页面可能会定义多个
WebBridge
- 多个
extension
extension
与iframe
共存
- 多个
- 为了区分多个
WebBridge
,需要自定义plat
字段
ContentBridge
- 用于
proxy
各方通信 - 和
WebBridge
配套,需要定义platWeb
字段
DevtoolBridge
- 不同Chrome Extension的
devtool
是互相隔离的,不需要指定plat
popup
,service-worker
同理
示例:iframe
通信
top页面:宿主页面
- 只有1个
- 使用
iframeEl.contentWindow.postMessage
通信
iframe页面:被嵌入的页面
- 可能有多个
- 需要指定
frameKey
- 使用
window.top.postMessage
通信
import { Plat } from '@yuhufe/browser-bridge'
// because we have only 1 top and multi iframe;
const frameKey = 'iframeTest' // multi iframe, so every iframe has a key
const topKey = Plat.iframeTop // 1 top so key is only one
const api = {
getFrameInfo: `${frameKey}/getInfo`,
getTopInfo: `${topKey}/getTopInfo`
}
// top.js
import { IFrameTopBridge, Plat } from '@yuhufe/browser-bridge'
const iframeTestTop = new IFrameTop({
frameKey,
frameEl: document.querySelector('iframe')
})
iframeTestTop.on(api.getTopInfo, async function({ topname }) {
console.log(topname);
return { top: 1 };
});
const userInfo = await iframeTestTop.request(api.getFrameInfo, { username: '' });
// iframe.js
import { IFrameBridge } from '@yuhufe/browser-bridge'
const iframeTest = new IFrameBridge({ frameKey })
iframeTest.on(api.getFrameInfo, async function({ username }) {
return { user: '', age: 0 }
});
const topInfo = await iframeTest.request(api.getTopInfo, { topname: '' });
示例:自定义环境通信
我这里只把我需求遇到的场景进行了bridge
封装,也可以使用BaseBridge
进行自定义封装。
如下是一个electron
中多个窗口通信场景
electron
在global
上挂了2个窗口mainWin
和backWin
- 类型为
Electron.BrowserWindow
- 类型为
监听事件:在各自的代码中调用
ipcRenderer.on
- ipcRenderer来自类型
Electron.IpcRenderer
- ipcRenderer来自类型
- 触发事件:
backWin
中调用global.mainWin.webContents.send
基于以上通信方式,构造一个支持Promise
的bridge
代码如下
import { BaseBridge, MsgDef } from '@yuhufe/browser-bridge'
const ipcRenderer = remote.ipcRenderer
export const WinPlat = {
backWin: 'backWin', // background页面
mainWin: 'mainWin', // 主页面
}
export const WinAPI = {
backToggle: `${WinPlat.backWin}/toggle`,
cptDynamicUpdateFileInfo: `${WinPlat.backWin}/cpt-dynamicUpdate-fileInfo`,
ipclog: `${WinPlat.mainWin}/ipclog`,
}
export class ElectronBridge extends BaseBridge {
constructor({ plat }: any = {}) {
super({ plat })
this.init()
}
init() {
ipcRenderer?.on('kxBridgeMessage', (evt, message) => {
if (!this.isBridgeMessage(message)) return
const { target, type } = message
// 只处理发给我的页面消息
if (target !== this.plat) return
if (type === MsgDef.REQUEST) {
this.handleRequest({
request: message,
sendResponse: response => {
this.sendMessage(response)
},
})
} else {
this.handleResponse({ response: message })
}
})
}
async sendMessage(message) {
const { target } = message
return global[target]?.webContents.send('kxBridgeMessage', message)
}
}
说明:
- 在初始化时开始监听事件
使用
handleRequest
处理请求消息- 提供
sendResponse
具体实现,本例中直接转发
- 提供
- 使用
handleResponse
处理response
消息 - 实现
sendMessage
方法,内容为向其他bridge
发送消息
ElectronBridge
使用代码如下
// backWin
const backBridge = new ElectronBridge({ plat: WinPlat.backWin })
backBridge.on(WinAPI.cptDynamicUpdateFileInfo, async data => {
// 业务逻辑
return {}
})
// mainWin
const mainBridge = new ElectronBridge({ plat: WinPlat.mainWin })
const data = await mainBridge.request(WinAPI.cptDynamicUpdateFileInfo, {})
项目地址
https://github.com/defghy/web-toolkits/tree/main/packages/wtool-chrome-bridge
完
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。