Electron tips & tricks: Detect OS in renderer process

NGUYEN Trung
4 min readDec 31, 2021

--

Credit to Pagergeek.fr

In this series of Tips & Tricks, I will share some experiences developing Desktop Application using Electron. I do not know in advance how many articles will be written in this series, but I’ll keep adding new ones, covering frequent patterns while working with Electron, so make sure to check back regularly for new material.

For the first article, let me start by asking a question: could you tell me the difference between these dialogs from the same Github Desktop application for Mac and Windows?

Adding local Git repository (Github Desktop for Mac)
Adding local Git repository (Github Desktop for Windows)

Easy easy!! You’re right about the position of the CTA Add Repository. But wait a minute, is it Add Repository or Add repository? 👀

And here comes the aha moment. Even if we use Electron to build cross-platform desktop applications, we still have some subtle distinction between different operating systems. In reality, the difference could be far more than simple styling issue.

For those who’re interested in this specific example: the difference is known as OK-Cancel or Cancel-OK problem. About casing issue, Apple Human Interface Guidelines recommends using title-case for push button titles. That’s why we have Add Repository for Mac.

To put it simply, we want to ensure the right position of the CTA according to platform information in the UI:

Dead simple logic to show CTA

As you might know, visual differences across platforms could happen at different levels of an Electron application:

  • The main process
  • A renderer process (UI process)

The main process

According to its definition, the main process has access to Node APIs:

So by using these above booleans, it’s good enough to know on which platform the main process is running. Easily done ✌️

A renderer process

Some might say that a renderer process is like a web page which has access to DOM APIs, so we could find some useful information in window.navigator. At the time of writing this article, there’re some confusing solutions related to window.navigator, perhaps the most reliable one was from javascripter.net, but at the end the author wasn’t even sure about that:

Caution! Some clients may spoof their navigator.userAgent string, i.e. substitute another OS name (or any string at all) for the user agent value. Generally, your code should not rely on navigator.userAgent to do anything important.

Looking at MDN, navigator.platform|navigator.appVersion are likely being deprecated 😢. It looks like trying to get platform information solely from the UI isn’t reliable enough.

There’re at least two approaches to resolve this issue but before getting into those, one tip I find extremely useful when working with Electron is trying to see the application as a whole unbreakable thing, even if we have different technical bricks behind the scene due to Electron architecture. This tip is not only useful for the rest of this article, but also valuable for resolving possible issues in the future.

1st approach: Sharing platform information between main process/renderer process

This approach is very much inspired by the idea of seeing an Electron application as a whole thing, all information should be shareable across processes.

Following the least privilege principle in security, Electron has NodeJS integration disabled and contextIsolation turned on by default to any newly created BrowserWindow (renderer process). So one way to share platform information to renderer process is to simply expose it via contextBridge in the preload script:

Example of a real Electron application following this approach: Simplenote

2nd approach: Getting platform information at build time

Another way to do is to get platform information on which the Electron application is running in advance, and then supply this information globally throughout the entire renderer process.

Let’s say we use Webpack as the bundler for our UI, we would proceed like following:

One caveat to this approach is we need to make sure the application packaging process should run on each corresponding platform. Notice that if we use something like electronuserland/builder:wine to have multi-platform build, process.platform will be likely linux all the time.

Example of a real Electron application following this approach: Github Desktop

Another possible approach (not recommended)

We could enable Node integration for renderer process and then rely directly on Node APIs to detect platform information in the same way as we did for the main process. But as it isn’t recommended by the Electron team, we should make sure at least we don’t load any external script into the renderer process that could do something harmful to the user’s machine. So do it with your own risk! 👮

Some excellent npm packages (like electron-util, electron-is) could help, but they will likely require enabling Node integration for renderer process to work correctly.

I hope that through this simple explanation you will have some clear idea about how to detect platform in an Electron renderer process. Thanks for your attention and please let me know if some points aren’t clear enough for you/you have another way to do it.

--

--