Full Duplex Communication Between Web App and Desktop Application Using WebSocket

mark0960

Ahmed Hesham

Posted on May 10, 2022

Full Duplex Communication Between Web App and Desktop Application Using WebSocket

Introduction

One of the limitations of web apps is hardware access. If you use some special hardware, communication might be difficult if not impossible.
In this case, the client will need to install a software to handle the communication between the web app and the hardware.

An example of this is when you get a new laptop and then visit the manufacturer website to check for driver updates. Most often, you will be asked to install a software that will check your laptop; then, the results will be displayed on the website.

In our case, this software acts as a WebSocket server, and the browser is the WebSocket client that initiates the session.

WebSocket communication between frontend and desktop

Depending on your design, the software might also send HTTP requests to the backend server.

Do You Really Need It?

This design should be used only when necessary; we are adding a component to our system which increases the complexity, and the users need to install a software to use our web app which raises the question whether we should use a web app in the first place.

This post covers most JavaScript APIs for communicating with hardware devices, check them first before attempting the WebSocket approach.

Overview

We will create a simple web page that will connect to a WebSocket server running on the client Windows machine. When a session is open, we can exchange messages between the web page and the server.

The WebSocket client is implemented with WebSocket API.
The WebSocket server is a console application written in .Net Framework, and we use SuperSocket for the WebSocket server implementation.

WebSocket Client

We make an input to write messages, and a div to display received messages.

<input id="input" type="text">
<div id="log"></div>
Enter fullscreen mode Exit fullscreen mode

This is a simple function that creates the WebSocket client instance, and assigns event listeners.

function createWebSocket() {
    const webSocket = new WebSocket('ws://127.0.0.1:85'); // In the constructor we use the loopback address and a free port

    webSocket.onopen = () => {
        webSocket.send('Connected');

        const input = document.getElementById('input');
        input.onkeydown = e => {
            if (e.code === 'Enter') {
                webSocket.send(input.value);
                input.value = null;
            }
        }
    }

    webSocket.onmessage = msg => {
        document.getElementById('log').innerHTML += `Server: ${msg.data} <br/>`;
    }

    return webSocket;
}
Enter fullscreen mode Exit fullscreen mode

In the following code:

  • We keep attempting to connect to the WebSocket server.
  • There is no way to reconnect to the server when a session is closed, instead we create a new client instance.
  • A session gets closed for several reasons, but here it happens because the server is unreachable, most likely because the server hasn't started yet.
window.onload = async () => {
    var webSocket = createWebSocket();

    while (webSocket.readyState !== webSocket.OPEN) {
        await new Promise(res => setTimeout(res, 1000)); // wait 1 second

        if (webSocket.readyState === webSocket.CLOSED)
            webSocket = createWebSocket();
    }
}
Enter fullscreen mode Exit fullscreen mode

WebSocket Server

  1. Create a Console Application
  2. Install both SuperSocket.Engine and SuperSocket.WebSocket; you can get them from NuGet.
  3. Create a server instance and add event listeners. Notice how similar it is to the client (JavaScript) code.
static void Main()
{
    using WebSocketServer server = new();
    WebSocketSession currSession = null;

    server.NewSessionConnected += session =>
    {
        currSession = session;
        currSession.Send("Connected");
    };

    server.NewMessageReceived += (session, message) =>
    {
        Console.WriteLine($"Client: {message}");

        if (message == "close")
            session.Close();
    };

    server.SessionClosed += (_, reason) => Console.WriteLine($"Server: Connection closed. Reason: {reason}, press 'Enter' to exit.");

    // In the ServerConfig, the default IP value is the localhost
    ServerConfig config = new()
    {
        Port = 85, // same port number as in client
        MaxConnectionNumber = 1 // depending on you design, there might be more than one connection
    };

    server.Setup(config);
    server.Start();

    while (true)
    {
        string input = Console.ReadLine();

        if (currSession != null && !currSession.Connected)
            break;

        if (currSession != null)
            currSession.Send(input);
        else
            Console.WriteLine("Server: No session");
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's Test It

Test over HTTPS

It works great!

Even though we are using non-secure protocol (ws) instead of the secure protocol (wss), and we are connecting from a secure context (https), the connection is not blocked.
That's because locally-delivered resources are considered secure, however http://localhost is not considered secure by older browsers, because it is not guaranteed to map to loopback address.
That's why although ws://localhost works fine with newer browsers, it's recommended to use ws://127.0.0.1

See Also

Some information is outdated.

💖 💪 🙅 🚩
mark0960
Ahmed Hesham

Posted on May 10, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related