DEV Community

loading...

Electron Adventures: Episode 10: Preload Script

Tomasz Wegrzanowski
Updated on ・2 min read

I was talking a lot about Electron security, and how just giving your frontend full access to your system might not be the best idea ever. So what else Electron recommends?

The current solution - and they reached that after a few iterations - is preload script.

We still have frontend and backend code, but we have third code - preload script - that initializes frontend, does any kind of highly privileged things, before handing it over to regular code.

If you do things right, frontend will have access to just the thing preload scripts setup, and nothing more. Which in our case won't help us at all, as we're writing terminal app, so the frontend is supposed to be able to literally any shell command, but this is great for more applications with more limited functionality, like a webchat app.

Enable preload script

Let's start with app we wrote in previous episode. We need te got rid of nodeIntegration: true and contextIsolation: false.

For some reason Electron really wants preload path to be absolute path, not relative path of file URL:

let { app, BrowserWindow } = require("electron")

function createWindow() {
  let win = new BrowserWindow({
    webPreferences: {
      preload: `${__dirname}/preload.js`,
    },
  })
  win.maximize()
  win.loadFile("index.html")
}

app.on("ready", createWindow)

app.on("window-all-closed", () => {
  app.quit()
})
Enter fullscreen mode Exit fullscreen mode

Preload script

Let's move runCommand to preload.js, and use contextBridge to expose it:

let child_process = require("child_process")
let { contextBridge } = require("electron")

let runCommand = (command) => {
  return child_process.execSync(command).toString().trim()
}

contextBridge.exposeInMainWorld(
  "api", { runCommand }
)
Enter fullscreen mode Exit fullscreen mode

contextBridge.exposeInMainWorld defines which extra things we expose in the frontend. In this case we told it that we want window.api object, with a single method runCommand.

It's important to note that this extra functionality will stay available even if you move to a completely different domain, so be careful.

Use preload script

In the frontend script, we just need to change two lines - remove the require as it would no longer work, and call our exposed method window.api.runCommand instead of child_process.execSync:

form.addEventListener("submit", (e) => {
  e.preventDefault()
  let command = input.value
  let output = window.api.runCommand(command)
  createTerminalHistoryEntry(command, output)
  input.value = ""
  input.scrollIntoView()
})
Enter fullscreen mode Exit fullscreen mode

Result

No screenshot this time, as it looks identical to previous episode, all that changes were internals.

So did we actually gain anything? Yes we did. As frontend script no longer uses weird require shenanigans, it's now a regular frontend code, and we can use any bundler we want with it.

So over the next few episodes we'll be exploring how to use various frontend frameworks with Electron.

As usual, all the code for the episode is here.

Discussion (0)