Tutoriels

MQTT dans le navigateur en JavaScript pur (sans build)

Chargez MQTT.js 5 depuis un CDN dans un seul fichier HTML et connectez-vous en WebSocket sécurisé : publier, s'abonner, reconnecter et déboguer sans npm ni bundler.

Mis à jour · 8 min de lecture

MQTT dans le navigateur en JavaScript pur (sans build)

Pas besoin de npm, de React ni d'un bundler pour parler MQTT depuis une page web. MQTT.js 5 fournit un bundle prêt pour le navigateur : ajoutez une balise <script>, appelez mqtt.connect('wss://…'), et la page publie et s'abonne en WebSocket sécurisé. Ce guide construit un client complet dans un seul fichier, que vous pouvez ouvrir depuis le disque, déposer sur n'importe quel hébergement statique ou coller dans un outil interne.

Pourquoi les navigateurs ont besoin des WebSockets

Les navigateurs ne peuvent pas ouvrir de sockets TCP bruts : mqtt://host:1883 et mqtts://host:8883 sont donc hors de portée d'une page web. Les brokers résolvent ce problème avec un listener WebSocket : les mêmes paquets MQTT voyagent dans des trames WebSocket. Pour broker.connectmq.com, les endpoints sont :

URLUsage
ws://broker.connectmq.com:8083/mqttWebSocket non chiffré, uniquement pour les pages servies en http:// ou file://
wss://broker.connectmq.com:8084/mqttWebSocket sécurisé (TLS), fonctionne depuis toute page ; à utiliser par défaut

Le chemin /mqtt compte : se connecter au bon port avec le mauvais chemin échoue généralement pendant l'upgrade HTTP (voir erreur 404 sur le chemin WebSocket). MQTT sur WebSockets explique ce transport en détail.

La page complète

Enregistrez ceci sous index.html. La page fixe une version exacte de MQTT.js depuis le CDN, qui expose un objet global mqtt.

index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>MQTT browser client</title>
  <script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script>
  <style>
    body { font: 15px/1.5 system-ui, sans-serif; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
    #log { background: #111; color: #9f9; padding: .75rem; height: 260px; overflow: auto; font-family: monospace; }
    input, button { font: inherit; padding: .4rem .6rem; }
  </style>
</head>
<body>
  <h1>MQTT over WSS</h1>
  <p>Status: <strong id="status">connecting…</strong></p>
  <form id="pub">
    <input id="msg" placeholder="Message" required>
    <button>Publish</button>
  </form>
  <pre id="log"></pre>

  <script>
    const URL = 'wss://broker.connectmq.com:8084/mqtt';
    const TOPIC = 'testmqtt/browser-demo/' + Math.random().toString(16).slice(2, 8);

    const $ = (id) => document.getElementById(id);
    const log = (line) => {
      $('log').textContent += new Date().toLocaleTimeString() + '  ' + line + '\n';
      $('log').scrollTop = $('log').scrollHeight;
    };

    const client = mqtt.connect(URL, {
      clientId: 'web-' + Math.random().toString(16).slice(2, 10),
      // username: 'username', password: 'password',   // see "Credentials" below
      protocolVersion: 5,
      clean: true,
      keepalive: 30,
      connectTimeout: 10_000,
      reconnectPeriod: 2_000,
    });

    client.on('connect', () => {
      $('status').textContent = 'connected';
      client.subscribe(TOPIC, { qos: 1 }, (err) => {
        log(err ? 'subscribe failed: ' + err.message : 'subscribed to ' + TOPIC);
      });
    });

    client.on('message', (topic, payload) => {
      log(topic + '  ' + payload.toString());
    });

    client.on('reconnect', () => { $('status').textContent = 'reconnecting…'; });
    client.on('offline',   () => { $('status').textContent = 'offline'; });
    client.on('error',     (err) => log('error: ' + err.message));

    $('pub').addEventListener('submit', (e) => {
      e.preventDefault();
      client.publish(TOPIC, $('msg').value, { qos: 1 });
      $('msg').value = '';
    });

    window.addEventListener('pagehide', () => client.end());
  </script>
</body>
</html>

Ouvrez le fichier, tapez un message et cliquez sur Publish : la page est abonnée à son propre topic aléatoire, le message revient donc par le broker et s'affiche dans le log. Chaque onglet a son propre topic et son propre client ID, deux onglets ouverts ne se gênent donc pas. Pour observer le trafic de l'extérieur, ouvrez le client MQTT en ligne, abonnez-vous à testmqtt/browser-demo/# et publiez d'un côté ou de l'autre.

Variante en module ES

Si vous préférez les modules, MQTT.js publie aussi un build ESM. Aucune variable globale n'est créée, vous l'importez donc :

Script module
<script type="module">
  import mqtt from 'https://unpkg.com/mqtt@5.10.1/dist/mqtt.esm.js';

  const client = mqtt.connect('wss://broker.connectmq.com:8084/mqtt', { protocolVersion: 5 });
  client.on('connect', () => client.publish('testmqtt/browser-demo/esm', 'hello from a module'));
</script>

Les scripts module sont différés automatiquement et ne polluent pas window. Certains navigateurs bloquent les scripts module sur les pages ouvertes en file:// : servez donc le dossier avec un serveur web local :

Servir le dossier en local
python3 -m http.server 8000
# puis ouvrez http://localhost:8000/

Les options qui comptent dans un navigateur

OptionRecommandéPourquoi
clientIdAléatoire par ongletDeux onglets avec le même ID s'éjectent mutuellement en boucle
protocolVersion5 (ou 4 pour 3.1.1)MQTT 5 fournit des reason codes dans les erreurs, ce qui facilite le débogage
keepalive30–60 sLes portables se mettent en veille et le Wi-Fi change ; un keep-alive court détecte plus vite les sockets morts
reconnectPeriod2 000–5 000 ms0 désactive la reconnexion automatique ; des valeurs trop petites martèlent le broker
connectTimeout10 000 msÉchouer vite et afficher un état à l'utilisateur plutôt que de charger indéfiniment
cleantrueUn onglet de navigateur a rarement besoin d'une session persistante

Générez des ID avec un préfixe reconnaissable dans les logs du broker, ou utilisez le générateur de client ID. Si vous avez besoin de la même configuration dans un autre langage, le générateur de code MQTT produit des extraits équivalents.

Identifiants dans une page statique

Tout ce qui figure dans un fichier HTML est public : n'importe qui peut afficher le code source et copier username et password. Traitez les identifiants côté navigateur en conséquence :

  • Créez un utilisateur de broker dédié à la page et limitez-le par ACL aux seuls topics dont elle a besoin. Un dashboard n'a généralement besoin que d'un accès en lecture à .../telemetry/#.
  • Ne réutilisez jamais dans le navigateur un compte d'appareil, de backend ou d'administration.
  • Pour les utilisateurs connectés, laissez votre backend émettre des identifiants par utilisateur ou à courte durée de vie après authentification, et passez-les à mqtt.connect.

L'authentification MQTT par WebSocket dans le navigateur détaille ces approches.

Déboguer la connexion

Ouvrez les DevTools (F12) et regardez à deux endroits :

  1. Console. Les erreurs de l'événement error et les messages WebSocket du navigateur lui-même s'y affichent.
  2. Network → WS. Sélectionnez la connexion pour voir l'upgrade HTTP (statut 101 Switching Protocols en cas de succès) et les trames MQTT binaires dans l'onglet Messages.
Message dans la consoleCause probableCorrectif
Mixed Content: The page at 'https://…' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://…'ws:// utilisé depuis une page HTTPSUtilisez wss://…:8084/mqtt
WebSocket connection to 'wss://…' failedMauvais port ou chemin, firewall, problème TLSVérifiez le port 8084 et le chemin /mqtt ; voir la connexion WebSocket échoue
connack timeoutLe WebSocket s'est ouvert mais le CONNACK n'est pas arrivé à tempsBroker surchargé ou proxy qui met en tampon ; augmentez connectTimeout et réessayez
Connection refused: Not authorized / Bad User Name or PasswordIdentifiants absents ou incorrectsVérifiez le nom d'utilisateur, le mot de passe et les ACL de l'utilisateur
L'état alterne entre connecté et reconnexionClient ID en double dans un autre onglet ou appareilRendez clientId unique par onglet

L'outil de recherche de reason codes traduit les codes numériques renvoyés par les brokers MQTT 5. Pour mettre complètement votre page hors de cause, testez la même URL dans le client en ligne : s'il s'y connecte, le problème vient de votre code, pas du broker ni du réseau.

Voir le topic de la page démo dans le client en ligne

Et ensuite

La page ci-dessus est un client complet. Les étapes suivantes classiques sont une interface de chat (un topic par salon, présence via Last Will), un graphique en direct alimenté par la télémétrie de capteurs ou un panneau de contrôle qui publie des commandes. Quand le code dépasse quelques centaines de lignes, passer au paquet npm et à un bundler est simple : l'API mqtt.connect est identique, c'est d'ailleurs pourquoi le guide MQTT.js s'applique tel quel.

Une balise script, une URL wss:// et quelques gestionnaires d'événements : c'est tout ce dont un navigateur a besoin pour parler MQTT. Fixez la version de la bibliothèque, donnez à chaque onglet son propre client ID, n'envoyez au navigateur qu'un compte restreint et vérifiez chaque modification dans les DevTools et le client en ligne avant de déployer.

Questions fréquentes

Un navigateur peut-il se connecter à MQTT sur le port 1883 ou 8883 ?

Non. Les navigateurs ne peuvent pas ouvrir de sockets TCP bruts : une page web ne peut joindre un broker MQTT que via un listener WebSocket. Utilisez wss:// (port 8084 avec le chemin /mqtt sur broker.connectmq.com) depuis toute page servie en HTTPS.

Est-il sûr de mettre des identifiants MQTT dans une page HTML statique ?

Tout ce qui se trouve dans la page est lisible par chaque visiteur. Utilisez un utilisateur de broker dédié dont l'ACL n'autorise que les topics nécessaires à cette page (souvent en lecture seule), ou laissez votre backend fournir des identifiants à courte durée de vie. N'envoyez jamais un compte administrateur ou un compte d'appareil au navigateur.

Pourquoi ma page fonctionne en local mais échoue une fois déployée ?

Le plus souvent à cause du contenu mixte. Une page servie en https:// ne peut pas ouvrir une connexion ws:// non sécurisée, le navigateur la bloque donc. Passez l'URL en wss:// sur le port WebSocket TLS et vérifiez le chemin.