Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Electron Workshop

Introduction

Workshop Description

Electron is a framework you can use to make desktop applications using javascript. You may have already used an Electron app if you have the Slack app or the text editor Atom. With electron, making a simple desktop application is as easy as making a simple web application. Learn to make your own Electron app in this 2-3 hour workshop :D

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.

No node experience required, but basic javascript experience would be helpful. If you want to play with Node ahead of time anyway (or brush up on your Javascript), I'd recommend NodeSchool's two command-line courses: Javascripting and LearnYouNode. If you'd enjoy reading/skimming a book, I might recommend Eloquent Javascript (free to read online).

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:

  1. You could tell at a glance (without an additional terminal window) whether your ping is currently good or bad
  2. You could get a notification when your wifi goes out, or comes back

Our Project

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:

  1. Use the tray api to change the app’s icon to reflect the current state
  2. 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:

Resource Intensity

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

Browser Notifications

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)"

Bonus

  • 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.

Project Setup

  • Create A New Repo. Suggested:
    • Name: electron-ping
    • Public
    • Initialize with README - yes
    • gitignore: node
    • license: MIT License
  • Clone your newly created repo (git clone REPO_URL)
  • (commit changes along the way)
  • start an npm project and add the electron npm package
    • npm init to create a package.json (used for npm packages AND electron uses this too )
      • all defaults are okay
    • to add the npm package electron to this app, npm install --save-dev electron
      • (the two options are --save and --save-dev)
      • open package.json and observe that it is there :)
      • open ./node_modules folder and observe that it is there, too (along with potentially many other things)
    • dependencies vs devDependencies
      • For example, you may not need a linter's code to be shipped to users - you'll only use it in "development".
      • the electron package 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 :) )

Optional/Recommended Steps

  • For a linter I recommend “standard”:
    • npm install --save-dev standard
    • & install the "standard linter" plugin for your text editor (jshint/eslint not required) (atom) VSCode
  • commit these changes (and all other changes along the way)
  • Additional style challenge: Don't use anonymous functions. All functions should have a name.

Tray Icon and Menu

Get the electron app to launch by having it put an icon in the menubar

A. Starting the App

  • Add a start command to the scripts: {} in package.json - so you'll be able to type npm start to run the app
    • "start": "node_modules/.bin/electron .",
  • Import a couple lines of boilerplate code from the electron docs:
  • try to run the app using npm start! It will fail - until we set the app icon (next section).
Uncaught Exception:
TypeError: Error processing argument at index 0, conversion failure from /path/to/my/icon

B. Tray Icon

According to the error we just got, we'll need to point it to an icon.

  • Download the icons from the bottom of this gist (this one and this one), and put them in the root folder of your repo.
  • In index.js, change path/to/my/icon to tray_icon_black.png
  • Goal: run the app using npm start! (it should succeed now! congrats!)
  • any time you make changes to the app, you will need to stop and start this process npm start again
  • reminder: are you making tiny commits? :)

C. Menu Item: Native Quit Button

  • Goal: Add a quit menu item.
    • You will want to use a role menu item (see the MenuItem docs above).
    • this will be an option when you click on the tray icon AND it should let you use cmd+q / ctrl+q

D. Menu Item: Most Basic

  • Goal: Add a menu item that executes this code: console.log('clicked the button') (reference the MenuItem docs)
    • When that menu item is clicked, you should see that text in the terminal running npm start

E. Menu Item: Change Icon

  • Goal: Make a button that changes the app icon on click to the purple one. (Refer to the Tray docs page for how to set the image to something else.)
  • Style tip: extract the click action function and pass it in (not an anonymous function).

Bonus

If you are ahead, try these:

  • Make this a menubar-only app (no app icon in dock or cmd+tab sequence)
    • OSX: hide the "dock icon"
    • Windows: I think it has no "dock icon" by default?
    • Bonus: to make this compatible on the other operating system too (detect current OS and conditionally hide the icon~)
  • Make your own icon
    • recommend a 16x16 icon
    • Start by getting a simple circle working
    • Then make it more elaborate.
    • (or find nice, free icons online)
  • Icon hover/click states
    • OSX: click state icon should probably be white, like many other apps
  • Make the icon-toggle menu item toggle back and forth between the two icons

Notifications from Electron

The 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 ipcMain and ipcRenderer directly.

  • npm install --save-dev electron-main-notification
  • Try the basic example from their README.md: https://github.com/MaxGfeller/electron-main-notification
  • Goal: Try making your app send a notification "hi!" every 1 second.
    • You may google how to make something happen every [amount of time] in javascript

Bonus

If you are ahead, try these:

  • make the notification not make any noise
  • get an icon to appear in the notification
    • you can use our tray icons from earlier for this
    • You'll want to use an absolute path for these. Use the two Node ~filesystem tools: __dirname and path.join()

Post-workshop Bonus

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 ipcMain and ipcRenderer!

Challenge:

  • Get this working without using electron-main-notification
  • Get the notification to close itself after being open for just 500ms

Ping

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 node-ping.

  • npm install --save-dev node-ping
  • skim https://github.com/danielzzz/node-ping
  • try a simple version, using the promise interface ping.promise.probe()
  • Goal: Every 1 second, ping google.com and display the latency in 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

Bonus

  • 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 500ms too.

Packaging The App

Goal: Package your application for your operating system, and use it to install this package on your own computer.

The tool 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 node_modules/.bin/electron-builder
  • Add dist/ to your .gitignore
  • Open 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 google.com)
    • Configure the interval time (currently hard-coded to 1000ms)

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

Slow ping

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-dev in 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?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment