Using mqtt5 WASM Client and Broker in JavaScript Applications
March 25, 2026 ยท View on GitHub
This guide explains how to use the mqtt5 MQTT client and in-tab broker in any JavaScript application (vanilla JS, React, Vue, etc.).
What You Get
When you build the WASM package, you get these files in pkg/:
graph TD
pkg["pkg/"] --> js["mqtt5.js<br/>JavaScript bindings (import this)"]
pkg --> wasm["mqtt5_bg.wasm<br/>Compiled Rust code"]
pkg --> dts["mqtt5.d.ts<br/>TypeScript definitions"]
pkg --> pjson["package.json<br/>npm package metadata"]
You only need to import mqtt5.js - it automatically loads the WASM file.
Building the WASM Package
# Install wasm-pack (one time)
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# Build client only
wasm-pack build crates/mqtt5-wasm --target web --features client
# Build client + in-tab broker
wasm-pack build crates/mqtt5-wasm --target web --features client,broker
# Build client + broker + payload compression codecs
wasm-pack build crates/mqtt5-wasm --target web --features client,broker,codec
# Output is in crates/mqtt5-wasm/pkg/
Basic Usage
In-Tab Broker (No External Dependencies)
Run a complete MQTT broker inside your browser tab:
<!DOCTYPE html>
<html>
<head>
<title>MQTT In-Tab Broker</title>
</head>
<body>
<script type="module">
import init, { Broker, MqttClient } from './pkg/mqtt5.js';
async function main() {
await init();
const broker = new Broker();
const client = new MqttClient('local-client');
const port = broker.createClientPort();
await client.connectMessagePort(port);
await client.subscribeWithCallback('test/topic', (topic, payload, properties) => {
const decoder = new TextDecoder();
console.log('Received:', topic, decoder.decode(payload));
if (properties.responseTopic) {
console.log('Response topic:', properties.responseTopic);
}
});
const encoder = new TextEncoder();
await client.publish('test/topic', encoder.encode('Hello from in-tab broker!'));
}
main().catch(console.error);
</script>
</body>
</html>
External WebSocket Broker
Connect to a remote MQTT broker over WebSocket:
<!DOCTYPE html>
<html>
<head>
<title>MQTT Client</title>
</head>
<body>
<script type="module">
import init, { MqttClient } from './pkg/mqtt5.js';
async function main() {
await init();
const client = new MqttClient('my-client-id');
await client.connect('ws://broker.hivemq.com:8000/mqtt');
await client.subscribeWithCallback('test/topic', (topic, payload, properties) => {
const decoder = new TextDecoder();
console.log('Received:', decoder.decode(payload));
});
const payload = new TextEncoder().encode('Hello MQTT');
await client.publish('test/topic', payload);
}
main().catch(console.error);
</script>
</body>
</html>
Important: You need a web server - WASM won't work with file:// URLs.
# Simple web server
python3 -m http.server 8000
# or
npx serve
React
# Copy pkg/ to your project
cp -r crates/mqtt5-wasm/pkg/ src/mqtt5/
src/mqtt.js
import init, { MqttClient } from './mqtt5/mqtt5.js';
let initialized = false;
export async function createMqttClient(clientId) {
if (!initialized) {
await init();
initialized = true;
}
return new MqttClient(clientId);
}
Component usage:
import { useEffect, useState } from 'react';
import { createMqttClient } from './mqtt';
function MyComponent() {
const [client, setClient] = useState(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
let mqttClient;
async function connect() {
mqttClient = await createMqttClient('react-client');
await mqttClient.connect('ws://broker.hivemq.com:8000/mqtt');
setClient(mqttClient);
setConnected(true);
}
connect();
return () => {
if (mqttClient) {
mqttClient.disconnect();
}
};
}, []);
async function handlePublish() {
if (!client) return;
const payload = new TextEncoder().encode('Hello from React');
await client.publish('react/topic', payload);
}
return (
<div>
<p>Status: {connected ? 'Connected' : 'Disconnected'}</p>
<button onClick={handlePublish}>Publish</button>
</div>
);
}
Vue 3
import { ref, onMounted, onUnmounted } from 'vue';
import init, { MqttClient } from './mqtt5/mqtt5.js';
export default {
setup() {
const client = ref(null);
const connected = ref(false);
onMounted(async () => {
await init();
client.value = new MqttClient('vue-client');
await client.value.connect('ws://broker.hivemq.com:8000/mqtt');
connected.value = true;
});
onUnmounted(() => {
if (client.value) {
client.value.disconnect();
}
});
async function publish() {
const payload = new TextEncoder().encode('Hello from Vue');
await client.value.publish('vue/topic', payload);
}
return { connected, publish };
}
};
Available Methods
MqttClient API
The JavaScript class is exported as MqttClient (Rust type: WasmMqttClient). The methods below are grouped by concern: connection management, publishing, subscribing, authentication, reconnection, and event callbacks.
Construction
const client = new MqttClient(clientId);
Connection
await client.connect(url);
await client.connectWithOptions(url, connectOptions);
await client.connectMessagePort(port);
await client.connectMessagePortWithOptions(port, connectOptions);
await client.connectBroadcastChannel(channelName);
await client.disconnect();
client.isConnected();
// Returns: boolean
client.isBrowserOnline();
// Returns: boolean (navigator.onLine state)
client.destroy();
Publishing
await client.publish(topic, payloadBytes);
await client.publishWithOptions(topic, payloadBytes, publishOptions);
const packetId = await client.publishQos1(topic, payloadBytes, callback);
// callback(reasonCode) called when PUBACK received
const packetId = await client.publishQos2(topic, payloadBytes, callback);
// callback(reasonCode) called when PUBCOMP received
Subscribing
const packetId = await client.subscribe(topic);
const packetId = await client.subscribeWithCallback(topic, callback);
// callback(topic, payload, properties) called for each matching message
const packetId = await client.subscribeWithOptions(topic, callback, subscribeOptions);
// Same callback signature, with advanced subscription options
// Rejects if broker returns reason code >= 0x80
const packetId = await client.unsubscribe(topic);
Enhanced Authentication
client.respondAuth(authData);
Reconnection
client.setReconnectOptions(reconnectOptions);
client.enableAutoReconnect(enabled);
client.isReconnecting();
// Returns: boolean
Event Callbacks
client.onConnect((reasonCode, sessionPresent) => {
console.log('Connected!', reasonCode, sessionPresent);
});
client.onDisconnect(() => {
console.log('Disconnected');
});
client.onError((error) => {
console.error('Error:', error);
});
client.onAuthChallenge((method, data) => {
// method: string (authentication method name)
// data: Uint8Array | null (challenge data from broker)
const response = computeResponse(method, data);
client.respondAuth(response);
});
client.onReconnecting((attempt, delayMs) => {
console.log(`Reconnect attempt ${attempt}, next retry in ${delayMs}ms`);
});
client.onReconnectFailed((error) => {
console.error('All reconnect attempts failed:', error);
});
client.onConnectivityChange((online) => {
console.log('Browser online:', online);
});
Broker API
The JavaScript class is exported as Broker (Rust type: WasmBroker).
Construction
const broker = new Broker();
const config = new BrokerConfig();
config.maxClients = 500;
config.allowAnonymous = true;
const broker = Broker.withConfig(config);
Client Ports
const port = broker.createClientPort();
// Returns: MessagePort for client to connect via connectMessagePort()
User Management
broker.addUser('alice', 'password123');
broker.addUserWithHash('bob', hashedPassword);
broker.removeUser('alice');
// Returns: boolean (true if user existed)
broker.hasUser('alice');
// Returns: boolean
broker.userCount();
// Returns: number
const hash = Broker.hashPassword('password123');
// Returns: string (static method)
ACL (Access Control Lists)
await broker.addAclRule('alice', 'sensors/#', 'read');
// permission: 'read', 'write', 'readwrite', 'deny'
await broker.clearAclRules();
const count = await broker.aclRuleCount();
await broker.setAclDefaultDeny();
await broker.setAclDefaultAllow();
Role-Based Access Control
await broker.addRole('admin');
await broker.removeRole('admin');
// Returns: boolean
const roles = await broker.listRoles();
// Returns: string[]
const count = await broker.roleCount();
await broker.addRoleRule('admin', 'system/#', 'readwrite');
await broker.assignRole('alice', 'admin');
await broker.unassignRole('alice', 'admin');
// Returns: boolean
const userRoles = await broker.getUserRoles('alice');
// Returns: string[]
await broker.clearRoles();
Configuration
const newConfig = new BrokerConfig();
newConfig.maxClients = 2000;
broker.updateConfig(newConfig);
broker.getConfigHash();
// Returns: number
broker.getMaxClients();
// Returns: number
broker.getMaxPacketSize();
// Returns: number
broker.getSessionExpiryIntervalSecs();
// Returns: number
System Topics ($SYS)
broker.startSysTopics();
// Publishes $SYS topics every 10 seconds
broker.startSysTopicsWithIntervalSecs(30);
// Custom interval
broker.stopSysTopics();
Bridging
const bridgeConfig = new BridgeConfig('my-bridge');
bridgeConfig.clientId = 'bridge-client';
bridgeConfig.cleanStart = true;
bridgeConfig.keepAliveSecs = 60;
bridgeConfig.username = 'bridge-user';
bridgeConfig.password = 'bridge-pass';
bridgeConfig.loopPreventionTtlSecs = 300;
bridgeConfig.loopPreventionCacheSize = 10000;
const mapping = new TopicMapping('sensors/#', BridgeDirection.Both);
mapping.qos = 1;
mapping.localPrefix = 'local/';
mapping.remotePrefix = 'remote/';
bridgeConfig.addTopic(mapping);
await broker.addBridge(bridgeConfig, remotePort);
await broker.addBridgeWebSocket(bridgeConfig, 'ws://remote-broker:8000/mqtt', connectOptions);
await broker.removeBridge('my-bridge');
const bridges = broker.listBridges();
// Returns: string[]
await broker.stopAllBridges();
Broker Event Callbacks
All broker events receive a single object argument with named properties.
broker.onClientConnect((event) => {
// event.clientId: string
// event.cleanStart: boolean
console.log(`Client connected: ${event.clientId}`);
});
broker.onClientDisconnect((event) => {
// event.clientId: string
// event.reason: string
// event.unexpected: boolean
console.log(`Client disconnected: ${event.clientId}, reason: ${event.reason}`);
});
broker.onClientPublish((event) => {
// event.clientId: string
// event.topic: string
// event.qos: number
// event.retain: boolean
// event.payloadSize: number
console.log(`${event.clientId} published to ${event.topic}`);
});
broker.onClientSubscribe((event) => {
// event.clientId: string
// event.subscriptions: Array<{topic: string, qos: number}>
console.log(`${event.clientId} subscribed to ${event.subscriptions.length} topics`);
});
broker.onClientUnsubscribe((event) => {
// event.clientId: string
// event.topics: string[]
console.log(`${event.clientId} unsubscribed from ${event.topics.join(', ')}`);
});
broker.onMessageDelivered((event) => {
// event.clientId: string
// event.packetId: number
// event.qos: number
console.log(`Message ${event.packetId} delivered to ${event.clientId}`);
});
broker.onConfigChange((oldHash, newHash) => {
console.log(`Config changed: ${oldHash} -> ${newHash}`);
});
BrokerConfig API
The JavaScript class is exported as BrokerConfig (Rust type: WasmBrokerConfig).
const config = new BrokerConfig();
config.maxClients = 1000; // default: 1000
config.sessionExpiryIntervalSecs = 3600; // default: 3600
config.maxPacketSize = 268435456; // default: 268435456 (256MB)
config.topicAliasMaximum = 65535; // default: 65535
config.retainAvailable = true; // default: true
config.maximumQos = 2; // default: 2 (0, 1, or 2)
config.wildcardSubscriptionAvailable = true; // default: true
config.subscriptionIdentifierAvailable = true; // default: true
config.sharedSubscriptionAvailable = true; // default: true
config.serverKeepAliveSecs = 120; // default: null (use client value)
config.allowAnonymous = false; // default: false
config.maxOutboundRatePerClient = 0; // default: 0 (unlimited)
config.changeOnlyDeliveryEnabled = true;
config.addChangeOnlyDeliveryPattern('sensors/#');
config.clearChangeOnlyDeliveryPatterns();
config.echoSuppressionEnabled = true;
config.echoSuppressionPropertyKey = 'x-origin-client-id';
// default key when enabled: 'x-origin-client-id'
ConnectOptions API
The JavaScript class is exported as ConnectOptions (Rust type: WasmConnectOptions).
const opts = new ConnectOptions();
opts.keepAlive = 60; // default: 60 seconds
opts.cleanStart = true; // default: true
opts.username = 'alice'; // default: null
opts.set_password(encoder.encode('pw')); // accepts Uint8Array
opts.protocolVersion = 5; // 4 (v3.1.1) or 5 (v5.0), default: 5
opts.sessionExpiryInterval = 3600; // default: null
opts.receiveMaximum = 65535; // default: null
opts.maximumPacketSize = 1048576; // default: null
opts.topicAliasMaximum = 10; // default: null
opts.requestResponseInformation = true; // default: null
opts.requestProblemInformation = true; // default: null
opts.authenticationMethod = 'SCRAM-SHA-256'; // default: null
opts.set_authenticationData(data); // accepts Uint8Array
opts.addUserProperty('key', 'value');
opts.clearUserProperties();
opts.addBackupUrl('ws://backup-broker:8000/mqtt');
opts.clearBackupUrls();
opts.getBackupUrls();
// Returns: string[]
const will = new WillMessage('client/status', encoder.encode('offline'));
will.qos = 1;
will.retain = true;
opts.setWill(will);
opts.clearWill();
// With codec feature
opts.setCodecRegistry(registry);
opts.clearCodecRegistry();
await client.connectWithOptions('ws://broker:8000/mqtt', opts);
PublishOptions API
The JavaScript class is exported as PublishOptions (Rust type: WasmPublishOptions).
const opts = new PublishOptions();
opts.qos = 1; // 0, 1, or 2; default: 0
opts.retain = true; // default: false
opts.payloadFormatIndicator = true; // default: null (true = UTF-8)
opts.messageExpiryInterval = 60; // default: null (seconds)
opts.topicAlias = 1; // default: null
opts.responseTopic = 'reply/topic'; // default: null
opts.set_correlationData(data); // accepts Uint8Array
opts.contentType = 'application/json'; // default: null
opts.addUserProperty('key', 'value');
opts.clearUserProperties();
await client.publishWithOptions('my/topic', payload, opts);
SubscribeOptions API
The JavaScript class is exported as SubscribeOptions (Rust type: WasmSubscribeOptions).
const opts = new SubscribeOptions();
opts.qos = 1; // 0, 1, or 2; default: 0
opts.noLocal = true; // default: false (don't receive own messages)
opts.retainAsPublished = true; // default: false
opts.retainHandling = 0; // 0=send at subscribe, 1=send if new, 2=don't send; default: 0
opts.subscriptionIdentifier = 42; // default: null
await client.subscribeWithOptions('my/topic', callback, opts);
WillMessage API
The JavaScript class is exported as WillMessage (Rust type: WasmWillMessage).
const will = new WillMessage(topic, payloadBytes);
will.topic = 'client/status';
will.qos = 1; // 0, 1, or 2; default: 0
will.retain = true; // default: false
will.willDelayInterval = 60; // default: null (seconds)
will.messageExpiryInterval = 3600; // default: null (seconds)
will.contentType = 'text/plain'; // default: null
will.responseTopic = 'reply/topic'; // default: null
ReconnectOptions API
The JavaScript class is exported as ReconnectOptions (Rust type: WasmReconnectOptions).
const opts = new ReconnectOptions();
opts.enabled = true; // default: true
opts.initialDelayMs = 1000; // default: 1000
opts.maxDelayMs = 60000; // default: 60000
opts.backoffFactor = 2.0; // default: 2.0
opts.maxAttempts = 20; // default: 20, null for unlimited
const disabled = ReconnectOptions.disabled();
client.setReconnectOptions(opts);
MessageProperties API
The JavaScript class is exported as MessageProperties (Rust type: WasmMessageProperties). This is passed as the third argument to subscription callbacks.
await client.subscribeWithCallback('test/#', (topic, payload, properties) => {
properties.responseTopic; // string | null
properties.correlationData; // Uint8Array | null
properties.contentType; // string | null
properties.payloadFormatIndicator; // boolean | null
properties.messageExpiryInterval; // number | null
properties.subscriptionIdentifiers; // number[]
properties.getUserProperties(); // Array<[string, string]>
});
BridgeConfig API
The JavaScript class is exported as BridgeConfig (Rust type: WasmBridgeConfig).
const config = new BridgeConfig('bridge-name');
config.clientId = 'bridge-client-1'; // default: 'bridge-{name}'
config.cleanStart = true; // default: true
config.keepAliveSecs = 60; // default: 60
config.username = 'bridge-user'; // default: null
config.password = 'bridge-pass'; // default: null
config.loopPreventionTtlSecs = 300; // default: 0 (disabled)
config.loopPreventionCacheSize = 10000; // default: 10000
const mapping = new TopicMapping('sensors/#', BridgeDirection.In);
config.addTopic(mapping);
config.validate();
TopicMapping API
The JavaScript class is exported as TopicMapping (Rust type: WasmTopicMapping).
const mapping = new TopicMapping(pattern, direction);
mapping.qos = 1; // 0, 1, or 2; default: 0
mapping.localPrefix = 'local/'; // default: null
mapping.remotePrefix = 'remote/'; // default: null
BridgeDirection Enum
BridgeDirection.In // Remote -> Local only
BridgeDirection.Out // Local -> Remote only
BridgeDirection.Both // Bidirectional
Transport Types Explained
Three transport types serve different deployment scenarios. Choose based on whether you have an external broker, need a self-contained in-tab setup, or want cross-tab communication.
1. WebSocket (External Broker)
Use WebSocket when you have a broker running on a remote server or local network. The client connects over standard HTTP upgrade, which works through firewalls and proxies. Use ws:// for development and wss:// (TLS) for production.
const client = new MqttClient('my-client-id');
await client.connect('ws://broker.example.com:8000/mqtt');
Free public brokers for testing: ws://broker.hivemq.com:8000/mqtt, ws://test.mosquitto.org:8080, ws://broker.emqx.io:8083/mqtt
2. MessagePort (In-Tab Broker)
Use MessagePort when you need a fully self-contained setup with no external dependencies. The broker runs inside your browser tab, and clients connect via MessagePort for zero-network-overhead communication. This is ideal for testing, demos, and offline-capable applications. The in-tab broker provides full MQTT v5.0 support (QoS 0/1/2, retained messages, shared subscriptions) with memory-only storage, configurable authentication, and bridging to remote brokers.
const broker = new Broker();
const client = new MqttClient('local-client');
const port = broker.createClientPort();
await client.connectMessagePort(port);
See crates/mqtt5-wasm/examples/local-broker/ for complete implementation.
3. BroadcastChannel (Cross-Tab Communication)
Use BroadcastChannel when multiple browser tabs or iframes need to share an MQTT connection. All tabs joined to the same channel name can exchange MQTT packets. One tab must run a broker that listens on the same channel.
const client = new MqttClient('tab-client');
await client.connectBroadcastChannel('mqtt-channel');
Tab running the broker:
const broker = new Broker();
const localClient = new MqttClient('broker-tab');
const port = broker.createClientPort();
await localClient.connectMessagePort(port);
Other tabs:
const client = new MqttClient('other-tab');
await client.connectBroadcastChannel('mqtt-channel');
Payload Compression (Codec Feature)
When built with the codec feature, the client supports transparent payload compression and decompression using MQTT v5.0 content type properties.
Setup
import init, { MqttClient, ConnectOptions, CodecRegistry,
GzipCodec, DeflateCodec,
createCodecRegistry, createGzipCodec, createDeflateCodec
} from './pkg/mqtt5.js';
const registry = new CodecRegistry();
const gzip = new GzipCodec();
// or with options: createGzipCodec(6, 128)
registry.registerGzip(gzip);
const deflate = new DeflateCodec();
// or with options: createDeflateCodec(6, 128)
registry.registerDeflate(deflate);
registry.setDefault('application/gzip');
const opts = new ConnectOptions();
opts.setCodecRegistry(registry);
await client.connectWithOptions('ws://broker:8000/mqtt', opts);
GzipCodec
const codec = new GzipCodec();
// Content type: 'application/gzip'
codec.withLevel(6); // Compression level 1-9, default: 6
codec.withMinSize(128); // Min payload size to compress, default: 128 bytes
codec.withMaxDecompressedSize(10485760); // Max decompressed size, default: 10MB
DeflateCodec
const codec = new DeflateCodec();
// Content type: 'application/x-deflate'
codec.withLevel(6); // Compression level 1-9, default: 6
codec.withMinSize(128); // Min payload size to compress, default: 128 bytes
codec.withMaxDecompressedSize(10485760); // Max decompressed size, default: 10MB
CodecRegistry
const registry = new CodecRegistry();
registry.registerGzip(gzipCodec);
registry.registerDeflate(deflateCodec);
registry.setDefault('application/gzip');
registry.getDefault(); // Returns: string | null
registry.hasCodec('application/gzip'); // Returns: boolean
Factory Functions
const registry = createCodecRegistry();
const gzip = createGzipCodec(6, 128); // (level?, minSize?)
const deflate = createDeflateCodec(6, 128); // (level?, minSize?)
Working with Messages
Encoding/Decoding Payloads
MQTT payloads are byte arrays. JavaScript uses TextEncoder/TextDecoder:
const encoder = new TextEncoder();
const payload = encoder.encode('Hello World');
await client.publish('test/topic', payload);
const data = { temperature: 25.5, humidity: 60 };
const jsonPayload = encoder.encode(JSON.stringify(data));
await client.publish('sensors/data', jsonPayload);
const decoder = new TextDecoder();
await client.subscribeWithCallback('sensors/data', (topic, payload, properties) => {
const text = decoder.decode(payload);
const json = JSON.parse(text);
console.log('Temperature:', json.temperature);
});
Common Patterns
Auto-Reconnect
const reconnectOpts = new ReconnectOptions();
reconnectOpts.initialDelayMs = 1000;
reconnectOpts.maxDelayMs = 30000;
reconnectOpts.backoffFactor = 2.0;
reconnectOpts.maxAttempts = 10;
client.setReconnectOptions(reconnectOpts);
client.onReconnecting((attempt, delayMs) => {
console.log(`Reconnect attempt ${attempt}, waiting ${delayMs}ms`);
});
client.onReconnectFailed((error) => {
console.error('Reconnection gave up:', error);
});
Request-Response Pattern
async function request(client, topic, payload, timeout = 5000) {
const responseTopic = `response/${crypto.randomUUID()}`;
const encoder = new TextEncoder();
return new Promise(async (resolve, reject) => {
const timer = setTimeout(() => {
client.unsubscribe(responseTopic);
reject(new Error('Request timeout'));
}, timeout);
await client.subscribeWithCallback(responseTopic, (topic, payload, properties) => {
clearTimeout(timer);
client.unsubscribe(responseTopic);
resolve({ topic, payload, properties });
});
const opts = new PublishOptions();
opts.responseTopic = responseTopic;
opts.set_correlationData(encoder.encode(crypto.randomUUID()));
await client.publishWithOptions(topic, encoder.encode(payload), opts);
});
}
Graceful Shutdown
window.addEventListener('beforeunload', async (event) => {
if (client && client.isConnected()) {
await client.disconnect();
}
});
Authenticated Broker
const config = new BrokerConfig();
config.allowAnonymous = false;
const broker = Broker.withConfig(config);
broker.addUser('alice', 'secret123');
await broker.addAclRule('alice', 'sensors/#', 'readwrite');
await broker.addAclRule('alice', 'admin/#', 'deny');
const port = broker.createClientPort();
const opts = new ConnectOptions();
opts.username = 'alice';
opts.set_password(new TextEncoder().encode('secret123'));
await client.connectMessagePortWithOptions(port, opts);
Broker Bridging
const localBroker = new Broker();
const remoteBroker = new Broker();
const bridgeConfig = new BridgeConfig('local-to-remote');
const mapping = new TopicMapping('sensors/#', BridgeDirection.Out);
mapping.localPrefix = 'local/';
bridgeConfig.addTopic(mapping);
const remotePort = remoteBroker.createClientPort();
await localBroker.addBridge(bridgeConfig, remotePort);
Bundler Configuration
Vite
// vite.config.js
export default {
build: {
target: 'esnext'
},
optimizeDeps: {
exclude: ['mqtt5']
}
};
Copy pkg/ to public/mqtt5/ or import directly if in src/.
Webpack
// webpack.config.js
module.exports = {
experiments: {
asyncWebAssembly: true
}
};
Create React App
CRA doesn't support WASM easily. Either:
- Eject and configure webpack
- Use
public/folder for pkg files - Use Vite instead
Browser Support
- Chrome/Edge 90+
- Firefox 88+
- Safari 15.4+
- Internet Explorer is not supported (no WASM)
Deployment Checklist
- MIME Types: Server must serve
.wasmfiles withapplication/wasm - CORS: If WASM and HTML on different domains, configure CORS
- HTTPS: Production should use
wss://(encrypted WebSocket) - Bundle Size:
mqtt5_bg.wasmis ~90KB (optimized) - Caching: Set long cache headers for
.wasmfiles (they're versioned)
Troubleshooting
"Failed to fetch WASM"
- Ensure web server is running (not
file://) - Check browser console for 404 errors
- Verify
mqtt5_bg.wasmexists in same directory asmqtt5.js
"Cannot read properties of undefined"
- Did you call
await init()before creating client? - Check if WASM initialization succeeded
"Connection failed"
- Verify broker URL (ws:// or wss://)
- Check broker is accessible (try in MQTT client first)
- Check browser console for CORS errors
"Module not found"
- Import path must be correct:
from './pkg/mqtt5.js' - Use relative paths, not node-style imports
WASM Limitations
The WASM build has constraints compared to the native Rust library:
Transport Limitations
- No TLS socket support: Browser security prevents raw TLS. Use browser-managed
wss:// - WebSocket only for external brokers: Must use WebSocket transport
- MessagePort for in-tab broker: Communication within same browser tab
- BroadcastChannel for cross-tab: Communication between browser tabs
Storage Limitations
- Memory-only storage: No file persistence in browser
- Session data lost on reload: All broker state is transient
Authentication Limitations
- In-tab broker defaults to deny anonymous: Set
allowAnonymous = trueon BrokerConfig or add users withaddUser()
Network Limitations
- No server sockets: Cannot listen for incoming connections
- No file-based configuration: All config must be programmatic
These limitations are inherent to the browser sandbox security model.
Next Steps
- See
crates/mqtt5-wasm/examples/for all browser examples - See
crates/mqtt5-wasm/examples/README.mdfor example descriptions - Read MQTT v5.0 spec for protocol details
TypeScript Support
TypeScript definitions are included in pkg/mqtt5.d.ts:
import init, { MqttClient, ConnectOptions, PublishOptions } from './mqtt5/mqtt5.js';
async function example(): Promise<void> {
await init();
const client = new MqttClient('ts-client');
const opts = new ConnectOptions();
opts.keepAlive = 30;
await client.connectWithOptions('ws://broker.example.com:8000/mqtt', opts);
const pubOpts = new PublishOptions();
pubOpts.qos = 1;
pubOpts.retain = true;
const payload: Uint8Array = new TextEncoder().encode('Hello');
await client.publishWithOptions('test/topic', payload, pubOpts);
await client.subscribeWithCallback('test/topic', (topic, payload, properties) => {
console.log('Received on', topic);
});
}