跨端通信终结者|桌面端已加入全家桶🔝

一篇姗姗姗姗姗姗姗姗来迟的文章。

前言

前一阵子笔者在忙于桌面端 Electron 相关的开发,工欲善其事,必先利其器。

对于基于浏览器的桌面端框架(Electron / Tauri / ...)来说,如何构建稳定快捷的跨端通信机制,是其中的开发重点。

那前面我们在跨端通信终结者|看我是如何保证多端消息一致性的 🔥一文中说到,使用codegen工具链解决跨端通信一致性的问题。桌面端只需要当作一个实现终端,加入到这套机制里即可。

分析

由于笔者公司是 Electron 框架开发的,所以这里只对 Electron 的实现举例。

Electron 进程间通信官方讲了很多种进程间通信的方式,但我们只需要最推荐的方式,且只需要异步的方法。

为什么不建议使用同步的方法?同步方法会堵塞主进程,虽然比如变量传值等用同步方法十分方便,但无法保证这个过程不会劣化。

那对于codegen来讲,需要去生成3部分的代码:

预加载脚本 preload.js

使用ipcRenderer向渲染进程有限暴露能力。

这个参照官方文档的定义即可。

我们生成的如下,分成多个模块:

web 调用端

调用端是一致的,无论是在 App 还是桌面端,使用方式一致,API 一致,自动适配各端。

但内部处理不同,生成的桌面端独有的调用方式,关键调用代码类似如下:

window.$gnb.$settings$checkUpdate()在桌面端上会去调用上文的preload.js预加载与桌面端主进程通信。

Electron 支撑端

同样的,我们在桌面端也是生成一套支撑库来用于协同通信一致性。

在实现上,只需要实现这些服务即可:

实现细节

本文着重的细节介绍在 Electron 支撑端生成的实现上,这还是有一点技巧的。

构造

这和在 App 上实现不大一样,App 上是收到通信后再根据name 进行转发,而在 Electron 上,通信需要先注册后使用,所以需要在constructor就把通信通道建立起来。

当然,你也可以只使用一个通道,然后内部根据name进行转发,这样与 App 一致,但多通道比单通道性能会更好。

生成的Service即如上图,ipcMain.handle()与预加载preload.js一一对应。

observer是实现的接口实例,需要外部实现,当然不实现也不会报错。

resultWithPromise()是为了结果一致性。

这里还要说的一点就是我们在桌面端把event也要传出去,这一点跟 App 的处理不一样,App 一般是当前容器发出的调用请求,但对桌面端来讲,天然是多容器同时存在的,我们在某些情况下就必须要区分是哪个容器发起的调用请求。

扩展一下,App 上可以区分出是哪个容器发的请求吗?

当然可以,需要对我们的js-bridge进行改造,在初始化容器时向 Web 中注入一个唯一容器 id,类似window.__webview_id=xxx,在调用链路上携带这个 id 即可判断。

当然,现在没有这个使用场景,没必要做这一层,后续需要做这一层也不会对当前实现产生影响。

event是一个IpcMainEvent,可以从中拿到当前是容器、窗口的句柄,比如获取窗口BrowserWindow.fromWebContents(event.sender);

事件通知

正向调用有了,那桌面端如何主动发起请求通知容器?还是与 App 的实现一样,构造一个事件通知机制。笔者发现上文也没有把事件通知如何实现的讲解出来... 这里一并补上。

定义

首先,我们还是在 YAML 文件中定义一层事件出来。

事件监听广播机制,在调用方会提供 on/off 方法,提供监听/取消监听的能力。

在宿主方发送全局广播。

name(必填) 事件名称

note(必填) 事件说明

callback(可选) API 返回出参

参数不建议超过5个,超过的话可以定义成模型。

  • name 参数名称

  • type 参数类型,按照 schema 提示定义,拒绝不符合规范的 type

  • note 参数说明

比如:

yaml 复制代码
events: 
    - name: loginDidSuccess 
      note: 登录成功 
    - name: userInfoDidChange 
      note: 用户信息发生变化

桌面端发出

消息通知应该是1对多的场景,天然在桌面端上,比如用户登出,登录成功,付费等等,需要改变多个容器或者窗口的显示状态。

那我们如何发出这一事件呢?按照官网的做法还是用 ipc 通信,可以,但不够好,不够好的原因是 ipc 通信是一个一对一通信,也不够通用,到 App 上还是要做一套。

通用的做法是什么呢?

我们可以直接使用执行 JS 方法的方式,进行容器通信:

可以看到,我们是执行dispatchEvent(new CustomEvent(...))的方式发送一个事件给各个容器即可。

那还需要一个管理总线,让每个容器注册上来进行发送,这很简单:

对于codegen来说,那抹平这一层,让调用范式化即可,比如:

前端接收

前端接收是通用的,App 可以执行到,桌面端也可以执行到,具体就是实现了一个event-manager来进行转发作业。

实现细节不贴了,但有监听就要有取消监听,使用方自己决定。

codegen生成的代码示例如下:

安全防护

安全总是我们要考虑的一环,除了在桌面端容器层面完全符合官方文档外,我们在跨端通信这一层做了白名单防护,防止加载到恶意页面后还能通过桥接操作原生信息。

这也很简单,在每个调用处增加了强制校验即可:

event获取到当前加载的 URL 是否符合校验规则。

特性能力

GNB 是通用的端侧通信约定层,但在桌面端上,现在有挺多不通用的通信约定,只在桌面端才会存在使用,不能很好跟通用约定所结合。

当然可以通过增加platform属性定义一些只在桌面端生效的 GNB API,如下红框:

但有一些 API 的场景十分特别,只在桌面端框架层使用,而不是通用页面。比如控制BrowserView间的视图关系、回到主窗口页面等,这些不适合来定义成通用端侧能力,而应该考虑其他的实现方式。

那我们在具体实现上,增加一个特殊的特性通道,专门用于构造这一部分特有的能力:

通过动态 type 解析的方式,完成框架渲染层与主进程的通信。

在逻辑开发时,同时提供给渲染层调用的代码方法,完成自身闭环。

具体使用例子也可以看:桌面端|Electron BrowserView 多容器管理(内附 Demo)

里面还有如何一对一通信的,细节不表了。

总结

从整体跨端通信的角度,我们建设上已经是完善了,除了 Flutter for web 的如何统一通信没讲(也不打算讲了),其他平台都有介绍。

后续

但这就是终点吗?并不是,我们构建统一跨端通信的目的是整合跨端项目,跨端通信,说具体点,是一套通信协议,是可以直接用,但直接使用底层能力并不是一个好的解决方式,我们希望的是从业务角度出发,解决前端页面在各个平台的能力兼容性,而不是多写一层 if else 来增加前端同学的理解成本。

后续文章规划:

《基于自动生成的跨端统一事件通信方案》

《基于自动生成的跨端统一存储方案》


感谢阅读,如果对你有用请点个赞 ❤️

相关推荐
明辉光焱4 小时前
[Electron]总结:如何创建Electron+Element Plus的项目
前端·javascript·electron
ZJ_.1 天前
Electron 沙盒模式与预加载脚本:保障桌面应用安全的关键机制
开发语言·前端·javascript·vue.js·安全·electron·node.js
fanxbl9571 天前
Electron 项目实现下载文件监听
javascript·electron·状态模式
关键帧Keyframe2 天前
音视频面试题集锦第 15 期 | 编辑 SDK 架构 | 直播回声 | 播放器架构
音视频开发·视频编码·客户端
怕冷的火焰(~杰)2 天前
创建vue+electron项目流程
vue.js·electron
new出一个对象3 天前
vue3+vite搭建脚手架项目本地运行electron桌面应用
前端·javascript·electron
明辉光焱3 天前
使用yarn,如何编译打包electron?
前端·javascript·electron·node.js
云只上3 天前
Electron + Vue3 开发桌面应用+附源码
前端·javascript·electron
fanxbl9573 天前
Electron 项目中杀掉进程的不同方式
前端·javascript·electron
Liigo4 天前
初次体验Tauri和Sycamore(1)
rust·electron·gui·tauri·wasm·sycamore