You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Electron中Windows平台自定义协议替代app.on('open-url')的实现方案

解决Electron跨平台自定义协议处理问题(macOS + Windows)

我太懂你现在的纠结了——macOS上自定义协议跑起来丝滑得很,到Windows直接罢工,旧的makeSingleInstance还被废弃了,官方推的requestSingleInstanceLock又好像拿不到URL。别慌,我做Electron应用时踩过一模一样的坑,现在把完整的跨平台解决方案分享给你。

核心思路

Electron在Windows和macOS上处理自定义协议的机制天生不一样,得针对性适配:

  • macOS:不管是首次启动还是后续用协议唤起,都能通过open-url事件拿到URL
  • Windows
    • 首次通过协议启动时,URL会作为命令行参数传入process.argv
    • 后续通过协议唤起时,因为requestSingleInstanceLock锁定了单实例,新实例会触发已有实例的second-instance事件,URL就藏在这个事件的参数里

我们要做的就是把这几种情况收拢到同一个逻辑里处理,实现跨平台一致性。

修改后的完整代码

1. 主进程index.js

const { app } = require('electron');
const createWindow = require('./utils/createWindow');
const handleOpenURL = require('./handleOpenURL');

const PROTOCOL = 'your-custom-protocol'; // 替换成你的自定义协议名

// 解析Windows首次启动时命令行里的协议URL
function parseWinLaunchArgs() {
  if (process.platform === 'win32') {
    // 从命令行参数里过滤出协议开头的内容
    const targetURL = process.argv.find(arg => arg.startsWith(`${PROTOCOL}://`));
    if (targetURL) {
      handleOpenURL(null, targetURL);
    }
  }
}

// 申请单实例锁
const gotSingleInstanceLock = app.requestSingleInstanceLock();

if (!gotSingleInstanceLock) {
  // 已有实例在运行,直接退出新实例
  app.quit();
} else {
  // Windows下后续唤起应用时触发该事件
  app.on('second-instance', (event, commandLine) => {
    if (process.platform === 'win32') {
      const targetURL = commandLine.find(arg => arg.startsWith(`${PROTOCOL}://`));
      if (targetURL) {
        handleOpenURL(event, targetURL);
      }
    }
    // 激活已有窗口,提升用户体验
    const { mainWindow } = require('./utils/createWindow');
    if (mainWindow) {
      if (mainWindow.isMinimized()) mainWindow.restore();
      mainWindow.focus();
    }
  });

  app.on('ready', () => {
    // 先处理Windows首次启动的命令行参数
    parseWinLaunchArgs();
    // 创建主窗口
    createWindow(`file://${__dirname}/views/welcome.html`);
    // 注册自定义协议
    app.setAsDefaultProtocolClient(PROTOCOL);
  });

  app.on('activate', () => {
    const { mainWindow } = require('./utils/createWindow');
    if (mainWindow === null) {
      createWindow(`file://${__dirname}/views/welcome.html`);
    }
  });

  // macOS专属的协议唤起事件
  app.on('open-url', handleOpenURL);
}

// 全平台窗口关闭逻辑(macOS保留dock图标)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

2. 统一URL处理函数handleOpenURL.js

const { mainWindow } = require('./utils/createWindow');
const createWindow = require('./utils/createWindow');

module.exports = (e, url) => {
  if (e) e.preventDefault();
  
  // 这里写你的URL解析逻辑,比如提取传递的数据
  console.log('收到自定义协议请求:', url);
  
  // 根据窗口状态做不同处理
  if (mainWindow && mainWindow.isVisible()) {
    mainWindow.focus();
    // 可以通过IPC把URL传给渲染进程处理,比如:
    // mainWindow.webContents.send('protocol-data-received', url);
  } else {
    // 把URL作为参数传给新窗口(如果需要的话)
    const encodedURL = encodeURIComponent(url);
    createWindow(`file://${__dirname}/views/welcome.html?protocol=${encodedURL}`);
  }
};

关键细节说明

  1. 单实例控制requestSingleInstanceLock保证同一时间只有一个应用实例,Windows下后续唤起会触发second-instance事件,我们从参数里提取URL即可。
  2. Windows首次启动适配:首次通过协议打开时,URL在process.argv里,必须在ready事件触发前解析,不然会错过。
  3. 逻辑统一:把macOS的open-url和Windows的两种场景都交给同一个handleOpenURL处理,避免重复代码。
  4. 用户体验优化:不管哪种方式唤起应用,都要激活已有窗口(如果存在),不能让用户找不到窗口。

这样调整后,你的Electron应用在macOS和Windows上就能一致处理自定义协议了,不管是首次启动还是后续唤起,都能拿到传递的URL数据。

内容的提问来源于stack exchange,提问作者Apal Shah

火山引擎 最新活动