Holochain Forum

How to enable dynamic configuration of Electron hApp — sim2h URL example

Overview

Following on Connor’s great article on How To Ship Your hApp, here is a short guide on how to extend from there to add the possibility to let end users change the sim2h server URL used for finding peers.
This is useful if ever the public one that was hardcoded in your electron happ is down and want to provide alternatives to your users. It also enables your users to use their own private servers for their community if they wish to.
Even though sim2h will be going away with the new Holochain, Holochain RSM, this guide will still be useful as, even with Holochain RSM, users could require to connect to a server for peer discovery or traffic relay over NAT.

Extending

With Connor’s setup, at first launch the electron container generates a conductor config from a template and stores it in the appData folder. Then it spawns a holochain subprocess that uses that config.
This is quite practical as it allows you to take in account values specific to the user, like the persistence folder path, or use updated values automatically like the latest DNA hash of your happ. However once generated, there is no end-user friendly way to change any setting in the conductor config. To modify it, you would have to find the file on your drive and open it with a text editor.

The additions I did in SnapMail to allow easy changing of sim2h URL are:

  1. Prompt the user for a sim2h URL at first launch before generating the conductor config, instead of having it hardcoded in the conductor config template.
  2. Add a menu item that lets the user change the sim2h URL.
  3. Once set, have electron regenerate the conductor config and restart the holochain subprocess.

Launching the Prompt

To easily do all this, I used a global variable to store the sim2h url: g_sim2hUrl.

At launch it will be set when loading the conductor config:

function tryLoadingConfig() {
  try {
    const conductorConfigBuffer = fs.readFileSync(CONDUCTOR_CONFIG_PATH);
    const conductorConfig = conductorConfigBuffer.toString();
    let regex = /sim2h_url = "(.*)"/g;
    let match = regex.exec(conductorConfig);
    g_sim2hUrl = match[1];
    ...

Otherwise it will launch the prompt:

app.on('ready', function () {
  ...
  // if sim2hUrl not set, prompt it, otherwise Start Conductor
  if(g_sim2hUrl === "") {
    promptSim2hUrl(function() {
      startConductor(true);
    });
  } else {
    startConductor(false);
  }
});

promptSim2hUrl() makes use of electron-prompt and creates a prompt of type input with an input type of URL. This is neat as you have user input validation for URLs. The argument is the callback function that will be called once the prompt has returned successfully. The value received from the prompt will be automatically stored in the global variable g_sim2hUrl.

Restarting Holochain

startConductor() restarts the holochain subprocess. The argument tells it if it should regenerate the conductor config or not. Thats why the value is true after prompting as we want to regenarate the config with the new sim2h URL value stored in g_sim2hUrl.

startConductor() also makes sure any previously launched holochain subprocess is killed first:

function startConductor(canRegenerateConfig) {
  // Make sure there is no outstanding holochain procs
  killAllWsl(HC_BIN);
  killAllWsl(HOLOCHAIN_BIN);

  // check if config and keys exist, if they don't, create one.
  if (!fs.existsSync(KEYSTORE_FILE_PATH) || !fs.existsSync(CONDUCTOR_CONFIG_PATH)) {
    createKeysAndConfig(HC_BIN, g_sim2hUrl, function(pubKey) {
      g_pubKey = pubKey;
      startHolochainProc();
    });
  } else {
    if (canRegenerateConfig) {
      log('info', 'Updating ConductorConfig with new sim2hUrl.');
      generateConductorConfig(g_pubKey, g_sim2hUrl);
    }
    log('info', 'Public key and config found. Launching conductor...');
    startHolochainProc();
  }
}

The Prompt

If the user cancels that prompt it will close the happ on the spot as a sim2h URL is required in order to generate a valid conductor config:

function promptSim2hUrl(callback) {
  prompt({
    title: 'Sim2h URL',
    height: 180,
    width: 400,
    alwaysOnTop: true,
    label: 'URL:',
    value: g_sim2hUrl,
    inputAttrs: {
      type: 'url'
    },
    type: 'input'
  }).then((r) => {
      if(r === null) {
        console.log('user cancelled');
        if (g_sim2hUrl === "") {
          app.quit(); // EXIT
        }
      } else {
        console.log('result', r);
        g_sim2hUrl = r; // STORE PROMPT VALUE
        callback();
      }
    })
    .catch(console.error);
}

Result:

The Menu

Adding a menu item in electron is quite straightforward:

const menutemplate = [
  {
    label: 'Edit',
    submenu: [
      {
        label: 'Change Sim2h URL', click: function () { 
          promptSim2hUrl(function() {
            startConductor(true);
          });
        }
      },
      {
        label: 'Restart Conductor', click: function () {
          startConductor(false);
        }
      },
    ],
  }];

With startConductor() already written, it was quite easy to add an additionnal menu item to let the user restart the conductor manually. Its also handy for testing and debugging.

Result:

Regenerating the conductor Config

The code was already generating a conductor config from a template, so we just needed to put that code in its own function so we can call it when required, as we’ve seen in the Menu and Prompt.
To make the code clearer, I moved the conductor config generation code in its own file in the SnapMail repo.

Conclusion

As you can see, Electron already provides the toolset to make this easy to implement. We could painlessly continue extending the electron happ like this and add more features like checking the status of a sim2h server by calling hc sim2h-client and automatically change to one that is online if the current one is down. Or we could add the possibility to switch between different agents or conductor configs. We just have to be mindful that those features would be specific to electron and will not be part of the happ when run in a web browser or a holoport.

2 Likes