Skip to content
mesh logoMeshWebrtc
Backendless peer-to-peer session transport over WebRTC data channels with manual pairing and host-authoritative star topology
Version
v3.0.0
Size
6.2 KB gzip
BrowserNode ≥22
createMeshHostcreateMeshGuestmeshCodecMeshProtocol

Why Mesh?

Browsers cannot discover peers on a LAN, so sharing live state between nearby devices has always meant running a server. Mesh pairs devices out-of-band — copy/paste, navigator.share, or QR via meshQrCodec — and then moves typed messages directly over a WebRTC data channel with no backend involved.

ts
// Before
const pc = new RTCPeerConnection();
const dc = pc.createDataChannel('sync');
await pc.setLocalDescription(await pc.createOffer());
// …wait for ICE, hand the SDP to the other device, wire expiry, timeouts,
// message framing, and dedupe yourself — per guest

// After
const host = createMeshHost<AppProtocol>();
const invitation = await host.createInvitation();
shareText(meshCodec.encode(invitation)); // copy/paste or navigator.share
const peer = await host.acceptAnswer(meshCodec.decode(answerText));
host.send(peer.id, 'snapshot', { rev: 1 });
FeatureMeshpulse (WebSocket)Raw RTCPeerConnection
Bundle size6.2 KB4.6 KB0 B
Runtime dependencies@vielzeug/arsenal none none
Works without a server
Typed message protocol
Multi-peer sessionsStar, host-authoritativeServer roomsManual per-peer wiring
Pairing safetyProof + TTL + approval hookServer auth

Use Mesh when a small group of devices on one network must exchange live state and you cannot — or do not want to — run a server.

Consider pulse when you have a backend anyway, need internet-wide reach, or want server-managed rooms and presence.

Limitations

Mesh is deliberately small. It does not provide:

  • Automatic LAN discovery — every pairing is an explicit out-of-band exchange.
  • Guest-to-guest relay — guests only ever talk to the host.
  • Reconnection across page reloads — a reload destroys the RTCPeerConnection; the consumer re-pairs and re-associates the stable peerId.
  • CRDTs or conflict resolution — payload semantics are yours.
  • TURN — there is no guarantee peers connect off-LAN.
  • Persistence, or any UI.

Two operational caveats matter in practice: the host page must stay open for the session to live, and client-isolated Wi-Fi, VPNs, or strict firewalls can block even same-LAN pairing.

Installation

sh
pnpm add @vielzeug/mesh
sh
npm install @vielzeug/mesh
sh
yarn add @vielzeug/mesh

Quick Start

Pair one host and one guest, then exchange typed messages. Pairing requires a secure context (https: or localhost) for RTCPeerConnection.

ts
import { createMeshGuest, createMeshHost, meshCodec, type MeshProtocol } from '@vielzeug/mesh';

// The consumer declares both directions of the conversation.
interface AppProtocol extends MeshProtocol {
  toHost: { note: string };
  toGuest: { ack: { ok: boolean } };
}

// Host side
const host = createMeshHost<AppProtocol>();
host.on('note', (message) => host.send(message.peerId, 'ack', { ok: true }));

const invitation = await host.createInvitation();
const invitationText = meshCodec.encode(invitation); // hand to the guest

// Guest side (other device)
const guest = createMeshGuest<AppProtocol>();
const answer = await guest.acceptInvitation(meshCodec.decode(invitationText));
const answerText = meshCodec.encode(answer); // hand back to the host

// Host accepts the answer; resolves once the channel is open
try {
  await host.acceptAnswer(meshCodec.decode(answerText));
  guest.send('note', 'hello over WebRTC');
} finally {
  host.dispose();
  guest.dispose();
}

Features

  • createMeshHost — host-authoritative star sessions; one RTCPeerConnection per guest.
  • createMeshGuest — single-host guest node with its own lifecycle.
  • meshCodec — compact base64url encoding for pairing payloads; pure and replaceable.
  • Typed protocol — declare toHost/toGuest maps once; send, on, and broadcast stay type-checked.
  • Manual pairing — invitation/answer exchange over copy/paste or navigator.share; proof of possession, TTL, and an approvePeer hook.
  • tap observability — status transitions, ICE state, byte counts, and rejections without affecting behavior.
  • peers, onPeer, kick — host-side peer inventory and lifecycle.
  • Injectable rtc — every WebRTC object comes from a factory, so tests run fully in memory.

Documentation

See Also

  • Pulse — typed WebSocket sessions when a server is available.
  • Arsenal — the utilities Mesh builds on for ids, randomness, and hashing.
  • Herald — in-process event bus for wiring mesh messages into your app.