自定义协议(Custom Protocol)是让桌面应用融入操作系统交互流程的强力手段。通过注册一个类似 myapp:// 的专属协议,你的用户就可以通过浏览器链接、命令行或第三方程序直接唤起你的应用,并向其传递参数。例如,VS Code 支持 vscode://file/路径 来打开指定文件,Zoom 通过 zoommtg:// 链接直接加入会议。
在 Electron 应用中实现这一能力,主要分为三步:注册协议、处理传参、安全加固。
12.4.1 注册协议
使用 app.setAsDefaultProtocolClient(protocol, [path, args]) 将你的应用设置为指定协议的默认处理程序。调用时机通常在 app.whenReady() 之后,或者应用首次启动时进行一次。
需要留意以下几点:
- 协议名只能包含小写字母、数字和横线,且不能以横线开头(例如
my-app、myapp2)。操作系统对协议名的规则可能略有差异,但 Electron 在内部做了兼容性屏蔽。 - 在 macOS 上,如果你希望一个链接点击后直接激活已运行的应用实例,而不启动新实例,可以配合
app.requestSingleInstanceLock()使用(12.3 节已有介绍)。 - 调用此方法会在操作系统注册表(Windows)、Info.plist(macOS)、.desktop 文件(Linux)中写入相应信息,因此通常只需在应用安装或第一次运行时执行一次。不过 Electron 本身并没有内置“仅首次注册”的机制,你需要自己用一种持久化标记来判断是否需要重复调用。
一个安全的做法是每次启动时都尝试注册,因为如果已经是默认协议处理程序,Electron 会直接返回 true,不会导致额外的副作用。此方法的签名还支持传入 path(例如 electron apps) 和 args(如 -- 参数),但在常规使用中,我们只需要指定协议字符串。
基础示例(main 进程):
const { app } = require('electron');
const protocol = 'myapp'; // 自定义协议名
app.whenReady().then(() => {
// 注册协议,若已注册则静默返回 true
const isDefault = app.setAsDefaultProtocolClient(protocol);
console.log(`是否已设为默认 ${protocol} 处理程序:`, isDefault);
});
12.4.2 接收和处理协议链接
不同操作系统在触发自定义协议时的行为有细微差异,尤其是在应用已运行与冷启动两种场景下,但 Electron 提供了统一的 API 来处理。
场景与事件:
- macOS:无论应用是否已经运行,点击协议链接时,系统都会通过
app模块的open-url事件将完整的 URL 传递给应用。你需要监听这个事件来获取传参。
- Windows 与 Linux:协议触发会尝试启动一个新的应用进程。为避免重复进程,常见的做法是配合
app.requestSingleInstanceLock(),然后在second-instance事件中捕获第二个实例收到的命令行参数(其中包含协议 URL),并将其转发给主窗口进行处理。
- 冷启动(应用未运行):在 macOS 上,
open-url事件同样会正常触发;在 Windows/Linux 上,应用启动时的命令行参数中会包含协议的 URL,你可以通过process.argv获取,但最好结合单实例锁来统一处理,避免碎片化。
综合来看,一个鲁棒的处理策略是:始终使用单实例锁,并在 second-instance 和 app 的 open-url (macOS) 事件中解析 URL,最后交给主窗口的渲染进程。
实现步骤(main 进程):
const { app, BrowserWindow, ipcMain } = require('electron');
const protocol = 'myapp';
let mainWindow = null;
// 单实例锁
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 在 Windows/Linux 下,命令行包含协议 URL
const url = commandLine.find(arg => arg.startsWith(`${protocol}://`));
if (url && mainWindow) {
// 通过 IPC 通知渲染进程处理链接
mainWindow.webContents.send('protocol-url', url);
// 当应用被其他实例唤起时,将窗口提升到前台
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
app.whenReady().then(() => {
mainWindow = createWindow();
// 注册协议
app.setAsDefaultProtocolClient(protocol);
// macOS 专用:监听 open-url 事件
app.on('open-url', (event, url) => {
event.preventDefault(); // 阻止默认行为(新开窗口)
if (mainWindow) {
mainWindow.webContents.send('protocol-url', url);
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
// 处理冷启动时的协议链接(Windows/Linux)
const protocolUrl = process.argv.find(arg => arg.startsWith(`${protocol}://`));
if (protocolUrl && mainWindow) {
mainWindow.webContents.once('did-finish-load', () => {
mainWindow.webContents.send('protocol-url', protocolUrl);
});
}
});
}
渲染进程接收参数:
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
onProtocolUrl: (callback) => ipcRenderer.on('protocol-url', (event, url) => callback(url)),
});
// renderer.js (或 Vue/React 组件内)
window.electronAPI.onProtocolUrl((url) => {
console.log('收到协议链接:', url);
// 解析 URL 提取需要的参数,例如 myapp://action/param
const parsed = new URL(url);
const action = parsed.hostname; // 对应 myapp://host/path 的结构
const path = parsed.pathname;
// 执行对应的业务逻辑,如打开特定页面、导入文件等
});
12.4.3 协议格式设计
为保证可读性和可扩展性,建议采用类似 HTTP URL 的结构:scheme://host/path?query=value。例如 myapp://open-file?path=/Users/me/doc.pdf 或 myapp://settings/account。这种格式可以直接使用 JavaScript 的 URL 构造函数解析,简洁且不容易出错。
同时也可以在应用安装包中通过配置文件(Windows 的 NSIS 安装脚本、macOS 的 plist 文件)让系统自动关联协议,但 Electron 的运行时注册更为方便,适合开发阶段与快速发布。
12.4.4 安全注意事项
- 验证来源:协议链接可以被任何网页或程序触发,在渲染进程处理之前,主进程最好对 URL 进行基本的白名单验证,只允许预期的 host 和路径模式。例如,仅允许
myapp://safe-action/*。 - 避免远程代码执行:永远不要直接
eval()或者类似动态执行的方式处理传入的参数。将参数看作不可信数据,只用于导航或显示,不拼接到脚本中。 - 单实例与焦点保护:确保
second-instance处理时能正确恢复窗口,避免用户点击链接后应用无反应或出现多个窗口互相覆盖。
12.4.5 实战验证
完成开发后,你可以在任意浏览器地址栏输入你注册的协议(例如 myapp://test/hello)并按回车,如果系统弹出协议启动确认(首次需要用户批准,macOS 和 Windows 均有安全提示),你的应用就会被唤起并接受到参数。这对于实现外部系统集成(如 OAuth 回调、通过网页打开本地工具)极为实用。
总结而言,自定义协议注册是 Electron 打通线上与线下体验的关键桥梁。只需十几行核心代码,就能让你的桌面应用像原生程序一样被外部链接激活,并携带上下文跳转到指定功能,极大提升了用户的工作流连贯性。