SPT Sri Pranav Tene
← all work

06 / WebRTC · RPC

Light

Typed RPC and low-latency datagrams over WebRTC data channels, for peer-to-peer sync engines in browsers, Node.js and Bun.

3 runtimes browsers · Node.js · Bun
Protobuf binary framing
0 retransmits on the datagram channel
End-to-end inferred types

Light is a small TypeScript library for apps whose peers talk directly to each other: collaborative editors, multiplayer games and local-first sync engines. It turns a WebRTC data channel into two things a sync engine needs: reliable, typed RPC for operations that must arrive, and unreliable datagrams for state that is stale the moment a newer update exists.

     Peer A                                       Peer B
  +-----------+         RTCDataChannel         +-----------+
  |   sync    |------- call(id, method) ------>|  handler  |
  |  engine   |<------ result(id, value) ------|           |
  +-----------+                                +-----------+
        \                                            /
         \______ signaling, only to connect ________/

Figure 1: Request and response over a data channel

Types on both ends

Each peer declares the methods it exposes as a plain TypeScript type. Light infers every call’s arguments and return type from it, so calling a method the other side doesn’t have, or passing the wrong argument, is a compile error rather than a runtime surprise.

type PeerB = {
  greetUser: (name: string) => string;
};

const peer = new LightPeer<PeerA, PeerB>({ initiator: true, handlers });

const greeting = await peer.call('greetUser', 'Alice'); // string
await peer.call('greetUser', 123); // type error

Two channels, two promises

RPCDatagrams
DeliveryReliable and orderedBest effort, maxRetransmits: 0
ShapeRequest and typed responseTopic and payload
Use it forOperations, document edits, commandsCursors, presence, game state

A cursor position that arrives late is worse than one that never arrives, so datagrams are never retransmitted: the next update replaces the lost one.

Built for flaky networks

  • Calls queue while connecting. Calls made before the channel opens, or during a reconnect, wait and go out once it is up.
  • Automatic reconnection with a configurable retry budget (three by default), and events for every state change.
  • Bring your own signaling. Light only needs a way to pass a few messages to set up the connection: a WebSocket, a broadcast channel, anything.
  • One code path everywhere. It uses the browser’s native WebRTC and falls back to werift in Node.js and Bun, so the same peer code runs in a tab, a server or a CLI.

Every message is framed with Protocol Buffers, and binary payloads go through as raw Uint8Arrays with no base64 detour.