Building electron app
13 Sep 2016All posts from the series can be found here
I used Radiant player for a long time and didn’t have any complaints till the moment most of Google Music interface stopped working well with the app. I checked bugtracker multiple times and no one raised similar issues. Feeling myself weak in Objective C land I didn’t have any desire to go and fix things myself and, as a consequence found myself opening GM app in the browser.
For some reason I really don’t like having apps openned in the background tabs. First of all it makes restarting browser a pain, second multimedia keys are not supported. So, after a while I cloned electron quickstart repository and started launching google music there and few days ago finally found the time to make a complete app - Perlotto. It’s called like this just because this word sounds amazing and I wanted to name something with it.
Overall setup took less time than I expected although I had solve few common issues, and solutions will be bellow.
Packaging
Somce it’s a real app it should be packaged as a real app, I have no desire to run npm start
every morning to start my music player. The task is really simple with electron-builder,
although it require a bit more effort to get app packaged automatically, since why would anyone
want to package app manuall all the time, right?
Building part is explained in electron builder readme.
Here is publishing part (I care only about Mac OS X at the moment). Travis CI can be used to automate packaging and publish attefact on github releases page.
My .travis.yml
osx_image: xcode7.1
os:
- osx
language: node_js
node_js:
- 6.1.0
script: npm run dist
cache:
directories:
- node_modules
- $HOME/.electron
- "test/fixtures/app-executable-deps/app/node_modules"
deploy:
provider: releases
skip_cleanup: true
file_glob: true
file: dist/mac/Perlotto-*.dmg
api_key: $GH_TOKEN
on:
tags: true
repo: can3p/perlotto
Few things there:
- Specifying osx_image alone didn’t helpe to make travis run build on OS X, I had to add os section.
- I specified version 6.1.0 for node, however something like 5.8.0 was used. Doesn’t really matter for me.
- script is the same command that is used to build package locally
- cache section is not necessary but helps to speed up process
- Builder produces image with name like Perlotto-0.1.dmg, so I used file_glob to workaround specifying precise filename
- api_key is a must however I did not want to put key on github, so I ended up specifying env variable and passing it there (it’s possible to specify env vars on settings section for the project)
- on section specifies that I want to make build for tags only, however travis still makes builds for every commit. electron-builder is smart though and publishes artifacts only if it’s a real release
- electron builder takes version from package.json file, but github cares about tags only, so I have to keep them sync myself.
I used travis
cli tool to generate some parts of the file and stole others from the internet,
however it’s absolutely possible to do everything without the tool via web interface.
So, this is how release process looks now:
- Update package.json version and push
- Go to github releases page and publish new version (same as in package json). One thing to keep in mind that electron-builder wants versions to be like x.y.z and tags like vx.y.z, an exception is thrown in other cases.
Closing the window
Default behaviour of electron quick start app is to exit application when user clicks close button. I’m not aware of any serious player that behaves like this hence this misbehaviour has to be fixed.
First thought is of course to prevent this on window close event, but the result is that it’s not possible to close app in any way other process manager.
So, trick there is to raise flag on app ‘before-quit’ event and check it on window ‘close’ event:
let forceQuiteApp;
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow.maximize();
// and load the index.html of the app.
mainWindow.loadURL('file://' + __dirname + '/index.html');
mainWindow.on('close', function(e){
if (!forceQuiteApp) {
e.preventDefault();
mainWindow.hide();
}
});
})
}
app.on('ready', createWindow)
app.on('before-quit', () => forceQuiteApp = true);
I run maximize method right after the start because I don’t want to worry about saving window dimensions anywhere
Media keys
This one is simple, the only moment is that keys should be bound with globalShortcut
object
available from electron module on app thread and actual dom manipulation happens in content thread.
Here is relevant app code part:
function createWindow () {
// ... init part ...
globalShortcut.register('MediaPlayPause', function(){
mainWindow.webContents.send('play-control', 'play-pause');
}) || console.log('MediaPlayPause binding failed');
globalShortcut.register('MediaPreviousTrack', function(){
mainWindow.webContents.send('play-control', 'rewind');
}) || console.log('MediaPreviousTrack binding failed');
globalShortcut.register('MediaNextTrack', function(){
mainWindow.webContents.send('play-control', 'forward');
}) || console.log('MediaNextTrack binding failed');
}
We use event api to transfer knowledge from app to thread, here is content part:
var ipc = require('electron').ipcRenderer;
ipc.on('play-control', function(event, command){
var webView = document.querySelector('webview#gpm-player');
webView.executeJavaScript("document.querySelector('#player-bar-" + command + "').click()");
});
Since script has to be embedded into content, local file is needed, hence here is index.html
<!DOCTYPE html>
<html>
<head>
<title>Perlotto</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body style="overflow: hidden">
<webview id="gpm-player" src="https://play.google.com/music/" style="height:100%;width:100%;position:absolute;"></webview>
<script src="./content.js"></script>
</body>
</html>
Margins had to be reset, otherwise webview is rendered with some margins.
Easy.
Conclusion
It’s funny, but this post contains 99% of project’s code. I treat project feature complete since I don’t care about notifications, tray icons and any other garbage that is useful only for distractions.
Electron ecosystem is surprisingly feature complete and stable for me, thanks guys!