Today we’ll take a look at how to build a WebRTC application using Electron. To make things simple, we’ll just use the bare APIs to build our own minimal signaling server using socket.io.

The application

The idea is to use Electron to open our client files and Express to serve our signaling server. In a production-like environment, the server would be available for multiple instances of the application running connect to it. For this demo, we’ll simply open multiple Electron windows because we’ll be running everything locally.

To begin, open a Terminal application and clone our webrtc-video-conference tutorial by running the following command. Make sure git and node are installed in your computer. Here is the git clone.

The structure of the project looks like this:

We have a server.js file that hosts the signaling server (powered by socket.io) and serves the content of the public folder. Inside it, we have the client.js that does all the WebRTC magic to get the local media and connect to the signaling server and then to the other peer. A finished version of the app is available in aforementioned repo in the electron branch.

Adding Electron

The next thing we’ll do is add Electron to our application. We will base on the electron-quick-start repo. Let’s start by installing the Electron module:

npm install -S electron

Next, let’s add the main.js file in the root folder of our project:

// main.js
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow

function createWindow () {
  // Create the browser window.
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // and load the index.html of the app.
  mainWindow.loadFile('public/index.html')

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') app.quit()
})

app.on('activate', function () {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) createWindow()
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

Since we will be opening client files from Electron, we don’t need Express serving them anymore. So we can comment the static hosting line from server.js:

// server.js
...
const port = process.env.PORT || 3000;

// express routing
// app.use(express.static('public'));


// signaling
io.on('connection', function (socket) {
..

After that, let’s create the preload.js file in the root of our project folder:

// preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
      const element = document.getElementById(selector)
      if (element) element.innerText = text
    } 
    
    for (const type of ['chrome', 'node', 'electron']) {
      replaceText(`${type}-version`, process.versions[type])
    }
  })

Now we need a way to run our application using Electron. Let’s add an electron script in package.json:

package.json

...
"scripts": {
    ...
    "electron": "electron main.js"
  },
...

And run our Electron app by typing:

npm run electron

We are now able to see our application running, but we still need to make some changes for it to work.

Make client files connect to backend

Our signaling server is served by Express, so we need to make sure our Electron application connects to it.

First, let’s change the route to the socket.io.js file in the index.html file:

<!-- public/index.html -->
...
<body>
    ...

    <script src="http://localhost:3000/socket.io/socket.io.js"></script>
    <script src="./client.js"></script>
</body>
...

For this demo, we’ll run the app locally. However, in a production-like environment, it should point to an actual backend hosted on the internet.

Let’s also change this value in the client.js file:

// public/client.js
...
var isCaller;

// Let's do this
var socket = io('http://localhost:3000');

btnGoRoom.onclick = function () {
...

We should now be able to execute the application. Open three terminal windows and run the following commands.

On Terminal 1:

npm start

On Terminal 2 and 3:

npm run electron

You should be able to establish the call by setting the same room number in both Electron windows.

Running it all at once

To avoid having to open multiple terminal windows, let’s use concurrently to run everything at once. Install concurrently:

npm install -S concurrently

And edit the package.json scripts:

package.json

...
"scripts": {
    ...
    "start": "concurrently \"npm run server\" \"npm run electron\" \"npm run electron\"",
    "server": "node server.js",
    "electron": "electron main.js"
  },
...

Now simply run:

npm start

Conclusion

When using Electron, having a WebRTC desktop application that runs on multiple platforms is very straightforward. That’s because Electron bundles everything inside the same package, so you can make sure all your users will get a similar experience.

Recent Blog Posts