Building a Web Client that can connect to an Electron Service

Phong Cao
3 min readAug 28, 2019

How we used WebRTC and a web browser to view remote content from an Electron service running on the cloud.

Cloud in hand
Cloud in hand — Photo by Samuel Zeller on Unsplash

In this post, I’ll share how we built a simple HTML page that can connect to the Electron service mentioned in Streaming Electron for Fun and Profit post. If you haven’t read that one yet, please spend some time reading it first.

Overview

Web Client Screenshot (aka stream consumer)

Our stream consumer consists of an HTML page and several source files written in TypeScript. The building system compiles them into JavaScript files and after that bundle them into one single file using browserify. That file is loaded with the HTML page and handles all WebRTC activities.

Here’s the NPM run build command in package.json file:

tsc -p . && browserify dist/stream-consumer/src/main.js -o dist/stream-consumer/src/bundle.js

Before diving into the code, you can check out our full sample, which is called browserd, on GitHub.

Here are some of the main technologies and components that we use:

Implementation Details

Connection flowchart

There are three main steps to connect to the Electron service running on the cloud (aka stream provider):

  • Sign in to signaling server. The stream consumer tries to connect to the signaling server. If the connection is successful, it’ll receive the assigned id and a list of available peers, including itself and the stream provider. If you’re not familiar with signaling, there is a good tutorial on tutorialspoint.

In the following code snippet, we assume that there is only one stream provider and one stream consumer:

  // Generate a random uuid for peer name
const peerName = uuid();

// Get stream provider id
let streamProviderId;
const peers = await signalProvider.signIn(peerName);
const streamConsumerId = signalProvider.id;
peers.forEach((peer: ISignalPeer) => {
if (peer.id !== streamConsumerId) {
streamProviderId = peer.id;
}
});
  • Initialize peer connection: The stream consumer tries to connect to the stream provider. There are several steps involved in this process:
    1. The stream consumer creates an offer and send to the stream provider.
    2. The stream provider creates an answer and reply to the stream consumer.
    3. Both peers start exchanging ICE candidates.
    4. If the negotiation is successful, a connection is established between both peers and the stream consumer receives the remote stream.
  const peer = new SimplePeer({
config: {
iceServers,
},
initiator: true,
trickle: false,
});
peer.on("signal", async (data) => {
await signalProvider.send(JSON.stringify(data), providerId);
});
peer.on("stream", (rstream: MediaStream) => {
startStreaming(rstream, peer);
});
  • Render remote content: This step is quite simple. Once we have the remote stream, we can render it using HTMLVideoElement. Additionally, we can listen for mouse events and send them to the stream provider for processing via data channel.
  // Play video
const videoElement = $("#remote-video") as any as HTMLVideoElement[];
videoElement[0].srcObject = rstream;
videoElement[0].play();

// Input handling
const inputMonitor = new InputMonitor(videoElement[0]);
const sendInputToPeer = (data: any) => {
peer.send(JSON.stringify(data));
};
inputMonitor.on(HtmlInputEvents.Mouseup, sendInputToPeer);
inputMonitor.on(HtmlInputEvents.Mousedown, sendInputToPeer);

I hope you’ve enjoyed this post. Feel free to leave any questions or feedback if you have and you’re welcome to contribute to our sample on GitHub.

Resources

--

--