深度链接 (Deep Links)
概览
本指南将会指导你配置 Electron 应用为 特定协议 的默认处理器。
通过此教程,您会掌握如何设置您的应用以拦截并处理任意特定协议的URL的点击事件。 在本指南中,我们假定这个协议名为“electron-fiddle://
”。
示例
主进程(main.js)
首先,我们需要从electron
导入所需的模块。 这些模块有助于控制应用的生命周期,或创建原生的浏览器窗口。
const { app, BrowserWindow, shell } = require('electron')
const path = require('node:path')
其次,我们将应用注册为“electron-fiddle://
”协议的处理器。
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('electron-fiddle', process.execPath, [path.resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('electron-fiddle')
}
现在我们定义负责创建浏览器窗口的函数,并加载应用的 index.html
文件。
let mainWindow
const createWindow = () => {
// 创建浏览器窗口
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
紧接着,我们将创建 BrowserWindow
并在应用中定义如何处理此外部协议被点击的事件。
这段代码在 Windows 和 Linux 上会与 MacOS 有所不同。 这是因为这两个平台会触发 second-instance
事件,而不是 open-url
事件,而且 Windows 需要额外的代码才能在同一个 Electron 实例中打开协议链接的内容。 请点击 此处 了解更多
Windows 和 Linux 的代码示例:
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 用户正在尝试运行第二个实例,我们需要让焦点指向我们的窗口
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
// 命令行是一个字符串数组,其中最后一个元素是深度链接的URL。
dialog.showErrorBox('Welcome Back', `You arrived from: ${commandLine.pop()}`)
})
// 创建主窗口,加载应用程序的其他部分,等等...
app.whenReady().then(() => {
createWindow()
})
}
MacOS 代码示例:
// Electron 在完成初始化,并准备创建浏览器窗口时,
// 会调用这个方法。
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(() => {
createWindow()
})
// 处理协议 在本例中,我们选择显示一个错误提示对话框。
app.on('open-url', (event, url) => {
dialog.showErrorBox('欢迎回来', `导向自: ${url}`)
})
最后,我们还需要处理应用的关闭事件。
// 在除 MacOS 的其他平台上,当所有窗口关闭后,退出当前应用。 在 MacOS 上,
// 应用及其菜单栏通常会保持活跃状态,
// 直到用户明确按下 Cmd + Q 退出应用
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
重要提醒:
打包
在 macOS 和 Linux 上,此功能仅在应用打包后才有效。 在命令行启动的开发版中无效。 当您打包应用程序时,你需要确保应用程序的 macOS Info.plist
和 Linux .desktop
文件已更新以包含新的协议处理程序。 一些绑定和分发应用程序的 Electron 工具会自动为你处理
Electron Forge
如果您使用 Electron Forge,请调整 macOS 支持的 packagerConfig
,以及适当调整 Linux 制造商的 Linux 支持配置,在 Forge 配置 (请注意以下示例仅显示添加配置时所需更改的最低值) :
{
"config": {
"forge": {
"packagerConfig": {
"protocols": [
{
"name": "Electron Fiddle",
"schemes": ["electron-fiddle"]
}
]
},
"makers": [
{
"name": "@electron-forge/maker-deb",
"config": {
"mimeType": ["x-scheme-handler/electron-fiddle"]
}
}
]
}
}
}
Electron Packager
对于 macOS 支持:
如果您正在使用 Electron Packager 的 API,添加对协议处理程序的支持类似于 Electron Forge 的处理方式, 其他 protocols
是传递到 packager
函数的 Packager 选项的一部分。
const packager = require('electron-packager')
packager({
// ...other options...
protocols: [
{
name: 'Electron Fiddle',
schemes: ['electron-fiddle']
}
]
}).then(paths => console.log(`SUCCESS: Created ${paths.join(', ')}`))
.catch(err => console.error(`ERROR: ${err.message}`))
如果您使用 Electron Packager 的 CLI,请使用 --protocol
和 --protocol-name
标志。 例如:
npx electron-packager . --protocol=electron-fiddle --protocol-name="Electron Fiddle"
结论
启动 Electron 应用后,在浏览器内键入包含该自定义协议的 URL,如 "electron-fiddle://open"
,观察应用是否正确响应并显示一个错误提示对话框。
docs/fiddles/system/protocol-handler/launch-app-from-URL-in-another-app (27.0.1)Open in Fiddle
- main.js
- preload.js
- index.html
- renderer.js
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron')
const path = require('node:path')
let mainWindow
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('electron-fiddle', process.execPath, [path.resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('electron-fiddle')
}
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
dialog.showErrorBox('Welcome Back', `You arrived from: ${commandLine.pop().slice(0, -1)}`)
})
// Create mainWindow, load the rest of the app, etc...
app.whenReady().then(() => {
createWindow()
})
app.on('open-url', (event, url) => {
dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})
}
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// Handle window controls via IPC
ipcMain.on('shell:open', () => {
const pageDirectory = __dirname.replace('app.asar', 'app.asar.unpacked')
const pagePath = path.join('file://', pageDirectory, 'index.html')
shell.openExternal(pagePath)
})
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require('electron')
// Set up context bridge between the renderer process and the main process
contextBridge.exposeInMainWorld(
'shell',
{
open: () => ipcRenderer.send('shell:open')
}
)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>app.setAsDefaultProtocol Demo</title>
</head>
<body>
<h1>App Default Protocol Demo</h1>
<p>The protocol API allows us to register a custom protocol and intercept existing protocol requests.</p>
<p>These methods allow you to set and unset the protocols your app should be the default app for. Similar to when a
browser asks to be your default for viewing web pages.</p>
<p>Open the <a href="https://www.electronjs.org/docs/latest/api/protocol">full protocol API documentation</a> in your
browser.</p>
-----
<h3>Demo</h3>
<p>
First: Launch current page in browser
<button id="open-in-browser" class="js-container-target demo-toggle-button">
Click to Launch Browser
</button>
</p>
<p>
Then: Launch the app from a web link!
<a href="electron-fiddle://open">Click here to launch the app</a>
</p>
----
<p>You can set your app as the default app to open for a specific protocol. For instance, in this demo we set this app
as the default for <code>electron-fiddle://</code>. The demo button above will launch a page in your default
browser with a link. Click that link and it will re-launch this app.</p>
<h3>Packaging</h3>
<p>This feature will only work on macOS when your app is packaged. It will not work when you're launching it in
development from the command-line. When you package your app you'll need to make sure the macOS <code>plist</code>
for the app is updated to include the new protocol handler. If you're using <code>electron-packager</code> then you
can add the flag <code>--extend-info</code> with a path to the <code>plist</code> you've created. The one for this
app is below:</p>
<p>
<h5>macOS plist</h5>
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>electron-api-demos</string>
</array>
<key>CFBundleURLName</key>
<string>Electron API Demos Protocol</string>
</dict>
</array>
<key>ElectronTeamID</key>
<string>VEKTX9H2N7</string>
</dict>
</plist>
</code>
</pre>
<p>
<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>
</body>
</html>
// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All APIs exposed by the context bridge are available here.
// Binds the buttons to the context bridge API.
document.getElementById('open-in-browser').addEventListener('click', () => {
window.shell.open()
})