Search code examples
javascriptreactjswebsocketmqttmqtt.js

How connect React to MQTT over WSS


I am trying to connect to the MQTT Broker using MQTT.js in my React application, but I am getting an error with the WSS protocol.

First, my mosquitto.conf file:

# mosquitto.conf

pid_file /run/mosquitto/mosquitto.pid

persistence true
persistence_location /var/lib/mosquitto/

log_dest file /var/log/mosquitto/mosquitto.log

include_dir /etc/mosquitto/conf.d

allow_anonymous false
password_file /etc/mosquitto/passwd

listener 3940
protocol mqtt

listener 39393
protocol websockets

listener 3941
protocol mqtt
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/broker.crt
keyfile /etc/mosquitto/certs/broker.key

listener 3942
protocol websockets
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/broker.crt
keyfile /etc/mosquitto/certs/broker.key

And the code block in my React application:

// some.jsx

useEffect(() => {
        let reconnectAttempts = 0;
        try {
            if (user && !client) {
                const host = "ws://my.domain.io:39393"
                // const host = "wss://my.domain.io:3942
                const options = {
                    keepalive: 60,
                    clientId: `${user.username}_${user.id}_${user.tenant}_${Date.now()}`,
                    username: "username",
                    password: "password",
                    clean: true,
                    rejectUnauthorized: false
                };
                const mqttClient = mqtt.connect(host, options);
                setClient(mqttClient);
                console.log("client: ", mqttClient);

                mqttClient.on("connect", () => {
                    console.log("Connected to MQTT broker");
                    setIsConnected(true);
                    setIsReconnecting(false);
                    setIsDisconnected(false);
                    reconnectAttempts = 0;
                });
                
                mqttClient.on("disconnect", () => {
                    console.log("Disconnected to MQTT broker");
                    setIsConnected(false);
                    setIsDisconnected(true);
                });
                
                mqttClient.on("close", (err) => {
                    console.log("Closed connection to MQTT broker", err);
                });

                mqttClient.on("reconnect", () => {
                    reconnectAttempts++;
                    setIsReconnecting(true);

                    if (reconnectAttempts > 5) {
                        mqttClient.end();
                        console.log("Reconnect attempts exceeded. Closing connection to MQTT broker");
                        setIsConnected(false);
                        setIsDisconnected(true);
                        return;
                    }
                    console.log("Reconnecting to MQTT broker", reconnectAttempts);
                });
                
                mqttClient.on("error", (err) => {
                    console.log("Error from MQTT broker", err); 
                });
            }   
        } catch (error) {
            console.error("Error connecting to MQTT broker", error);
        }

        return () => {
            if (client) {
                console.log("MQTT Hook cleanup")
                client.end();
                setClient(null);
            }
        }
    }, [user, client]);

I can connect to the host mqtts://my.domain.io:3941 on my local device, but I cannot connect to the host wss://my.domain.io:3942 in the production environment because I need to connect via HTTPS. Additionally, I can connect to wss://my.domain.io:3942 and perform pub/sub operations without any issues using Python, MQTT Box, and MQTT Explorer.

SS from MQTT Box: enter image description here

UPDATE:

# some.py
    def connect(self):
        self.client.on_message = self.on_message
        self.client.on_connect = self.on_connect
        self.client.on_disconnect = self.on_disconnect
        
        self.client.tls_set(cert_reqs=ssl.CERT_NONE)
        
        self.client.username_pw_set(self.username, self.password)
        
        self.client.connect(self.broker, self.port, keepalive=10)
        self.client.loop_start()

My Mosquitto logs are as follows. My backend application running in the local environment can connect to the port 3941 without a certificate, while my React application can only connect to the port 39393 without a certificate.

enter image description here

When my React application in the production environment tries to connect to port 39393, my console shows:

enter image description here

When it tries to connect to port 3942, my console shows:

enter image description here

Lastly, even though my React application is set to either port 39393 or 3942 in the production environment, no data appears in my Mosquitto logs.

So I want connect React to MQTT over WSS in my production server.


Solution

  • I had an issue where I couldn't connect to a secure MQTT broker at wss://dash.meetlarry.io:3942 over HTTPS while on a React Linux server. The steps I followed to solve this issue are as follows:

    1. First, I modified the Nginx configuration files to include the MQTT configuration, as the React server already had configurations for the certificates:

      # React App Server
      server {
          server_name my.domain.io;
      
          root /var/www/html/app/build;
          index index.html;
      
          listen 443 ssl;
          ssl_certificate /etc/letsencrypt/live/my.domain.io/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/my.domain.io/privkey.pem;
      
          location / {
              try_files $uri /index.html;
          }
      
          location /mqtt {
              proxy_pass http://localhost:39393;  # Mosquitto WebSocket port
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection "upgrade";
              proxy_set_header Host $host;
          }
      }
      
      # Redirect HTTP to HTTPS for React App
      server {
          listen 80;
          server_name my.domain.io;
          return 301 https://$host$request_uri;
      }
      
    2. Then, I updated the code within React as follows:

      const options = {
          host: "my.domain.io", // current server name with certificate
          port: 443, // port listened by nginx
          path: '/mqtt', // location assigned in nginx
          protocol: 'wss', // protocol
          keepalive: 60,
          clientId: `${user.username}_${user.id}_${user.tenant}_${Date.now()}`,
          username: "username",
          password: "password",
          rejectUnauthorized: false
      };
      const mqttClient = mqtt.connect(options);
      setClient(mqttClient);
      
    3. Next, I updated the MQTT configurations on the DRF side:

      class MQTTClient:
          def __init__(self, broker, port, username, password):
              self.broker = broker
              self.port = port
              self.username = username
              self.password = password
      
              client_id = f"larry_hotline-{random.randint(0, 1000)}"
              self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id, transport="websockets")
      
          def connect(self):
              self.client.on_message = self.on_message
              self.client.on_connect = self.on_connect
              self.client.on_disconnect = self.on_disconnect
      
              self.client.tls_set(cert_reqs=ssl.CERT_NONE)
      
              self.client.username_pw_set(self.username, self.password)
      
              self.client.connect(self.broker, self.port, keepalive=10)
              self.client.loop_start()
      
    4. I ran my Django application on the 3942 WebSocket port to ensure messages are transmitted, and this solved the strange issue.

    The reason I resorted to this solution is that I tried to certificate the IP address of my MQTT broker and connect by entering this IP address instead of the domain name through MQTT.js, but both attempts failed. I would have preferred to create a specific certificate for the MQTT broker and connect with MQTT.js directly. If anyone has managed to connect in this way before, I would be very happy if they could explain this method to me as well :)