🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.

Extensible API for in-app chat


Build scalable realtime features


Programmatic push notifications



Read the docs to learn how to use our products


Explore our tutorials to build apps with Pusher products


Reach out to our support team for help and advice

Sign in
Sign up

Building a video call and chat app - Part 3: Packaging the app

  • Wern Ancheta

March 20th, 2019
This tutorial uses Node, React and Electron.

In this part, we’ll update the app for production and package it for all three major operating systems: Windows, Mac OS, and Linux.

This is the last part of a three-part series on creating a video call and chat app with Simple Peer and Chatkit.


Knowledge of React is required.

Reading part one and two of this series is optional if you only want to learn how to prepare an Electron app for packaging.

In order to package the app for several platforms, you need to have Windows, Mac, and Linux operating systems. It’s okay if you don’t have all three of these, you can still package the app for the operating system that you’re using.

Lastly, this tutorial requires a Zeit.co account and a Twilio account.

Project setup

If you’re following this tutorial without having read the first two parts, you can set it up by cloning the repo and switching to the part2 branch:

    git clone git@github.com:anchetaWern/ElectronVideoChat.git
    cd ElectronVideoChat
    git checkout part2

Once that’s done, we just have one little update to make to the login page. In production build, the below code no longer works:

    if (response.statusText === "OK ") {
      // ...

So we need to replace it with the following:

    // src/screens/Login.js
    if (response.status === 200) {
      // ...
    // the rest of the previous code...

Deploying the server

The first thing that we need to do is deploy the server. The simplest way to do that is via the Now service by Zeit.co. Go ahead and sign up for an account if you don’t have one already.

Once you’re logged in, you can view your projects from the dashboard. If you haven’t previously used Zeit, there will be no projects there. Let’s go ahead and add one.

Start by installing the now CLI. We’ll use it to deploy the server:

    npm install -g now

Once that’s installed, add your Pusher Channels and Chatkit credentials as a secret. These secret values can then be used as an environment variable (the same as what we’ve used so far):

    now secret add electron_videochat_channels_app_id YOUR_PUSHER_APP_ID
    now secret add electron_videochat_channels_app_key YOUR PUSHER_APP_KEY
    now secret add electron_videochat_channels_secret YOUR_PUSHER_APP_SECRET
    now secret add electron_videochat_channels_app_cluster YOUR_PUSHER_APP_CLUSTER

    now secret add electron_videochat_chatkit_instance_id YOUR_CHATKIT_INSTANCE_LOCATOR_ID
    now secret add electron_videochat_chatkit_secret YOUR_CHATKIT_SECRET

Note that the values don’t have to be wrapped in double quotes. Also, omit the v1:us1: prefix from the Chatkit instance locator ID.

Next, navigate to the server directory of the project and execute the following:

    now -e APP_ID=@electron_videochat_channels_app_id -e APP_KEY=@electron_videochat_channels_app_key -e APP_SECRET=@electron_videochat_channels_secret -e APP_CLUSTER=@electron_videochat_channels_app_cluster -e CHATKIT_INSTANCE_ID=@electron_videochat_chatkit_instance_id -e CHATKIT_SECRET_KEY=@electron_videochat_chatkit_secret

The command above assigns the secret values as an environment variable for the project that’s being deployed. It uses the following format:


Once the project is deployed, you’ll see a screen similar to the following:

The URL it returns is the one you need to put in place of the ngrok HTTPS URL that we’ve been using:

    // src/screens/Login.js and src/screens/GroupChat.js
    const BASE_URL = "YOUR NOW.SH URL";

You can try accessing the URL in the browser to see if it’s working. It should return “All is well.” since that’s what we have in the root route.

Using a custom STUN/TURN server

By default, the Simple Peer library uses Google and Twilio’s STUN servers. This is set by default on their config:

    config: { 
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' }, 
        { urls: 'stun:global.stun.twilio.com:3478?transport=udp' }

This works most of the time. But there are cases where the public IP address of the user is located behind a NAT or firewall. If it is, and the firewall doesn’t allow the two users to connect directly, then a peer-to-peer connection simply isn’t possible. It is the job of the TURN server to relay data (in our case, it’s the video stream) between the two users. If you want to learn more about this process, be sure to check out Twilio’s FAQ on what STUN, TURN, and ICE are.

Simple Peer only uses a TURN server when needed. The two servers it uses are free to use. But for production apps, you don’t really want to rely on a free server because of practical reasons (for example, it might go down and you don’t have control over it).

For this tutorial, we will be using Twilio’s paid Network Traversal Service. Create a Twilio account if you don’t have one already.

Once you’re logged in to Twilio, visit your Console. By default, your account SID and auth token will be displayed. Replace the values in the command below using those credentials and execute it in the command line:

    curl -X POST https://api.twilio.com/2010-04-01/Accounts/YOUR_ACCOUNT_SID/Tokens.json \

That will return a response similar to the following. Note that the first item in the ice_servers property is Twilio’s freely available Global STUN server. And below it is the TURN server’s which requires authentication:

       "date_created":"Wed, 06 Feb 2019 06:00:40 +0000",
       "date_updated":"Wed, 06 Feb 2019 06:00:40 +0000"

Copy those values over to your src/screens/GroupChat.js file:

    _connectToPeer = (username, stream = false) => {
      const peer_options = {
        initiator: this.is_initiator,
        trickle: false,

        // add these:
        config: {
          iceServers: [
            { urls: "stun:stun.l.google.com:19302" },
            { urls: "stun:global.stun.twilio.com:3478?transport=udp" },
              urls: "turn:global.turn.twilio.com:3478?transport=udp",
              username: "xxxx_username",
              credential: "xxxx_cred"
              urls: "turn:global.turn.twilio.com:3478?transport=tcp",
              username: "xxxx_username",
              credential: "xxxx_cred"
              urls: "turn:global.turn.twilio.com:443?transport=tcp",
              username: "xxxx_username",
              credential: "xxxx_cred"

      if (stream) {
        // ...

      // the rest of the existing code...

Note that we’re still using the default STUN servers used by Simple Peer. This is because Twilio’s service is paid. If you want to avoid incurring lots of fees, you should leverage the free services as much as possible. Especially if your app is free. The ones on the top will be used by Simple Peer by default, so the paid servers only get used if there’s an actual need.

There’s also another free service that you can use here.

Another option is to run your own server. You can find instructions on how to do that here. You can then test if it’s working by using this service. All you have to do is enter the URI or IP address of your server along with the credentials and then click on Add Server. Once it’s added to the list, select it and click on the Gather candidates button below. If the output looks similar to the one in the screenshot below, then your server is doing its job correctly.

Alternatively, you can set the following option to force the peer to only use TURN to test if your server is working correctly:

    config: {
      iceServers: [ ... ],
      iceTransportPolicy: 'relay' // add this

If the peer-to-peer connection isn’t established, it means that your server doesn’t work.

Preparing the app for packaging

We need to make a few changes to the app before we can package it. First, import the following packages. These are already internally used by Electron so we don’t need to install them separately:

    // src/starter.js
    const path = require("path");
    const url = require("url");

We use the above packages for formatting the URL in which the build version of the project is located. We’ll generate this file later in this section:

    const startUrl = process.env.ELECTRON_START_URL || url.format({
      pathname: path.join(__dirname, "/../build/index.html"),
      protocol: "file:",
      slashes: true


Next, scroll down until you see the code for opening the developer tools. We don’t really need to show that in production so we can set to only show it when in the development environment:

    if (process.env.ELECTRON_START_URL) {

Next, update your package.json file to include the homepage property. Set its value to the root project directory. We need this because, by default, create-react-app builds an index.html which uses absolute paths. Electron won’t understand this so it will fail. Setting the homepage allows us to use relative paths instead:

      "name": "electron-videochat",
      "homepage": "./", // add this

Now we’re ready to generate the build version of the project:

    yarn run build

Here’s what it will look like once the build is complete:

By now, you should be able to run the app using the build version:

    yarn run electron

It’s important to test things out first to see if they’re still working before we move to the next section.

Packaging the app

Now we’re ready to actually package the app so it can be executed on all three major operating systems: Windows, Mac, and Linux. We will be using Electron Packager to package the app.

The first step is to update your package.json file to include a productName property. This will be used as the name of the app:

      "productName": "Electron Video Chat"

For the rest of this section, you can skip to the instructions for the OS that you’re currently using. But I recommend you to read what each option does under the Packaging for Mac section below. You can also read about all the available options here.

Packaging for Mac

Next, execute the following at the root directory of the project:

    electron-packager . --overwrite --platform=darwin --arch=x64 --icon=assets/icons/mac/icon.icns --prune=true --out=release-builds

This will look for a .icns file inside the assets/icons/mac folder. You can download the one we have on the repo or find an icon using iconfinder. Be sure to only use those that are labeled “free for commercial use”. The one I used for this project is also labeled as such.

Here’s a brief description of what each option does. We will be using most of these options for Windows and Linux as well:

  • overwrite - overwrites whatever files are in the output directory.
  • platform - the platform to build for.
  • arch - the architecture of the platform. This can be either 32-bit (x86) or 64-bit (x64). But since most modern computers are now 64-bit, that’s what we’ve used.
  • icon - the path to the icon file. The file format differs depending on the platform. For Mac, it’s .icns, for Windows, it’s .ico, and for Linux, it’s .png.
  • prune - for removing unnecessary node modules from the app before packaging.
  • out - the name of the output folder. This may or may not already exist.

Once it has done doing its thing, the app will be available in the following directory:

    release-builds/Electron Video Chat-darwin-x64/Electron Video Chat.app

At this point, you can now launch it like your usual apps.

Packaging for Linux

For Linux, execute the following command:

    electron-packager . electron-videochat --overwrite --platform=linux --arch=x64 --icon=assets/icons/linux/icon.png --prune=true --out=release-builds

This will generate the executable app on the following path:


You can launch it just like any executable:


Packaging for Windows

For Windows, execute the following command. It’s best to use Git Bash to avoid any problems that comes with the Windows Command Line:

    electron-packager . electron-videochat --overwrite --asar=true --platform=win32 --arch=ia32 --icon=assets/icons/win/icon.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName="Electron Video Chat"

The executable file will be generated in the following directory:



In this tutorial, you learned how to use a custom TURN server, and package an Electron app.

That also wraps up the entire series. In this series, we created a video call and chat app from scratch using Electron, React, Simple Peer, and Chatkit. We used ngrok as a development server and Zeit’s Now for production. We also used Twilio’s Network Traversal Service in case the free TURN servers we are using doesn’t work. Lastly, we used Electron Packager to package the app for multiple platforms.

You can find the code used in this entire series on its GitHub repo.

Clone the project repository
  • Chat
  • JavaScript
  • Node.js
  • React
  • Chatkit
  • Channels


  • Channels
  • Chatkit
  • Beams

© 2020 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.