unbroker-beacon
v0.5.1
Published
Client SDK for Unbroker Beacon — demand-driven realtime infrastructure.
Maintainers
Readme
unbroker-beacon
Client SDK for Unbroker Beacon — demand-driven realtime infrastructure. Subscribe to channels, get last-value snapshots, and publish messages over a single WebSocket connection.
npm install unbroker-beaconUsage
import { Beacon } from "unbroker-beacon";
const beacon = new Beacon({
publicKey: "pk_live_xxx", // your project's public key
token: jwt, // short-lived JWT minted by your backend
});
// Subscribe (optionally request the last cached value as a snapshot)
const sub = beacon.channel("stocks.AAPL").subscribe({
lastValue: true,
onSnapshot: (data) => console.log("latest", data),
onMessage: (data) => console.log("update", data),
});
// Publish
beacon.channel("chat.room_1").publish({ message: "Hello" });
// Later
sub.unsubscribe();The simple form takes just a message handler:
beacon.channel("stocks.AAPL").subscribe((data) => console.log(data));WebSocket implementation
Browsers and Node 22+ expose a global WebSocket, used automatically. On older
Node, pass one explicitly:
import WebSocket from "ws";
new Beacon({ publicKey, token, WebSocketImpl: WebSocket });Staying connected (token refresh)
Client tokens are short-lived. Pass token as a function and the SDK calls
it for a fresh token on every (re)connect — so the connection survives token
expiry and stays "always on". It also auto-reconnects with backoff and uses a
heartbeat to recycle dead connections.
new Beacon({
publicKey: "pk_live_xxx",
// fetch a fresh JWT from your backend on every (re)connect
token: () => fetch("/api/beacon-token").then((r) => r.json()).then((d) => d.token),
});Reliability
The SDK is built to recover on its own:
- Auto-reconnect with jittered exponential backoff (up to 15s between attempts, forever). Jitter spreads a fleet's reconnects so a gateway restart doesn't produce synchronized retry waves.
- Dead-connection detection: a heartbeat ping every 30s; if nothing arrives for 60s the socket is recycled and reconnected.
- Full state restoration: on reconnect every subscription, demand watch and last-value snapshot request is replayed automatically.
- Bounded publish semantics: once a publish is on the wire, it rejects
after
requestTimeout(default 10s) instead of hanging on a lost ack, and rejects immediately when the connection drops before the ack. Publishes issued while offline are queued (up tomaxQueueSize, default 1000) with no timeout, flushed on reconnect, and their ack timeout starts at the flush. Payloads are snapshotted atpublish()time, so mutating the object afterwards never changes what is sent. - Connection state events: pass
onStateChangeto render connectivity and to detect gaps — an"open"after a"reconnecting"means messages may have been missed while offline; refetch anything not covered by alastValuesnapshot.
const beacon = new Beacon({
publicKey: "pk_live_xxx",
token: () => fetchFreshToken(),
onStateChange: (state) => {
ui.setConnectivity(state); // "connecting" | "open" | "reconnecting" | "closed"
if (state === "open" && wasReconnecting) refetchMissedEvents();
},
onError: (err) => console.warn("beacon:", err),
});Note: a static string token that has expired is a fatal error — the SDK
stops reconnecting (state "closed") and reports it via onError, because
retrying a dead token can never succeed. Use the function form of token so
every reconnect gets a fresh one.
Keyless (public) mode — no backend
For public realtime data you don't need a backend at all. Omit token and the
SDK connects with just the public key:
const beacon = new Beacon({ publicKey: "pk_live_xxx" });
beacon.channel("public.prices.BTC").subscribe((d) => console.log(d));This works when the project enables anonymous access, which is:
- subscribe-only (keyless clients can't publish),
- restricted to the project's public channel patterns (default
public.*), - origin-pinned — only browsers on your allow-listed domains can connect.
Origin pinning is browser-enforced (the Origin header): it stops other sites
from using your public key, but it is not authentication. Keep anything
sensitive on the token-based flow below.
Security
The publicKey is public. The token is a JWT your backend mints with the
project's secret (scoped per-channel, short-lived) — the secret never reaches
the client. See the Beacon spec.
License
MIT
