39

We have an electron crypto app that signs transactions (among other things).

We want other websites to have the ability to have a button that opens that electron app, pre-filled with some params (the transaction information).

flow is:

  1. user clicks "make transaction" on some-crypto-site.com
  2. electron app opens up with pre-filled params
  3. user clicks "sign transaction" in electron app
  4. electron app does stuff behind the scenes
  5. electron app closes and sends a message to some-crypto-site.com

This could be done at runtime, or install time.

What I tried (linux, chrome)

calling app.setAsDefaultProtocolClient with the code of this gist, which is basically:

app.setAsDefaultProtocolClient("my-app")

But after I put my-app://foo?bar=baz in chrome browser, I get the following popup, and pressing open-xdg does nothing (other than dismissing the popup)

enter image description here

I looked into

  1. Electron protocol api which seems to handle in-app protocols only
  2. webtorrent .desktop file This might be the way to go, I'm just not sure how to go about it.

Maybe there's a way to do so at install time through electron builder?

Thanks in advance for the help, I have no idea how to proceed here!

Resources that might be useful

  1. github repo with mac+window example
  2. github comment for linux
  3. github comment for linux 2
  4. SO answer for all 3 OSs
  5. SO windows answer
  6. npm package for windows registery
  7. SO mac answer
  8. SO linux answer
  9. microsoft docs for windows
  10. windows article
  11. github comment for windows
  12. github comment for mac
  13. info.plst for mac
  14. old repo for mac and win
Michael Gaskill
  • 7,913
  • 10
  • 38
  • 43
Shining Love Star
  • 5,734
  • 5
  • 39
  • 49
  • call `app.setAsDefaultProtocolClient("my-app")`, then open it with `my-app://foo?bar=baz` result in call "your/path/electron.exe my-app://foo?bar=baz" . An delectron.exe will take its first argument as app path, so it's not working. – hiitiger Nov 30 '18 at 09:33
  • @hiitiger Thanks for commenting, I don't understand what you say. Can you clarify? – Shining Love Star Dec 01 '18 at 22:09

2 Answers2

64

Since this may be relevant to what I’m doing at work, I decided to give it a go. I’ve only tested this on OSX though!

I looked at the documentation for app.setAsDefaultProtocolClient and it says this:

Note: On macOS, you can only register protocols that have been added to your app's info.plist, which can not be modified at runtime. You can however change the file with a simple text editor or script during build time. Please refer to Apple's documentation for details.

These protocols can be defined when packaging your app with electron-builder. See build:

{
  "name": "foobar",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "dist": "electron-builder"
  },
  "devDependencies": {
    "electron": "^3.0.7",
    "electron-builder": "^20.38.2"
  },
  "dependencies": {},
  "build": {
    "appId": "foobar.id",
    "mac": {
      "category": "foo.bar.category"
    },
    "protocols": {
      "name": "foobar-protocol",
      "schemes": [
        "foobar"
      ]
    }
  }
}

In your main thread:

const {app, BrowserWindow} = require('electron');

let mainWindow;

function createWindow () {
  mainWindow = new BrowserWindow({width: 800, height: 600})
  mainWindow.loadFile('index.html');
}

app.on('ready', createWindow);

var link;

// This will catch clicks on links such as <a href="foobar://abc=1">open in foobar</a>
app.on('open-url', function (event, data) {
  event.preventDefault();
  link = data;
});

app.setAsDefaultProtocolClient('foobar');

// Export so you can access it from the renderer thread
module.exports.getLink = () => link;

In your renderer thread:

Notice the use of the remote API to access the getLink function exported in the main thread

<!DOCTYPE html>
<html>
  <body>
    <p>Received this data <input id="data"/></p>
    <script>
      const {getLink} = require('electron').remote.require('./main.js');
      document.querySelector('#data').value = getLink();
    </script>
  </body>
</html>

Example

<a href="foobar://abc=1">open in foobar</a>

enter image description here

This also allows you to launch from the command line:

open "foobar://xyz=1"

enter image description here

How do you get back to the original caller?

I suppose that when you launch the app you could include the caller url:

<a href="foobar://abc=1&caller=example.com”>open in foobar</a>

When your electron app finishes processing data, it would simply ping back that url

Credits

Most of my findings are based on:

customcommander
  • 17,580
  • 5
  • 58
  • 84
  • 1
    Fantastic, will look into it ASAP! Did this work for you on development, or did you have to produce an installer and install it to make this work on your local machine? Thanks again! – Shining Love Star Dec 05 '18 at 16:34
  • I haven't tried actually. This was indeed tested against a packaged and installed app. – customcommander Dec 05 '18 at 16:46
  • On linux I've tried with a zippped packaged app, and it doesn't work. At the moment we are not using an installer for linux, so can't test that. Will update if we get this to work. – Shining Love Star Dec 11 '18 at 00:28
  • 2
    Note that the `open-url` event is mac only. Windows and Linux will need to use the argv param of `process.argv` to loop through the params and check if one of them is the url that was used to launch the app. – mix3d Aug 26 '20 at 23:57
  • I am just wondering how would you dubug this kind of application? – Pragam Shrivastava Aug 09 '22 at 08:25
  • Thanks a lot. You have shed light with clear explanation – Alaksandar Jesus Gene Oct 02 '22 at 00:04
1

All little bit different from above.

open-url fires before the ready event so you can store it in a variable and use within the widow did-finish-load.

let link;

let mainWindow;

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 1280,
        height: 720,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false
        }
    });
    mainWindow.openDevTools();
    mainWindow.setContentProtection(true);
    mainWindow.loadFile('index.html');
    mainWindow.webContents.on("did-finish-load", function() {
        mainWindow.webContents.send('link', link);
    });
}

app.on('ready', createWindow);

// This will catch clicks on links such as <a href="protocols://abc=1">open in foobar</a>
app.on('open-url', function(event, url) {
    link = url;
    if (mainWindow?.webContents) {
        mainWindow.webContents.send('link', link);
    }
});

app.setAsDefaultProtocolClient('protocols');

You can then use the value in your render html like this.

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <script>
            const ipc = require("electron").ipcRenderer;
            ipc.on("link", function (event, url) {
                console.log(url);
                console.log(parseQuery(decodeURI(url)));
            });
            function parseQuery(queryString) {
                queryString = queryString.substring(queryString.indexOf("://") + 3);
                var query = {};
                var pairs = (queryString[0] === "?" ? queryString.substr(1) : queryString).split("&");
                for (var i = 0; i < pairs.length; i++) {
                    var pair = pairs[i].split("=");
                    query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
                }
                return query;
            }
        </script>
    </body>
</html>
user1503606
  • 3,872
  • 13
  • 44
  • 78
  • I tried the above steps in windows, throws me an error stating the unable to find electron app.. any suggestions? – santosh kumar Nov 28 '21 at 05:27
  • This was exactly what I needed. To explain the value here: the `open-url` event may arrive either **before** or **after** the main window is initialized, depending on whether the URL was the reason the app launched. For the **before** case, we save the URL to be handled later in `did-finish-load`; for the **after** case, we handle it immediately (as `mainWindow` is no longer `undefined`). – ThatsJustCheesy Oct 09 '22 at 19:26