Skip to content

Repository files navigation

@zeromq/omq

ZMQ client library for browsers over WebSocket. Implements ZMTP 3.x NULL and PLAIN security over the ZWS 2.0 framing protocol, with optional LZ4 dictionary compression.

Supports connect-side WebSocket sockets for REQ/REP, PUB/SUB, XPUB/XSUB, PUSH/PULL, DEALER/ROUTER, PAIR, CLIENT/SERVER, RADIO/DISH, SCATTER/GATHER, PEER, and CHANNEL. Security mechanisms are NULL and PLAIN.

Install

Published on JSR as @zeromq/omq.

deno add jsr:@zeromq/omq

Usage

Subscribe

import { initLz4, Sub } from "@zeromq/omq";

await initLz4();

const sub = new Sub();
sub.subscribe("market.");
sub.connect("lz4+ws://broker.example.com/sub");

for await (const msg of sub) {
  console.log(msg.string(0), msg.string(1));
}

Request-Reply

import { Message, Req } from "@zeromq/omq";

const req = new Req();
req.connect("wss://broker.example.com/req");

const reply = await req.send(new Message("hello"));
console.log(reply.string(0));

Push

import { Message, Push } from "@zeromq/omq";

const push = new Push();
push.connect("wss://broker.example.com/push");

await push.send(new Message("event", JSON.stringify({ ts: Date.now() })));

LZ4 Dictionary Compression

For lz4+ws:// URLs, call initLz4() once before connecting. An optional pre-shared dictionary improves compression ratio on small messages:

import { initLz4, Sub } from "@zeromq/omq";

await initLz4();

const dict = new Uint8Array([...]); // pre-shared dictionary
const sub = new Sub({ lz4Dict: dict });
sub.subscribe("");
sub.connect("lz4+ws://broker.example.com/sub");

TypeScript senders reject individual LZ4 parts at the 1 GiB multi-block boundary. Split huge payloads at the application layer.

PLAIN Authentication

Use PLAIN when the WebSocket server requires username/password auth. Prefer wss:// because PLAIN itself does not encrypt credentials.

const push = new Push({
  plain: { username: "alice", password: "secret" },
});
push.connect("wss://broker.example.com/push");

TLS and Client Authentication

Use wss:// for transport privacy. Browser JavaScript cannot choose or provide a client certificate/key for WebSocket; mTLS has to be handled by the browser profile, operating system, or a TLS-terminating proxy. It is not a portable application-level auth mechanism for this library.

For browser clients, prefer wss:// plus PLAIN with a scoped token or JWT as the password when the server needs to authenticate the application peer.

CURVE is not implemented in omq.ts for now. Over wss://, it would mostly duplicate TLS encryption and add a larger ZMTP crypto handshake surface. CURVE over ws:// could make sense later for ZMQ-style key identity without TLS client-certificate deployment, but it is intentionally out of scope today.

Robustness Options

Sockets reconnect by default after peer-side close or WebSocket error. Current subscriptions and group joins replay after reconnect.

const sub = new Sub({
  reconnectInitialDelayMs: 100,
  reconnectMaxDelayMs: 5000,
  receiveHighWaterMark: 1000,
  sendHighWaterMark: 1000,
  onError: (error) => console.error(error),
});

Browser WebSocket does not expose inbound TCP backpressure. If receiveHighWaterMark is set and the receive queue is full, omq.ts calls onError, closes the affected connection, and reconnects unless reconnects are disabled. The overflow is reported as connection failure instead of a silent queue drop.

Live Interop Tests

Run npm run test:interop from this repo to start real omq-tokio WebSocket peers from the crates.io test fixture and verify NULL, PLAIN, and lz4+ws:// traffic.

Run npm run test:browser for the headless Firefox browser interop test. It starts a Rust WebSocket peer, serves a temporary browser bundle, and verifies the same paths through native browser WebSocket and WASM LZ4, including wss://.

About

ZMQ client for browsers over WebSocket (ZMTP 3.x / ZWS 2.0) with LZ4 dictionary compression

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages