Full Duplex Communication Between Web App and Desktop Application Using WebSocket
Tags: javascript, webdev, dotnet, tutorial
Published: 2022-05-10 17:38:39

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skadpd2gxitdst6xwqn2.png)

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](https://web.dev/devices-introduction/) 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](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API).
The WebSocket server is a console application written in .Net Framework, and we use [SuperSocket](https://www.supersocket.net/) 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>
```

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;
}
```

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();
    }
}
```

## 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");
    }
}
```

## Let's Test It

![Test over HTTPS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o22vx09p1ffvhp7700xg.png)

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](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_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.

* [Browser Architecture: Web-to-App Communication Overview](https://textslashplain.com/2019/08/28/browser-architecture-web-to-app-communication-overview/)
* [When a web PKI certificate won't cut it](https://emilymstark.com/2021/12/24/when-a-web-pki-certificate-wont-cut-it.html)
* [HOW PLEX IS DOING HTTPS FOR ALL ITS USERS](https://words.filippo.io/how-plex-is-doing-https-for-all-its-users/)
* [Let 'localhost' be localhost](https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-let-localhost-be-localhost-02)
* [WebSockets for fun and profit](https://stackoverflow.blog/2019/12/18/websockets-for-fun-and-profit/)