Short URL: caseywatts.com/electronworkshop
Electron provides a set of cross-platform APIs you can use to interact with the desktop computer. You can use these to have an icon in the tray/menubar, to display notifications, register keyboard shortcuts, and much more. An Electron application has a node “server” running locally doing most of the work, and often additionally a browser process running locally to display things prettily.
Scenario / User Story
You work remotely. When pair programming or video chatting online, sometimes the connection gets bad.
You're never sure if it's your side or their side having issues.
Sometimes you smartly have a terminal open and have
ping google.com running - then you can tell whether your connection is good!
You'd love it if:
- You could tell at a glance (without an additional terminal window) whether your ping is currently good or bad
- You could get a notification when your wifi goes out, or comes back
In this workshop, you will create from start to finish a tray icon that will display whether or not you are currently connected to the internet. A node process inside of the Electron app will ping google.com every second. We will then:
- Use the tray api to change the app’s icon to reflect the current state
- Display a notification when the internet appears to go up or down
You can see two example applications like this, here:
And an unrelated application that I've written:
An electron menubar app uses RAM and CPU resources comparable to other menubar apps.
Casey's electron app
electron-ping uses this amount of CPU and RAM to run:
- 0.9% CPU, 70.6 MB of RAM
Spot-checks of some other menubar apps running in the background:
- Dropbox 0.3% CPU, 165MB
- Screenhero 0.2% CPU, 124MB
- Google Drive 0.3% CPU, 151MB
We’ll use the HTML5 browser notification API
Goal: Make a notification that says "ping!".
- Use a normal Chrome window. Do NOT use an incognito window for this - notifications are always blocked in that environment.
- Open the developer console on any web page
- Requst permission, then make the notification trigger.
Notification.requestPermission()should return a Promise (which we'll ignore), and display a request for notifications permission dialog (which you should accept)
- then doing
new Notification('yay')should display the notification
- An Electron app will not need to ask for permissions, it has them granted automatically.
- If you want to un-set the permission, you can do so by clicking the lock icon or (i) icon to the left of the url, and setting "Notification" back to "Ask (default)"
- Try controlling whether or not it makes a sound
- (some people report that it doesn't ever make a sound for them somehow? I want to know more about this...)
- Try displaying an icon (use an image url from anywhere online just to get going quickly)
- Some websites (like gist.github.com) have CORS enabled and won't use external images. The mozilla.org com page above doesn't have CORS enabled.
- Create A New Repo. Suggested:
- Initialize with README - yes
- Clone your newly created repo (
git clone REPO_URL)
- (commit changes along the way)
- start an npm project and add the
npm initto create a
package.json(used for npm packages AND electron uses this too
- all defaults are okay
- to add the npm package
electronto this app,
npm install --save-dev electron
- (the two options are
package.jsonand observe that it is there :)
./node_modulesfolder and observe that it is there, too (along with potentially many other things)
- (the two options are
- For example, you may not need a linter's code to be shipped to users - you'll only use it in "development".
electronpackage contains more code than what's needed for users to run the app in "production" (it contains code for multiple operating systems, some additional logging code, etc)
(If you want to do
git init locally and want to push it up to github after that's okay, I just don't have instructions for that :) )
- For a linter I recommend “standard”:
- commit these changes (and all other changes along the way)
stylechallenge: Don't use anonymous functions. All functions should have a name.
Notifications from Electron
index.js (node) file in Electron can't make notifications itself, but it can make them using a browser process. We’ll need to make a (secret, hidden) browser window in order to use the HTML5 Web Notification we practiced earlier. (thinker: why can’t node do notifications directly?)
Communication can happen between our main.js node process and browser windows using the electron tools ipcMain and ipcRenderer. Sidenote: a very similar communication protocol is how Chrome Extensions communicate between processes.
For now, let's let a 3rd party npm package to take care of creating a hidden browser window and sending notifications to it. If we have time later, let's look at its source code and try to use
npm install --save-dev electron-main-notification
- Try the basic example from their
- Goal: Try making your app send a notification "hi!" every 1 second.
If you are ahead, try these:
- make the notification not make any noise
- get an icon to appear in the notification
By default, browser notifications close after a few seconds. What if we want it to close after only 1 second? We could do that in the browser process using
notificationInstance.close(), but unfortunately
electron-main-notification doesn't give us a way to call that from the main thread
index.js. But you could do it yourself with
- Get this working without using
- Get the notification to close itself after being open for just
We want to ping google.com periodically to determine whether our internet is currently working. The system ping utility is great for this, we just need a way to interface with it in node, so we'll use the npm package
npm install --save-dev node-ping
- skim https://github.com/danielzzz/node-ping
- try a simple version, using the promise interface
- Yay promises! For now, just go with it. Later you might want to read more about promises in general
console.log()at first, then try our
- Goal: Every
google.comand display the
latencyin a notification
- this is just a sanity check that our ping is actually happening every second
- since this is too much information for the user, let's turn this part off. We'll make better notifications in our next step.
- Goal: every time wifi state changes (goes down or comes back on), display a notification about the change
- we can do this based on the [last-seen ping's alive/dead state] (which we'll have to store somewhere)
- test this by turning your wifi off and on
- Style challenge: use named functions instead of anonymous functions
- Change the tray icon based on the most recent ping response (whether it was alive/dead)
- If/when you do the bonus challenge from earlier where the notification goes away after
500ms, then change the ping's timeout to be
Packaging The App
Goal: Package your application for your operating system, and use it to install this package on your own computer.
electron-builder will automatically create the most common 3 builds for you (OSX, Windows, Linux)
- Install electron-builder
npm install --save-dev electron-builder
- Run it using
dist/folder, and install your packaged app to your computer
More App Improvement Ideas
Now that you've completed the workshop, here are some additional things you could do to this application to make it even more useful:
Config options page
- In a browser window
- Configure the server we ping (currently hard-coded to
- Configure the interval time (currently hard-coded to
- Configure the server we ping (currently hard-coded to
Display ping data
- Display ping data in a browser window (like the Dropbox client's window)
- Summary Data
- % of pings dropped in the past 60 seconds
- average latency in the past 60 seconds
“Shaky Internet” state
- If we’ve dropped any number of pings recently but we’re back up now, display a different icon than our success/failure icons
- still - if the most recent ping was a fail, then display fail
What will happen to our app if ping is >1000ms (or what was set in the configuration)?
- Can that happen?
- Would ping notifications potentially come in out of order?
- Does our use of setTimeout give us any issues?
- what can we do to avoid issues?
Slow pings are bad too
Pings of over 300ms are pretty bad - count them as if they’re dropped
Custom App Icon
For the Applications folder and for icons in the Notification (alive ping / dead ping)
Your own ideas
Of course, feel free to do any of your own modifications too. Comment below if you think others might be interested in those ideas, too!
Outstanding Questions from Casey
- quit role button’s title is ugly because the name of the project is dasherized "electron-ping" not title case "Electron Ping". Can it be title case here somehow?
- Should everything be
--save-devin electron, since we always compile it anyway? (I think so, but I'd love to be more certain)
- Should I continue to recommend absolute paths in context like I did - or wait until the error in post-packaging to do it, and then it'll be more motivated?