How to upgrade to Electron 14+

I use an open source application called Caprine which has a very old Electron version (10). Due to a bug in Electron 10 it wasn't even able to start on my OS (Ubuntu 22.04). Then, I decided to fork the project and try my best to update it to Electron 13. I merged an open upstream pull request which was able to get me up to Electron 11. From there, I changed the deprecated shell.moveItemToTrash to shell.trashItem which got me to Electron 13.

Now, since Electron 13 has been deprecated and doesn't even work on Wayland, I think that is it time to upgrade again. The only problem is that I have trouble with the remote module which has been moved to userland. How am I supposed to enable the remote module?

P.S. : I don't know a thing about JS/TS. I only have some limited experience with C++. I just want Caprine to work correctly on my system and also help others who have similar issues.

What I have tried so far:

  1. enableRemoteModule is missing from Electron v14 TypeScript type definitions
  2. https://github.com/electron/electron/issues/21408#issuecomment-564184702

When I follow those steps the application just doesn't run.

Upvotes: 0

Views: 574

Answers (1)

reZach
reZach

Reputation: 9429

I unfortunately do not have enough time in order to investigate every single method call within the project source or your fork, but would like to say in general, what you will need to do is move any npm package or OS-system call (ie. fs) to your main process instead of the renderer process. I've written a post describing how the Electron framework has changed. The big picture is that the best practices in latest versions of Electron is that the main process should be accessing anything on your OS, while the renderer (ie. view) is only rendering the UI of your app. It's bad security wise to have OS-level operations in your UI. I spent almost two years creating this secure Electron template and still maintain it today. (Old Electron apps can require some significant investment in order to nicely upgrade to new versions of the Electron framework if they heavily use Remote or require modules in their renderer processes).

Move away/don't use Remote. I have another post that has a more concrete example of how one might use fs and not use Remote in an Electron app. You'd have to something similar with any other modules that are require'd in the renderer/UI process in order to move Caprine to a more-recent Electron framework version.

main.js

const {
  app,
  BrowserWindow,
  ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");

// 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;

async function createWindow() {

  // Create the browser window.
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // is default value after Electron v5
      contextIsolation: true, // protect against prototype pollution
      enableRemoteModule: false, // turn off remote
      preload: path.join(__dirname, "preload.js") // use a preload script
    }
  });

  // Load app
  win.loadFile(path.join(__dirname, "dist/index.html"));

  // rest of code..
}

app.on("ready", createWindow);

ipcMain.on("toMain", (event, args) => {
  fs.readFile("path/to/file", (error, data) => {
    // Do something with file contents

    // Send result back to renderer process
    win.webContents.send("fromMain", responseObj);
  });
});

preload.js

const {
    contextBridge,
    ipcRenderer
} = require("electron");

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        send: (channel, data) => {
            // whitelist channels
            let validChannels = ["toMain"];
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, data);
            }
        },
        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        }
    }
);

index.html

<!doctype html>
<html lang="en-US">
<head>
    <meta charset="utf-8"/>
    <title>Title</title>
</head>
<body>
    <script>
        // Called when message received from main process
        window.api.receive("fromMain", (data) => {
            console.log(`Received ${data} from main process`);
        });

        // Send a message to the main process
        window.api.send("toMain", "some data");
    </script>
</body>
</html>

Upvotes: 1

Related Questions