# @metered-ca/realtime

Browser SDK for the [Metered Realtime Messaging service](https://metered.ca).
WebSocket pub/sub + WebRTC peer-to-peer in one package: auto-reconnect,
perfect-negotiation, ICE-restart ladder, multi-stream metadata,
identity-preserving reconcile across transient WS drops.

```bash
npm install @metered-ca/realtime
```

## Get started in 5 minutes

Pick the path that matches what you're building:

- **[5-Min Quickstart · WebRTC →](https://www.metered.ca/docs/realtime-messaging/quickstart-webrtc)** — working video call between two browser tabs, single HTML file, no backend
- **[5-Min Quickstart · Pub/Sub →](https://www.metered.ca/docs/realtime-messaging/quickstart-pubsub)** — chat / IoT / AI agents over the wire protocol, any stack

## Quick start

```ts
import { MeteredPeer } from "@metered-ca/realtime";

const peer = new MeteredPeer({ apiKey: "pk_live_…" });

peer.on("peer-joined", ({ peer: remote }) => {
  remote.on("stream-added", ({ stream, metadata }) => {
    document.getElementById(metadata?.role ?? "remote").srcObject = stream;
  });
});

await peer.join("room-42");

const cam = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
peer.addStream(cam, { role: "camera" });
```

## Get a key

[Sign up for a free account](https://dashboard.metered.ca/signup?tool=turnserver), then in the dashboard go to **Realtime Messaging → Keys → Create key** and choose the **Publishable** type.

**Enable `Send` on the key.** It's off by default for publishable keys, but the WebRTC layer uses it to exchange SDP and ICE candidates between peers — without it, `peer.join()` and `peer.addStream()` succeed but the call never negotiates video. Leave `Subscribe`, `Publish`, and `Presence` on.

Copy the `pk_live_…` value and pass it as `apiKey`. Full walkthrough: [WebRTC quickstart](https://www.metered.ca/docs/realtime-messaging/quickstart-webrtc).

## Full documentation

The full SDK docs — API reference, migration guides, recipes, examples —
live at **[metered.ca/docs/realtime-messaging/sdk-javascript](https://www.metered.ca/docs/realtime-messaging/sdk-javascript)**.

- **[Getting started](https://www.metered.ca/docs/realtime-messaging/sdk-javascript/getting-started)** — installation, auth, first peer-to-peer call
- **[API reference](https://www.metered.ca/docs/realtime-messaging/sdk-javascript/api-reference/metered-peer)** — every public method, event, type, error class
- **[Guides](https://www.metered.ca/docs/realtime-messaging/sdk-javascript/guides/webrtc-video-call)** — auth patterns, reconnect best-practices, video call, AI agent comms, IoT telemetry, low-latency data channels, no-backend WebRTC
- **[Examples](https://www.metered.ca/docs/realtime-messaging/sdk-javascript/examples/basic-call)** — basic-call, data-channel, React integration
- **[Migration](https://www.metered.ca/docs/realtime-messaging/sdk-javascript/migration/from-simple-peer)** — from simple-peer or PeerJS

## What you get

- **One WebRTC session, N peers.** `peer.join(channel)` discovers peers
  via presence; every remote is exposed as a `RemotePeer` with its own
  events. No per-pair connection juggling.
- **Auto-reconnect across all three layers.** WebSocket-level reconnect
  with exponential backoff. Automatic ICE restart for TURN failover /
  Wi-Fi → cellular roam. Channel-level reconcile preserves `RemotePeer`
  identity through transient WS drops — your refs stay valid, surviving
  peers' underlying RTCPeerConnection is silently swapped.
- **Multi-stream + per-track metadata.** `peer.addStream(stream, { role })`
  ships a metadata bag the receiver gets on `stream-added`. Routes
  camera + screen + canvas through a single peer without a side channel.
- **Two auth paths.** A `pk_live_` publishable key for browser-only
  apps (zero backend), or a `tokenProvider` callback that hits your own
  mint endpoint and returns a JWT signed with `sk_live_` for per-user
  scoping (custom `channels`, `permissions`, `peerMetadata`,
  per-session TURN credentials).
- **Typed errors.** `instanceof`-able classes with stable `code` /
  `name` / field contracts. No parsing of message strings.
- **Browser-pure, Node-friendly.** Zero Node dependencies. Works in
  every modern browser. Node is supported too (useful for SSR /
  tests / Node-side relays): Node 22+ uses the global `WebSocket`;
  on Node 18–21 pass a `webSocketFactory` (e.g. the `ws` package).

## CDN (no bundler)

```html
<script src="https://unpkg.com/@metered-ca/realtime@1/dist/index.umd.js"></script>
<script>
  const peer = new MeteredPeer.MeteredPeer({ apiKey: "pk_live_…" });
</script>
```

Also available on jsDelivr at the same path.

## Build outputs

| File | Purpose |
|---|---|
| `dist/index.mjs` | ESM (modern bundlers) |
| `dist/index.cjs` | CJS (Node consumers) |
| `dist/index.umd.js` | UMD with browser global `MeteredPeer` (CDN, pre-minified) |
| `dist/index.d.ts` | Bundled type declarations |

UMD bundle is **~12.5 KB gzipped**, within a 30 KB public budget.

## Browser support

Chrome 90+, Firefox 90+, Safari 15+. WebRTC features require browsers
that implement the unified-plan SDP semantics + perfect-negotiation
rollback.

## React Native

Runs on React Native via
[`react-native-webrtc`](https://github.com/react-native-webrtc/react-native-webrtc):
the SDK uses whatever `RTCPeerConnection` / `WebSocket` the runtime
provides, so there's no separate fork. Call `registerGlobals()` (or pass
`rtcPeerConnectionFactory`) and follow the
[React Native guide](https://www.metered.ca/docs/realtime-messaging/sdk-javascript/guides/react-native).

## License

[MIT](./LICENSE) © Metered Inc.
