Search code examples
javascriptelectrondeep-linking

Can't get payload from Deep Link while open app from browser (Electron 20)


I'm trying to handle Deep Links in my App (electron@20.1.3, electron-builder@23.1.0). Overall it works (at least on MacOS) well, but when i close my App completely and trying to open App with Deep Links App opens, but without Deep Links behaviour.

I was using code from Official Documentation for that case, but i can't see any lines of code for my problem.

"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();
});
{
  "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}"
  }
}

Solution

  • Figured it out!

    My mistake was that I added event listener to ipcMain to late, because i was using app.whenReady, not app.on("open-url").

    Seems like app.whenReady fires earlier than app.on("open-url") and app lost their chance to listen app.on("open-url") and get event that comes with running app.

    Final code is something like that:

    "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.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);
      });
    
      // placed after open-url handler just in case and use another ready event handler
      app.on("ready", async () => {
        await createWindow();
      });
    }
    
    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();
    });