Framework-agnostic browser client for the Hiyve Semantic Relay — built for high-frequency, fire-and-forget, room-scoped messaging.
Connects over WebTransport (HTTP/3 / QUIC datagrams, preferred) with automatic WebSocket fallback: when WebTransport is missing or cannot connect (e.g. networks that block UDP), the client transparently retries over WebSocket against the same relay endpoint. Consumers use one API either way; the active transport is reported via client.transportKind.
For React applications, use @hiyve/react-semantic-relay which wraps this client with a context provider and topic-scoped pub/sub hooks.
npm install @hiyve/semantic-relay-client
import { SemanticRelayClient } from '@hiyve/semantic-relay-client';
const client = new SemanticRelayClient({
url: 'https://semantic.hiyve.tv:4443/relay',
roomId: 'lesson-abc',
userId: 'alice@example.com',
token: jwtFromBackend,
serverCertificateHashes, // optional; required for self-signed dev/prod certs
});
const session = await client.connect();
console.log('session id', session.sessionId, 'peers', session.peers);
const unsubscribe = client.subscribe('instrument-notes', ({ payload, userId }) => {
console.log(`${userId} played`, payload);
});
client.publish('instrument-notes', { midi: 60, on: true, velocity: 100 });
// Later:
unsubscribe();
client.dispose();
serverCertificateHashesis aRelayCertHash[]of SHA-256 fingerprints — see the WebTransport docs for derivation. Production deployments using a public CA can omit it.
| Option | Required | Description |
|---|---|---|
url |
yes | Base WebTransport URL (https://...:4443/relay). |
roomId |
yes | Room identifier — relay-side broadcast scope. |
userId |
yes | Sender identity attached to every published message. |
token |
yes | Short-lived auth token minted by the app backend. |
serverCertificateHashes |
no | SHA-256 certificate hashes for self-signed certs. |
onError |
no | Connection / publish / decode errors. |
onConnectionChange |
no | Notification when the connection state changes (connecting, connected, closed, error). |
onDebug |
no | Debug logger; pairs with createDebugLogger('hiyve:semantic-relay'). |
maxSeenMessages |
no | Per-topic dedupe cache size (default 200). |
pruneCount |
no | Dedupe entries dropped when the cache is full (default 50). |
retryDelaysMs |
no | Connect-retry backoff within a single connect() call (default [200, 800, 2000]). Auth failures bypass. |
reconnect |
no | Keep the connection alive for the life of the room (default true). See Staying connected. |
reconnectBaseDelayMs |
no | First reconnect delay, doubling to the cap (default 1000). |
reconnectMaxDelayMs |
no | Ceiling for the reconnect delay (default 30000). |
getToken |
no | Called before each reconnect attempt to supply a fresh token. Without it, a reconnect that fails authentication stops. |
transport |
no | 'auto' (default), 'webtransport', or 'websocket'. In 'auto', WebTransport is tried first when available; any non-auth connect failure falls through to WebSocket. |
wsBufferedAmountHighWater |
no | WebSocket congestion threshold in bytes (default 16 KB). While the socket is congested, outbound messages coalesce latest-wins per topic so fresh state is never queued behind stale state. Ignored on WebTransport. |
The client wraps relay messages in a small envelope; serialization, framing, and identity stamping are handled internally.
A relay connection can be lost long after it was established — a VPN or tunnel
dropping, a Wi-Fi handover, a laptop waking, the relay restarting. By default
the client re-establishes it on its own, with a delay that doubles from
reconnectBaseDelayMs to reconnectMaxDelayMs, and it keeps trying even when
the very first connect() never succeeded, so a room joined on a network that
blocks the relay recovers as soon as that network changes. Retries fire
immediately when the browser reports the network is back or the tab becomes
visible, rather than waiting out the remaining delay.
Two things follow for consumers:
sessionInfo after a reconnect. The relay assigns a new session
ID on rejoin, so the snapshot connect() resolved with is stale from the
first drop onwards. onConnectionChange firing 'connected' again is the
signal.connect() still rejects when the first attempt fails — reconnection runs
alongside that rejection rather than replacing it. Pass reconnect: false to
connect once and stop.
Tokens expire. If a lesson can outlive its token, supply getToken so each
attempt presents a fresh one; without it, a reconnect rejected on
authentication ends the loop, because retrying a token the relay has already
refused achieves nothing. getToken is given the same 8s budget as a
handshake — a token endpoint on the same broken network usually hangs rather
than failing, and an unbounded wait there would stop the loop as surely as an
error. Rejecting or timing out is not fatal: the next attempt asks again.
MAX_DATAGRAM_BYTES); larger messages should fragment at the application layer.Every current browser is supported. WebTransport (the preferred transport) is available in Chrome/Edge 97+, Firefox 114+, and Safari 26.4+; everywhere else — including older Safari/iOS and networks that block UDP — the client falls back to WebSocket automatically. No consumer-side capability gating is required.
Delivery semantics are fire-and-forget on both transports: messages may be dropped under network congestion (natively on WebTransport; via latest-wins coalescing on WebSocket), so protocols built on this client should tolerate loss — exactly as they must for datagrams.
| Export | Description |
|---|---|
SemanticRelayClient |
The client class. |
RelayCertHash |
Shape of a single SHA-256 cert hash entry. |
RelayConnectionState |
'idle' | 'connecting' | 'connected' | 'error' | 'closed'. |
RelayEnvelope<T> |
Wire envelope shape. |
RelayMessage<T> |
Subscriber-visible message: { topic, payload, userId, messageId, timestamp }. |
RelayOptions |
Constructor options shape. |
RelayTransportPolicy |
'auto' | 'webtransport' | 'websocket'. |
RelayTransportKind |
'webtransport' | 'websocket' — see client.transportKind. |
DEFAULT_RECONNECT_BASE_MS |
Default first reconnect delay (1000). |
DEFAULT_RECONNECT_MAX_MS |
Default reconnect delay ceiling (30000). |
RECONNECT_HEALTHY_MS |
How long a connection must last before a later drop restarts the backoff from the base delay (30000). |
DefaultedRelayOptions |
Options after defaults are applied. |
RelaySubscribeHandler<T> |
Callback signature for subscribe. |
SessionInfo |
Session metadata returned by connect(). |
hasWebTransport() / hasWebSocket() |
Runtime capability probes. |
deriveWebSocketUrl(url) |
Maps the relay's https:// URL to its wss:// equivalent. |
DEFAULT_OPTIONS |
The full default options object. |
mergeOptions(user) |
Merge user options on top of DEFAULT_OPTIONS. |
MAX_DATAGRAM_BYTES |
Max envelope size (~1200). |
SESSION_ID_HEADER_BYTES |
Bytes reserved for the session-id header in each datagram. |
DEFAULT_MAX_SEEN_MESSAGES |
Default dedupe cache size. |
DEFAULT_PRUNE_COUNT |
Default prune count when the cache is full. |
DEFAULT_RETRY_DELAYS_MS |
Default reconnect backoff schedule. |
DEFAULT_CONNECT_TIMEOUT_MS |
Default connect timeout. |
DEFAULT_WS_BUFFERED_HIGH_WATER |
Default WebSocket congestion threshold (bytes). |
WS_FLUSH_INTERVAL_MS |
Congestion re-check cadence for coalesced sends. |
SESSION_INFO_TYPE |
Tag for SessionInfo messages on the control topic. |
MIT
@hiyve/semantic-relay-client— framework-agnostic browser client for the Hiyve Semantic Relay (Go WebTransport datagram service).Designed for high-frequency, fire-and-forget, room-scoped messaging where the WebRTC data channel's per-message overhead (negotiation, peer-to-peer routing, JSON snapshot envelopes) is too heavy. Typical use cases: instrument note events, semantic-avatar blendshape streams, spatial audio cues.
For React applications, use
@hiyve/react-semantic-relaywhich wraps this client with a context provider and topic-scoped pub/sub hooks.Example