Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Electron - How to create deep-linking on linux

I have an ElectronJS project and I use the protocols (deep-link) in this one. It's work on MacOS and Windows but on Linux I can't understand how to create this protocol.

I have looked in the ElectronJS documentation as well as on the web for issues etc. but I can't figure out how to initialize protocol on Linux. All I want is to achieve, as I have succeeded on MacOS and Windows, a protocol to interact with the app in deep-link.

Code that works on MacOS and Windows :

// main.ts

// –– B ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

ProtocolUtils.setDefaultProtocolClient();

// eslint-disable-next-line default-case
switch (process.platform) {
    case 'darwin':
        ProtocolUtils.setProtocolHandlerOSX();
        break;
    case 'win32':
        ProtocolUtils.setProtocolHandlerWin32();
        break;
}
// –– E ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

// protocol.ts

export abstract class ProtocolUtils {
    /**
     * @description Create default protocole for call this app.
     *  Ex : in your browser => myapp://test
     */
    public static setDefaultProtocolClient(): void {
        if (!app.isDefaultProtocolClient('myapp')) {
            // Define custom protocol handler.
            // Deep linking works on packaged versions of the application!
            app.setAsDefaultProtocolClient('myapp');
        }
    }

    /**
     * @description Create logic (WIN32) for open url from protocol
     */
    public static setProtocolHandlerWin32(): void {
        // Force Single Instance Application on win32
        const gotTheLock = app.requestSingleInstanceLock();

        app.on('second-instance', (e: Electron.Event, argv: string[]) => {
            // Someone tried to run a second instance, we should focus our window.
            if (MainWindow.mainWindow) {
                if (MainWindow.mainWindow.isMinimized()) MainWindow.mainWindow.restore();
                MainWindow.mainWindow.focus();
            } else {
                MainWindow.openMainWindow(); // Open main windows
            }

            app.whenReady().then(() => {
                MainWindow.mainWindow.loadURL(this._getDeepLinkUrlForWin32(argv)); // Load URL in WebApp
            });
        });

        if (gotTheLock) {
            app.whenReady().then(() => {
                MainWindow.openMainWindow(); // Open main windows
                MainWindow.mainWindow.loadURL(this._getDeepLinkUrlForWin32()); // Load URL in WebApp
            });
        } else {
            app.quit();
        }
    }

    /**
     * @description Create logic (OSX) for open url from protocol
     */
    public static setProtocolHandlerOSX(): void {
        app.on('open-url', (event: Electron.Event, url: string) => {
            event.preventDefault();
            app.whenReady().then(() => {
                MainWindow.openMainWindow(); // Open main windows
                MainWindow.mainWindow.loadURL(this._getUrlToLoad(url)); // Load URL in WebApp
            });
        });
    }

    /**
     * @description Format url to load in mainWindow
     */
    private static _getUrlToLoad(url: string): string {
        // Ex: url = myapp://deep-link/test?params1=paramValue
        // Ex: Split for remove myapp:// and get deep-link/test?params1=paramValue
        const urlSplitted = url.split('//');
        // Generate URL to load in WebApp.
        // Ex: file://path/index.html#deep-link/test?params1=paramValue
        const urlToLoad = format({
            pathname: Env.BUILDED_WEBAPP_INDEX_PATH,
            protocol: 'file:',
            slashes: true,
            hash: `#${urlSplitted[1]}`,
        });

        return urlToLoad;
    }

    /**
    * @description Resolve deep link url for windows from process argv
    */
    private static _getDeepLinkUrlForWin32(argv?: string[]): string {
        let url: string;
        const newArgv: string[] = !isNil(argv) ? argv : process.argv;
        // Protocol handler for win32
        // argv: An array of the second instance’s (command line / deep linked) arguments
        if (process.platform === 'win32') {
            // Get url form precess.argv
            newArgv.forEach((arg) => {
                if (/myapp:\/\//.test(arg)) {
                    url = arg;
                }
            });

            if (!isNil(url)) {
                return this._getUrlToLoad(url); // Load URL in WebApp
            } else if (!isNil(argv) && isNil(url)) {
                throw new Error('URL is undefined');
            }
        }
    }
}

I have no worries for macOS and windows, but on linux the protocol does not exist even with the line : ProtocolUtils.setDefaultProtocolClient(); who is responsible for creating the myapp: // protocol...

When I run this command : xdg-open myapp://deep-link/test?toto=titi An error tells me that this protocol does not exist

If anyone has an example for me to configure on Linux or can just help me ?

Thanks

like image 775
Jboucly Avatar asked Dec 06 '25 03:12

Jboucly


1 Answers

Ok i have found the solution !

First, we removed electron-forge and replaced it with electron-builder (cf doc).

Then after reading a lot of documentation for deep links on Linux, Examples of documentation :

  • Desktop file
  • Specification of desktop entry
  • Electron builder desktop file

And my solution is :

# electron-builder.yml
appId: com.myapp.myapp
productName: myapp
directories:
    output: out
linux:
    icon: src/assets/icons/app/[email protected]
    category: Utility
    mimeTypes: [x-scheme-handler/myapp] # Define MimeType
    desktop:                            # Define desktop elem
        exec: myapp %u                  # Define Exec
    target:
        - target: deb
          arch:
              - x64

So i defined the MimeType with the name of my protocol here myapp which could give:

myapp://toto?foo=bar

And in my desktop file define Exec with myapp %u because %u => A single URL. Local files may either be passed as file: URLs or as file path. (cf doc)

And for finish in my main.ts and protocol.utils.ts:

// main.ts


// –– B ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

ProtocolUtils.setDefaultProtocolClient();

switch (process.platform) {
    case 'darwin':
        ProtocolUtils.setProtocolHandlerOSX();
        break;
    case 'linux':
    case 'win32':
        ProtocolUtils.setProtocolHandlerWindowsLinux();
        break;
    default:
        throw new Error('Process platform is undefined');
}
// –– E ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// protocol.utils.ts

export abstract class ProtocolUtils {
    public static setDefaultProtocolClient(): void {
        if (!app.isDefaultProtocolClient('myapp')) {
            // Define custom protocol handler.
            // Deep linking works on packaged versions of the application!
            app.setAsDefaultProtocolClient('myapp');
        }
    }

    /**
     * @description Create logic (WIN32 and Linux) for open url from protocol
     */
    public static setProtocolHandlerWindowsLinux(): void {
        // Force Single Instance Application
        const gotTheLock = app.requestSingleInstanceLock();

        app.on('second-instance', (e: Electron.Event, argv: string[]) => {
            // Someone tried to run a second instance, we should focus our window.
            if (MainWindow.mainWindow) {
                if (MainWindow.mainWindow.isMinimized()) MainWindow.mainWindow.restore();
                MainWindow.mainWindow.focus();
            } else {
                // Open main windows
                MainWindow.openMainWindow();
            }

            app.whenReady().then(() => {
                MainWindow.mainWindow.loadURL(this._getDeepLinkUrl(argv));
            });
        });

        if (gotTheLock) {
            app.whenReady().then(() => {
                // Open main windows
                MainWindow.openMainWindow();
                MainWindow.mainWindow.loadURL(this._getDeepLinkUrl());
            });
        } else {
            app.quit();
        }
    }

    /**
     * @description Create logic (OSX) for open url from protocol
     */
    public static setProtocolHandlerOSX(): void {
        app.on('open-url', (event: Electron.Event, url: string) => {
            event.preventDefault();
            app.whenReady().then(() => {
                if (!isNil(url)) {
                    // Open main windows
                    MainWindow.openMainWindow();
                    MainWindow.mainWindow.loadURL(this._getUrlToLoad(url));
                } else {
                    this._logInMainWindow({ s: 'URL is undefined', isError: true });
                    throw new Error('URL is undefined');
                }
            });
        });
    }

    /**
     * @description Format url to load in mainWindow
     */
    private static _getUrlToLoad(url: string): string {
        // Ex: url = myapp://deep-link/test?params1=paramValue
        // Ex: Split for remove myapp:// and get deep-link/test?params1=paramValue
        const splittedUrl = url.split('//');
        // Generate URL to load in WebApp.
        // Ex: file://path/index.html#deep-link/test?params1=paramValue
        const urlToLoad = format({
            pathname: Env.BUILDED_APP_INDEX_PATH,
            protocol: 'file:',
            slashes: true,
            hash: `#${splittedUrl[1]}`,
        });

        return urlToLoad;
    }

    /**
    * @description Resolve deep link url for Win32 or Linux from process argv
    * @param argv: An array of the second instance’s (command line / deep linked) arguments
    */
    private static _getDeepLinkUrl(argv?: string[]): string {
        let url: string;
        const newArgv: string[] = !isNil(argv) ? argv : process.argv;
        // Protocol handler
        if (process.platform === 'win32' || process.platform === 'linux') {
            // Get url form precess.argv
            newArgv.forEach((arg) => {
                if (/myapp:\/\//.test(arg)) {
                    url = arg;
                }
            });

            if (!isNil(url)) {
                return this._getUrlToLoad(url);
            } else if (!isNil(argv) && isNil(url)) {
                this._logInMainWindow({ s: 'URL is undefined', isError: true });
                throw new Error('URL is undefined');
            }
        }
    }

And it's WORK :D

like image 167
Jboucly Avatar answered Dec 09 '25 13:12

Jboucly