当Web遇上桌面应用:如何实现在网页中调用Electron?

1,542 阅读4分钟

摘要:本文将介绍如何在Web应用中通过定义伪协议唤起Electron桌面端应用,并详细阐述不同平台(Mac和Ubuntu)下的处理方式和思路。我们还将讨论如何在Electron应用安装时将自定义协议注册到计算机的注册表,以及处理不同操作系统间的差异、参数处理、冷启动和热启动逻辑的差异。文章主要基于electron-builder工具。

1. 需求背景

随着Web应用的发展,有时候我们需要在Web界面中唤起本地的Electron桌面端应用。这种需求在很多场景下都很常见,例如从浏览器中直接打开本地的图像编辑器、音频播放器或其他桌面应用。通过唤起Electron应用,我们可以融合Web和本地功能,提供更丰富的用户体验。

2. 伪协议的定义

为了在Web中唤起Electron应用,我们可以定义一个伪协议(例如:myapp://),并将其与Electron应用关联。当用户在Web界面点击相关链接或按钮时,浏览器会尝试唤起与该协议关联的应用。

3. 不同平台的处理方式和思路

3.1 Mac平台

3.1.1 注册协议

在 electron-builder 配置中添加如下协议内容:

"protocols": [
  {
    "name": "myapp",
    "schemes": [
      "myapp"
    ]
  }
]

在 electron 应用的入口文件中添加如下代码:

const { app, protocol } = require("electron");


const PROTOCOL = 'myapp';
if(!app.isDefaultProtocolClient(PROTOCOL)) {
  app.setAsDefaultProtocolClient(PROTOCOL);
}

在Electron应用的main进程中,监听open-url事件

const { app } = require('electron');

function handleUrl(urlStr) {
  LOG?.info(`handleUrl fn get params: ${urlStr}`);

  try {
    
    const {search} = new URL(urlStr);
    const searchParams = new URLSearchParams(search);
    const searchEntries = Object.fromEntries(searchParams.entries());
    
    LOG?.info(`handleUrl get query: ${searchEntries}`);

    // todo...
  } catch (error) {
    LOG?.error(error);
  }
}

app.on('open-url', (event, url) => {
  // 解析并处理传递的URL参数
  // 执行相应的操作
  handleUrl?.(url);
});

在Web应用中,使用包含自定义协议的链接来唤起Electron应用, 或者直接在chrome浏览器里面输入:myapp://do-something?a=xxx&b=xxx,并回车~。

<a href="myapp://do-something?a=xxx&b=xxx">打开应用</a>

3.2 linux平台

注册阶段和mac平台一致, 此处不再赘述~

在Electron应用的main进程中,监听second-instance事件来获取热启动情况下的参数传递。

const { app } = require('electron');

/*
...
*/

function handleArgv(argv) {
  LOG?.info(`get params: ${argv}`);
  const offset = app.isPackaged ? 1 : 2;
  const url = argv.find((arg, i) => i >= offset && arg.startsWith(PROTOCOL));

  if (url) handleUrl(url)
}

app.on("second-instance", (event, argv) => {
  if (process.platform === 'linux') {
    handleArgv(argv)
  }

  try {
    const allWindows = BrowserWindow.getAllWindows();
    
    const mainWindow = allWindows?.[0];

    LOG?.info(`get main window: ${mainWindow}`);

    if (mainWindow) {
      mainWindow?.show();
      mainWindow?.focus();
    }
  } catch (error) {
    LOG.error(error);
  }
});

在Web应用中,使用包含自定义协议的链接来唤起Electron应用。

<a href="myapp://do-something">打开应用</a>

⚠️注意:linux 上chrome 浏览器无法在地址栏中唤起,研究了很久都没成功,Firfox 可以,有知道的同学欢迎留言讨论~

做完以上操作之后,你会发现在 linux 平台,第一次冷启动时可以成功唤起应用,但是无法获取到参数,这是为什么呢?

这是因为在linux平台,冷热启动都不会走 open-url 事件,也就无法通过改事件监听到对应事件并获取参数。第一次唤起应用(冷启动)的时候,首先执行的是主进程,通过app的whenReady事件获取到参数后,由于此时渲染进程还没有准备好,也就是说在whenReady 中获取到参数并发送给渲染进程的时候,渲染进程并没有成功接收,所以我们需要先把获取到的参数存储起来,并监听渲染进程的ready事件,在渲染进程 ready 之后,再发送给渲染进程。👇:

4. 参数处理、冷启动和热启动逻辑的差异

在主进程启动的时候添加如下代码:

// 提前存起来
handleArgv(process.argv);

app.whenReady().then(() => {
  const baseWindow = createBaseWindow();

// 处理冷启动拿不到参数的情况的情况
  baseWindow?._window?.on("ready-to-show", () => {
    baseWindow?._window?.show();

    const data = `提前存起来的值`
    // 向渲染进程发送参数
    baseWindow.webContents.send(data)
})
});

由于业务暂时没有window的场景,没有实际测试效果,暂不做分析~

总结

通过定义伪协议并结合不同平台的处理方式,我们可以实现在Mac和Ubuntu系统上的应用唤起。通过适当的协议注册和安装处理,我们能够确保应用在安装时将自定义协议正确注册到计算机的注册表。同时,处理不同电脑系统的差异、参数和启动逻辑的差异,能够更好地满足用户的需求,提升应用的用户体验。