Electron 20从浏览器打开应用时无法获取Deep Link负载
Electron macOS 深度链接启动问题:完全关闭应用后无法触发对应行为
环境信息
- Electron版本:20.1.3
- electron-builder版本:23.1.0
问题描述
应用在macOS上正常运行时,深度链接功能完全正常;但完全关闭应用后通过深度链接启动应用,虽然应用能成功启动,但不会触发深度链接对应的业务行为。参考Electron官方文档的代码实现后,仍未找到解决办法。
当前实现代码
主进程代码
"use strict"; const { app, BrowserWindow, session, ipcMain, Menu, nativeTheme, } = require("electron"); const { homedir } = require("os"); const { resolve, join } = require("path"); const windowStateKeeper = require("electron-window-state"); const { menuTemplate } = require("./templates"); const isMac = process.platform === "darwin"; let startUpLink = ""; if (process.defaultApp) { if (process.argv.length >= 2) { app.setAsDefaultProtocolClient("my-application", process.execPath, [ resolve(process.argv[1]) ]); } } else { app.setAsDefaultProtocolClient("my-application"); } const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); } else { app.on("second-instance", () => { const mainWindow = BrowserWindow.getAllWindows()[0]; if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } }); app.whenReady().then(async () => { await createWindow(); }); app.on("open-url", async (event, url) => { startUpLink = url; event.preventDefault(); let mainWindow = BrowserWindow.getAllWindows()[0]; if (mainWindow) { mainWindow.focus(); } else { await createWindow(); mainWindow = BrowserWindow.getAllWindows()[0]; } mainWindow.webContents.send("handle-deep-link", url); }); } const createWindow = async () => { nativeTheme.themeSource = "light"; const mainWindowStateKeeper = windowStateKeeper({ defaultWidth: 1600, defaultHeight: 900, fullScreen: true }); const mainWindow = new BrowserWindow({ width: mainWindowStateKeeper.width, height: mainWindowStateKeeper.height, x: mainWindowStateKeeper.x, y: mainWindowStateKeeper.y, icon: resolve(__dirname, "./assets/icon.png"), webPreferences: { contextIsolation: false, nodeIntegration: true, devTools: process.env.NODE_ENV === "development" || DEV_TOOLS }, minWidth: 1008, minHeight: 738, title: "title", fullscreen: mainWindowStateKeeper.isFullScreen, fullscreenable: true }); const menu = Menu.buildFromTemplate( menuTemplate(isMac, !PRODUCTION_BUILD || DEV_TOOLS, mainWindow) ); Menu.setApplicationMenu(menu); await mainWindow.webContents.session.clearCache(); mainWindow.setBackgroundColor("#ffffff"); await mainWindow.loadURL( !PRODUCTION_BUILD ? "http://localhost:8080/index.html" : "file://" + resolve(__dirname, "../vue-app/index.html") ); ipcMain.on("call-open-url", () => { if (startUpLink) { BrowserWindow.getAllWindows()[0].webContents.send( "handle-deep-link", startUpLink ); } }); mainWindow.on("close", () => { startUpLink = ""; BrowserWindow.getAllWindows().forEach(otherWindow => { if (mainWindow.id !== otherWindow.id) { otherWindow.close(); } }); }); mainWindowStateKeeper.manage(mainWindow); }; app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); }); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
electron-builder配置
{ "appId": "app.id", "productName": "productname", "protocols": { "name": "app-name", "schemes": [ "my-application" ] }, "files": [ "dist/**/*", "!node_modules${/*}" ], "directories": { "buildResources": "dist", "output": "build" }, "win": { "icon": "dist/electron-app/assets/icon.png", "target": [ "portable", "nsis" ] }, "mac": { "icon": "dist/electron-app/assets/icon.icns", "target": { "target": "default", "arch": "universal" } }, "linux": { "icon": "./dist/electron-app/assets/icon.png", "target": [ "deb", "AppImage" ], "artifactName": "Name.${ext}" } }
问题分析
核心问题在于事件触发顺序和页面加载时机不匹配:
- 完全关闭应用后通过深度链接启动时,
open-url事件会在app.whenReady()之前触发,此时窗口尚未创建,后续创建窗口后直接发送事件,可能页面还未加载完成,导致事件丢失。 - 依赖前端触发
call-open-url的IPC调用处理启动链接,若前端未及时触发,链接会被遗漏。
修复方案
调整事件监听顺序,新增专门的链接处理逻辑,确保页面加载完成后再发送深度链接事件:
修改后的主进程代码
"use strict"; const { app, BrowserWindow, session, ipcMain, Menu, nativeTheme, } = require("electron"); const { homedir } = require("os"); const { resolve, join } = require("path"); const windowStateKeeper = require("electron-window-state"); const { menuTemplate } = require("./templates"); const isMac = process.platform === "darwin"; let startUpLink = ""; if (process.defaultApp) { if (process.argv.length >= 2) { app.setAsDefaultProtocolClient("my-application", process.execPath, [ resolve(process.argv[1]) ]); } } else { app.setAsDefaultProtocolClient("my-application"); } const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); } else { // 提前注册open-url事件,确保启动时能捕获到深度链接 app.on("open-url", (event, url) => { startUpLink = url; event.preventDefault(); }); app.on("second-instance", () => { const mainWindow = BrowserWindow.getAllWindows()[0]; if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } }); app.whenReady().then(async () => { await createWindow(); // 窗口创建后处理暂存的深度链接 handlePendingDeepLink(); }); } // 处理暂存的深度链接,确保页面加载完成后发送事件 const handlePendingDeepLink = () => { if (!startUpLink) return; const mainWindow = BrowserWindow.getAllWindows()[0]; if (!mainWindow) return; if (mainWindow.webContents.isLoading()) { mainWindow.webContents.once("did-finish-load", () => { mainWindow.webContents.send("handle-deep-link", startUpLink); startUpLink = ""; // 处理后清空链接 }); } else { mainWindow.webContents.send("handle-deep-link", startUpLink); startUpLink = ""; } }; const createWindow = async () => { nativeTheme.themeSource = "light"; const mainWindowStateKeeper = windowStateKeeper({ defaultWidth: 1600, defaultHeight: 900, fullScreen: true }); const mainWindow = new BrowserWindow({ width: mainWindowStateKeeper.width, height: mainWindowStateKeeper.height, x: mainWindowStateKeeper.x, y: mainWindowStateKeeper.y, icon: resolve(__dirname, "./assets/icon.png"), webPreferences: { contextIsolation: false, nodeIntegration: true, devTools: process.env.NODE_ENV === "development" || DEV_TOOLS }, minWidth: 1008, minHeight: 738, title: "title", fullscreen: mainWindowStateKeeper.isFullScreen, fullscreenable: true }); const menu = Menu.buildFromTemplate( menuTemplate(isMac, !PRODUCTION_BUILD || DEV_TOOLS, mainWindow) ); Menu.setApplicationMenu(menu); await mainWindow.webContents.session.clearCache(); mainWindow.setBackgroundColor("#ffffff"); await mainWindow.loadURL( !PRODUCTION_BUILD ? "http://localhost:8080/index.html" : "file://" + resolve(__dirname, "../vue-app/index.html") ); mainWindow.on("close", () => { startUpLink = ""; BrowserWindow.getAllWindows().forEach(otherWindow => { if (mainWindow.id !== otherWindow.id) { otherWindow.close(); } }); }); mainWindowStateKeeper.manage(mainWindow); }; app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); }); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow().then(() => { handlePendingDeepLink(); }); } });
可选配置优化
在electron-builder的protocols中补充role字段(不影响功能,仅完善系统注册信息):
{ "protocols": { "name": "app-name", "schemes": ["my-application"], "role": "Editor" } }
关键修复点
- 将
open-url事件监听提前到app.whenReady()之前,确保启动时不会错过深度链接。 - 新增
handlePendingDeepLink函数,专门处理暂存的启动链接,等待页面加载完成后再发送事件。 - 在
app.activate回调中创建窗口后也调用链接处理逻辑,覆盖从dock启动但有暂存链接的场景。 - 移除依赖前端触发的
call-open-urlIPC调用,改为主进程主动处理,避免前端未及时调用导致的链接丢失。
内容的提问来源于stack exchange,提问作者fearistoff
相关产品推荐
相关产品推荐

