Electron on the App Store, Pain & tears.
Tags: electron, javascript, appstore, menubar
Published: 2021-02-09 21:40:41

This article is part of a 5 articles series about the publication of an Electron application into the Mac AppStore, [Fenêtre](https://fenêt.re).

> [Fenêtre,](https://fenêt.re/) `fənɛtʁ`, lets you better multitask on your mac. It enables a [picture-in-picture](https://en.wikipedia.org/wiki/Picture-in-picture) mode for any website/web-app, image, video or flat file.
> You can find the [free version](https://itunes.apple.com/us/app/fen%C3%AAtre-lite/id1288451627)and the [paid version](https://itunes.apple.com/us/app/fen%C3%AAtre/id1286743037) on the Mac App-Store.

---

Development usually comes with some pain, this is not new, but I'll try to cover what was particularly hard to fix and/or to find a solution for.

# Custom schemes

I wanted to use the custom scheme `fenetre://`to open links from the browser into the app. It seemed so easy following [Electron's](https://github.com/electron/electron/blob/master/docs/api/app.md#appsetasdefaultprotocolclientprotocol-path-args) and [Apple's](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115) documentation on the subject. And it worked flawlessly _in development_.

Once sand-boxed, it stopped working. And it wasn't an easy found bug, since it took 3 fully published and reviewed versions to figure this one out.

You could accurately follow my descent into the abyss through the [@FenetreApp](https://twitter.com/FenetreApp) twitter feed.

{% twitter 917662600401506305 %}

{% twitter 917883630243983361 %}

But, eventually... in the end.

{% twitter 918254037467910144 %}

Instead of using a custom scheme, I had to run a server in the app, on a specified port. Then, the browser extension would call a route on this server to open the URL, passed as argument, into the app 🤮.

And I hate this so much.

# DRM Content

*EDIT:* This is now impossible to mock. You'll have to use a proprietary tool to publish your app in order to hope serving DRM content.

When delivering content from the web, especially videos, you'll be hit in the face with DRM. Netflix, for example, won't let you play videos anywhere you like. You need a decoding plugin, called [Widevine](http://www.widevine.com/). It's already embedded in your day-to-day browser, but when you're using Chromium (Electron's core) you'll need to get it yourself.

The best way is to look for [the Chromium's major version your current Electron uses](https://github.com/electron/electron/blob/master/docs/tutorial/using-widevine-cdm-plugin.md) via `process.versions` in the renderer process. Then download the [same version of Chrome](https://www.slimjet.com/chrome/google-chrome-old-version.php) and go spelunking into the `.app` file.

At the time of this writing, it can be found here:

```
Google Chrome.app/Contents/Versions/[version]/Google Chrome Framework.framework/Versions/A/Libraries/WidevineCdm/
```

Finally, activate it in your app, as early as you can, before `app.on('ready')`:

{% gist https://gist.github.com/yoannmoinet/38fc03c9ca275bb934841fe438a09644 file=activatePlugins.js %}

> You can do the same with the PepperFlash plugin, to read flash content.

**Important notes:**

* to be updated alongside Electron.
* to be manually copied into your package.
* to be referenced as an absolute path.

# The French tartine de caca

Since I'm French, I wanted something that sounded French. That's where this `ê` came in, busting everything I did.

Fenêtre was a fun name, pronounced `fənɛtʁ` or _Fonaytre_, it means window in French, so it was very relevant to the project and it sounded _putain de_ French. But nothing prepared me to how painful it would be to use a non-ASCII character in today's internet. I already knew it was stupid, but not _that_ stupid.

* **APFS vs HFS+**

Some time during the development, I decided to upgrade my machine to High Sierra, what a mistake that was.

The file system changed [from HFS+ to APFS](https://eclecticlight.co/2017/07/05/high-sierra-and-filenames-apple-is-relenting/), and now, the system doesn't normalize filenames like it used to. So if you have non-ASCII characters in your filenames, [you might be fucked](https://eclecticlight.co/2017/04/06/apfs-is-currently-unusable-with-most-non-english-languages/). I could not sign my app with `codesign` through [electron-osx-sign](https://github.com/electron-userland/electron-osx-sign) for a few days before finding a solution.

The solution I've found, with the help of [Zhuo Lu,](https://github.com/sethlu) was to [get the name from the Finder and copy the special character from there to use it where needed in the code](https://github.com/electron-userland/electron-osx-sign/issues/155). Simply because I'm not that well versed in normalization matters, it was an easy enough way of fixing this annoyance once and for all.

* **Domain Name**

[Internationalized domain name](https://en.wikipedia.org/wiki/Internationalized_domain_name) are around for some time now. You'd think it should be well supported all around the internet... BOOM, wake up, it's not.

First, in most forms where you have to enter a domain name, you won't be able to use the special form `fenêt.re`, it will be rejected by the validation, instead you'll have to use the `xn--fent-ipa.re` form. So, developers, please update your validations so I can submit my website in its best form.

Second, now that it passes the form validation, it will be displayed either badly, without the special char like `fent.re`, or simply will be swapped back to the `xn--fent-ipa.re` form.

Third, it won't always be recognized to fetch open-graph data and you might not get this fancy card with your website's name/description/visual.

Don't think it's just small, underground platforms that don't support it yet. It happened on ProductHunt, Google Chrome WebStore, CloudFront, Twitter, Facebook, Slack, to name a few and it really doesn't help the internationalization of domain names.

* **Keyboards**

This one is just minimal, and nothing can be done for this, I think. But some keyboards make it very difficult to type special characters, especially the US one. That's why I also bought the `getfenet.re` domain.

Small tips on how to type special characters on a _US International — PC_ layout:

* `'` then `e` = `é`
* `` ` `` then `a` = `à`
* `Shift + 6` then `e` = `ê`
* `"` then `i` = `ï`
* `'` then `c` = `ç`

Of course, you can combine the accent with many other letters.

# Clipboard Watch

There is no event for the clipboard in Electron (Chromium), so you'll need to watch it yourself. And if you're using a `setInterval` for this, [you'll see it slowly dying with your inactive app](https://github.com/electron/electron/issues/4465).

That's where [`powerSaveBlocker`](https://github.com/electron/electron/blob/master/docs/api/power-save-blocker.md) enters.

{% gist https://gist.github.com/yoannmoinet/0d937d95395f926f81329938782ec22f %}

# Destroyed BrowserWindow

When manipulating or doing stuff with an opened `BrowserWindow`, be very careful that it's still alive, especially if it's asynchronous.

Or you'll get hit by an exception.

{% gist https://gist.github.com/yoannmoinet/ec76436a0bb911a5081710890b9ea2a8 %}
> Use this in every asynchronous piece of code.

# Transparent Windows

I wanted to implement a see-through feature, being able to keep the window in front, but the cursor would cut through it to reveal what's behind. And let the user click through it as well.

![demo](https://dev-to-uploads.s3.amazonaws.com/i/2k5v0k5os485nt43zp89.gif) 

It was even easier than what I first thought (_or I was just being an idiot_), it's actually just a combination of `BrowserWindow`'s configurations and some CSS sorcery 🧙‍️:

{% gist https://gist.github.com/yoannmoinet/1d1090ebb51136e6168eaa05dbe85e87 %}
> Main and Renderer process code for see-through feature.

# Using the app as a MacOS service

In my journey to make this app the most deeply integrated into the OS as possible, I wanted to have it registered as [a MacOS service](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/SysServices/Articles/overview.html#//apple_ref/doc/uid/20000850-97688).

Unfortunately, Electron's team doesn't find it [important enough to put it in the core](https://github.com/electron/electron/issues/8394) (yet?).

Which is a shame, or maybe, just not enough people care about it yet.

Next step will be to implement a native Node module I guess.

# Reducing package size

When shipping Electron with your app, you're getting a pretty huge deal of a package. Electron alone will add ~117MB to your package 🏋️‍♀️. So the more you'll remove, the better.

* **Webpack**

A good way to have a smaller sized bundle would be to have a build system. I've chosen Webpack, because I'm familiar with it. But any other would have worked of course. Grunt, Gulp or any basic concatenation of files (_if you're that barbaric_)...

Webpack lets you [target both `electron-main`](https://webpack.js.org/configuration/target/#string) [and `electron-renderer`](https://webpack.js.org/configuration/target/#string). This way, with only one `webpack.config.js` you can output both your main process and your renderer process.

Going deeper with webpack, you can declare globals thanks to the [`DefinePlugin`](https://webpack.js.org/plugins/define-plugin/) built in.

And, if you need to use absolute paths from within your app's package using node's `path` (for plugins for example), you should deactivate [webpack's `__dirname` mock](https://webpack.js.org/configuration/node/#node-__dirname).

Here's a simplified version of my webpack's configuration:

{% gist https://gist.github.com/yoannmoinet/a300a37aa61f4609c8a95626300b888e %}
> Snippet of my webpack config. Showing dual output, _dirname obfuscation and globals definition.

Then run `webpack --env.IS_PRO --env.IS_PROD --env.IS_PACKAGED` depending on which build you need to create.

Having those globals helped considerably keeping a single codebase with different codepaths:

* `IS_PACKAGED` : helped with the declaration of absolute paths. For plugins for example.
* `IS_PROD`: helped with adding debug points and debugger only in development.
* `IS_PRO`: helped with obfuscating pro features.

Closing tip. Register all your dependencies as a `devDependency` will help with the packaging. Using electron-packager it will completely discard your `node_modules` folder and only keep your bundled JavaScript when packaging your app, reducing the size considerably.

* **Languages**

![image](https://dev-to-uploads.s3.amazonaws.com/i/lijxl00qypr64nqwk349.png) 

Without cleaning, you'll end up having all languages listed on your app's page.

Electron adds a `.lproj` folder for each supported language, [for](https://github.com/electron/electron/pull/363) [reasons](https://github.com/electron-userland/electron-builder/issues/708). It will clutter your application's page on the Mac AppStore and will communicate wrong information about your app being internationalized in all these languages.

You can remove them yourself **after the packaging of your app**. To only keep the ones you support:

{% gist https://gist.github.com/yoannmoinet/6c161660889ea60d2ab7b561fa5c6945 %}

# Icons

When you iterate on your designs, you might need to update your icons quite a lot. And generating those can be a pain, since you need _many_size and format. Especially this `icon.icns` for which many apps can ask **up to 5$** to generate.

To ease this process, I've used this script coming from this awesome [SO answer](https://stackoverflow.com/a/20703594/488325):

{% gist https://gist.github.com/yoannmoinet/051d92d132218dc3ad801f74f6f1536c %}

> Here's the 5$ script.

Basically, just use it as `./icons.sh <input_file> <output_folder>`, it is important to note that your input file _must_be at least 1024px in both directions.

If you need to upscale it to a 1024px square, you can use [ImageMagick](https://www.imagemagick.org/script/index.php):

{% gist https://gist.github.com/yoannmoinet/bef541634b2880e2ea3c3fb2c8d98c4c %}

# Unsupported videos

Chromium only support a small set of video format. Mostly mp4 and its derivatives. So if a user wants to play an `.avi` video, it won't work, because it doesn't work in Chromium... bummer.

Since I'm just using a basic `<video>` tag to load all local videos, I'm stuck with this. Except... that's my app, and I can do whatever the fuck I want, if I want to support more video types, I will, try and stop me.

Fortunately for us, we can listen to errors on the video, and even luckier for us, we can target missing support errors:

{% gist https://gist.github.com/yoannmoinet/c3b862a7223880efe30d9967b9fc34e3 %}

From there, in [Fenêtre](https://fenêt.re), I'm sending a ping back to the main process saying that I can't support this video type. The local server will create a new route for this video file and decode it on the fly using [fluent-ffmpeg](https://github.com/fluent-ffmpeg/node-fluent-ffmpeg) and stream it back to the renderer process:

{% gist https://gist.github.com/yoannmoinet/97d9e3fe479fa3bf9132bff184443f03 %}

Finally, simply update your `<video>`'s `src` attribute with the newly created route.

The only down side is that you need to ship [`ffmpeg`](https://www.ffmpeg.org/download.html) with your app. And note that you have to compile it yourself with the ` --disable-securetransport` flag, otherwise it will be rejected by Apple since it's using the Security API that isn't available while sand-boxed.

[I was stuck at this point for a really long time](https://stackoverflow.com/questions/47629110/ffmpeg-gets-aborted-in-an-electron-sandboxed-application), since I couldn't compile a static executable of `ffmpeg`.

But the issue was that OSX kept dynamic libraries in `/usr/local/bin` which all take precedence over everything else. So even if you try to compile your `ffmpeg` statically, it won't work with these libraries on the way as they will be linked to your executable.

So you have to move all those `/usr/local/bin/*.dylib` somewhere else, compile the static executable, and TADAAaa... the build will work in the sandbox.

---

See? It wasn't all _that_ bad, you're still here, up and reading. How about we [Ship it](https://dev.to/yoannmoinet/electron-on-the-app-store-ship-it-4mf0) now?