reZach / secure-electron-template

The best way to build Electron apps with security in mind.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Can't get initial app configuration from main

donadiomauro opened this issue · comments

Hi,

I anticipate I'm not an Electron expert.
I am using secure-electron-template as a valid and strong starting point for my application.

The issue I am having is with the store.
I understand the data-flow for secure-electron-store but I'm struggling to make main reading it.

I need to read the app configuration (options such as "Start at login") and set them in the tray menu. This means it has to happen at the app bootstrap and can't wait for the window (or React app) to render.

I have tried in many ways, even triggering a useConfigInMainRequest from the main file but with no avail and I am now a bit lost.

How can I achieve it by keeping the best practices?

PS: I know this is not the best place where to ask for implementation help but for issues. However, I haven't found any other forum where to ask.

Thanks,
Mauro

Hi @donadiomauro,

What exactly are you trying to configure? Can you share your main code? You are using a tray?

The best fix is to minimize the app, and after configuring what you need, maximize the app as shown here. You are saying this process doesn't work for your scenario?

Hi @reZach ,

Thank you for your response.

I started with secure-electron-template and there is still no custom code added, only the Tray block.
In fact, I am using Tray and added the following:

app.whenReady().then(() => {
  tray = new Tray(path.join(__dirname, "../assets/IconTemplate.png"));
  const contextMenu = Menu.buildFromTemplate([
    { label: "Start at login", checked: configVariableFromStoreAtBootstrap },
    { label: "Preferences" },
    { type: "separator" },
    { role: "quit" },
  ]);
  tray.setToolTip("This is my application.");
  tray.setContextMenu(contextMenu);
});

In theory, I would get the value of configVariableFromStoreAtBootstrap form the store and set it during the main bootstrap.

Using the minimize-and-maximise instructions don't allow me to set configVariableFromStoreAtBootstrap because main has already been initialised. So, in my case, the Tray icon wouldn't be properly initialised.

Thanks,
Mauro

@donadiomauro ,

whenReady is fired after the Electron app is intialized (https://www.electronjs.org/docs/api/app#event-ready), so I don't think running the code after the app has started presents any problems.

I think you need a few modifications:

  1. Hold a reference to tray outside the whenReady block, it may be getting garbage collected and deleted before you can see it. Similar to how win is set up in the template:
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
  1. Instead of initializing the tray in whenReady, just set a variable that holds the event was called. I'm suggesting, then, to initialize the tray in the callback, perhaps polling with an interval for this variable to be 1 or true, in here:
  // Sets up main.js bindings for our electron store;
  // callback is optional and allows you to use store in main process
  const callback = function(success, store){
    console.log(`${!success ? "Un-s" : "S"}uccessfully retrieved store in main process.`);
    console.log(store); // {"key1": "value1", ... }
    
    win.maximize(); // modify BrowserWindow, for example
  };

Give this a try, hopefully it will work.

Hi @reZach ,

I have tried the solution you suggested and it sorted out part of the issue.

I can now set up the initial state for the Tray in the callback function.
However, in order to update the state, I will need an onClick event. The action of this event will update the store state from the tray menu. This causes an error.

In code-terms, I have changed main as follows:

// main.js

// added tray as a global variable
let tray;
// main.js

const callback = function (success, store) {
  console.log(
    `${!success ? "Un-s" : "S"}uccessfully retrieved store in main process.`
  );
  console.log(store); // {"key1": "value1", ... }

  // if tray is not initialised
  if (!tray) {
    console.log(
      `store.startAtLogin contains ${store.startAtLogin} and I am now creating the Tray`
    );

    tray = new Tray(path.join(__dirname, "../assets/IconTemplate.png"));
    const contextMenu = Menu.buildFromTemplate([
      {
        label: "Start at login",
        type: "checkbox",
        checked: store.startAtLogin,
        click: (event) => {
          // *** this won't work here ***
          store.send(writeConfigRequest, "startAtLogin", event.checked);
        },
      },
      { type: "separator" },
      { label: "Preferences" },
      { role: "quit" },
    ]);
    tray.setToolTip("This is my application.");
    tray.setContextMenu(contextMenu);
  }

  win.maximize();
};

The callback of the onClick event in the tray will generate an error. There, I don't know how to update the store.

// root.jsx

import {
  writeConfigRequest,
  useConfigInMainRequest,
} from "secure-electron-store";

// ...

componentDidMount() {
  // Request so that the main process can use the store
  window.api.store.send(useConfigInMainRequest);
  window.api.store.send(writeConfigRequest, "startAtLogin", true);
}

// ...

@donadiomauro what's the error? I'm guessing store isn't defined?

@reZach

yes, that's correct, I get the following error:

A JavaScript error occurred in the main process

Uncaught Exception:
TypeError: store.send is not a function
    at click (/XYZ/app/electron/main.js:97:19)
    at MenuItem.click (electron/js2c/browser_init.js:1549:9)
    at Function.executeCommand (electron/js2c/browser_init.js:1810:13)

I have also tried with the following, but it was more a blind test-and-fail than something well pondered.

ipcMain.store.send(writeConfigRequest, "startAtLogin", event.checked);
// and
win.store.send(writeConfigRequest, "startAtLogin", event.checked);

@donadiomauro as I expected.

This can help point you in the right direction, if it doesn't work outright. The click event has three parameters, and the error is occurring because store is not defined in this function.

Something like this is ugly, but I think it can work

const callback = function (success, store) {
  // if tray is not initialized
  if (!tray) {
    console.log(
      `store.startAtLogin contains ${store.startAtLogin} and I am now creating the Tray`
    );

    tray = new Tray(path.join(__dirname, "../assets/IconTemplate.png"));
    const clickHandler = function(menuItem, browserWindow, event){
        this.send(writeConfigRequest, "startAtLogin", event.checked);
    }.bind(store);
    const contextMenu = Menu.buildFromTemplate([
      {
        label: "Start at login",
        type: "checkbox",
        checked: store.startAtLogin,
        click: clickHandler
      },
      { type: "separator" },
      { label: "Preferences" },
      { role: "quit" },
    ]);
    tray.setToolTip("This is my application.");
    tray.setContextMenu(contextMenu);
  }

  win.maximize();
};

Otherwise,.. one could look at this code for inspiration, that would likely be necessary to be added to this package for it to work the way you intend it.

@donadiomauro Didn't mean to close this, Github is getting smarter!

I pushed changes to the store which should allow you to do what you originally asked with this issue. Can you please review, [and re-open if necessary]?