You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}"
  }
}

问题分析

核心问题在于事件触发顺序和页面加载时机不匹配:

  1. 完全关闭应用后通过深度链接启动时,open-url事件会在app.whenReady()之前触发,此时窗口尚未创建,后续创建窗口后直接发送事件,可能页面还未加载完成,导致事件丢失。
  2. 依赖前端触发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"
  }
}

关键修复点

  1. 将open-url事件监听提前到app.whenReady()之前,确保启动时不会错过深度链接。
  2. 新增handlePendingDeepLink函数,专门处理暂存的启动链接,等待页面加载完成后再发送事件。
  3. 在app.activate回调中创建窗口后也调用链接处理逻辑,覆盖从dock启动但有暂存链接的场景。
  4. 移除依赖前端触发的call-open-urlIPC调用,改为主进程主动处理,避免前端未及时调用导致的链接丢失。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 16:10:54