Skip to content
mesh logoMeshWebrtc
Backendless peer-to-peer session transport over WebRTC data channels with manual pairing and host-authoritative star topology
Version
v26.10.0
Size
5.9 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 size5.9 KB4.9 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 + kick: host-side peer inventory and removal; join/leave arrive as tap events carrying the live peer.
  • Injectable rtc: every WebRTC object comes from a factory, so tests run fully in memory; @vielzeug/mesh/testing ships the in-memory fake.

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.