npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zakkster/lite-rollback-webrtc

v1.0.0

Published

RTCDataChannel-backed transport for @zakkster/lite-rollback. Browser-to-browser, no server in the data path. Includes a minimal manual-signalling helper and a deterministic 2-player Pong demo.

Readme

@zakkster/lite-rollback-webrtc

RTCDataChannel transport for @zakkster/lite-rollback. Browser-to-browser, no server in the data path. Ships with a manual-signalling helper for LAN play and a deterministic 2-player Pong demo as the reference integration.

npm version sponsor Zero-GC npm bundle size npm downloads TypeScript Dependencies License: MIT

npm install @zakkster/lite-rollback @zakkster/lite-rollback-local @zakkster/lite-rollback-webrtc
import { wrapDataChannel, connectPeer } from '@zakkster/lite-rollback-webrtc';
import { createSession } from '@zakkster/lite-rollback';

// You bring the open RTCDataChannel.
const transport = wrapDataChannel(myOpenDataChannel);

const session = createSession({ /* ... */ });
const peer = connectPeer({ session, transport, localPlayer: 0 });

function frame() {
  peer.tickLocal(readInputBitfield());
  peer.drain();
  session.step();
  render(session.fields);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

What this package provides

| Export | Purpose | |---|---| | wrapDataChannel(dc) | Wrap an already-open RTCDataChannel as a lite-rollback Transport. The only function you actually need. | | createHost(opts) | Convenience: returns a host-side WebRTC controller with createOffer, acceptAnswer, onOpen, pc. | | createJoiner(opts) | Joiner-side mirror: acceptOffer, onOpen, pc. | | connectPeer({...}) | Re-exported from @zakkster/lite-rollback-local: glues a Session + a Transport. | | packInput, unpackMessage, unpackMessageInto, MSG | Re-exported wire-protocol helpers. Same encoding as the local transport. | | assertTransport(t) | Re-exported runtime contract check. |

wrapDataChannel is the universal entry point. createHost / createJoiner are convenience for the simplest possible signalling: copy and paste SDP into a text box.


Allocation honesty

This is the only sister package with a non-zero per-message cost. Worth being explicit about:

| Path | Allocation per call | |---|---| | transport.send(payload) | 0 bytes. RTCDataChannel.send accepts an ArrayBufferView directly; the SCTP stack copies at the C++ boundary. | | transport receive (per incoming message) | 1 Uint8Array view (~24 bytes). The WebRTC API delivers each message as a fresh ArrayBuffer owned by the browser; we cannot pre-allocate or pool the underlying buffer. We wrap it in a Uint8Array view before handing it to the handler. The buffer itself is browser-allocated and out of our control. |

The downstream connectPeer parses these bytes directly into a pre-allocated Uint32Array FIFO via unpackMessageInto, so no further per-message garbage is produced. At 60 Hz × 2 players, that's ~120 view-headers per second per peer -- well within any GC budget.

The other two packages (lite-rollback core, lite-rollback-local) are strictly zero-alloc after construction. This package is zero-alloc except for the unavoidable per-receive view -- a fixed cost of the WebRTC API.


Real signalling

The built-in SDP-paste helpers are demoware. For production, use a real signalling channel -- WebSocket, Firebase Realtime Database, Supabase, your own backend, doesn't matter. The pattern:

import { wrapDataChannel } from '@zakkster/lite-rollback-webrtc';

const pc = new RTCPeerConnection({ iceServers: [/* your STUN/TURN */] });

// Host side:
const dc = pc.createDataChannel('rollback', { ordered: false, maxRetransmits: 0 });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendOverSignalling(offer);
const answer = await waitForAnswer();
await pc.setRemoteDescription(answer);

// Once `dc.readyState === 'open'`:
const transport = wrapDataChannel(dc);

Joiner side is symmetric: receive offer, call pc.setRemoteDescription, createAnswer, setLocalDescription, send the answer back, wait for ondatachannel, wrap.

Recommended RTCDataChannel options:

| Option | Value | Why | |---|---|---| | ordered | false | Inputs are self-contained per-frame; ordering is reconstructed from the frame number inside the payload. Out-of-order delivery is fine. | | maxRetransmits | 0 | Unreliable mode. A lost input is fine -- the next frame's input message updates the same slot with newer data. Reliability would only add latency. |


Pong demo

Open examples/pong/index.html in a modern browser. Three modes:

| Mode | What it does | |---|---| | Local (two tabs) | Opens a BroadcastChannel-backed Session in this tab. Open the page in two tabs, choose P0 in one and P1 in the other, play with yourself. | | WebRTC · host | Generates an SDP offer. Paste it to your peer; paste their answer back. Real RTCDataChannel. | | WebRTC · joiner | Paste in the host's offer; copy the generated answer back to them. |

The demo's <script type="importmap"> resolves @zakkster/lite-rollback and @zakkster/lite-rollback-local to jsdelivr CDN URLs, so the demo runs from file:// or any static server with no monorepo checkout and no build step. The @zakkster/lite-rollback-webrtc package itself resolves to a path relative to the demo, which works wherever the demo is served from inside the installed package.

What you'll see

  • Frame counter, rollback count, state checksum in the status bar.
  • Both peers' checksums stay identical every frame -- that's the determinism contract being verified in real time.
  • Open the network tab; throttle to "Slow 3G" -> the game keeps simulating; rollbacks tick up; visuals stay smooth.

Transport contract

All sister packages implement the same shape (verified by @zakkster/lite-rollback's assertTransport):

interface Transport {
  send(payload: Uint8Array, peer?: string): void;
  onMessage(handler: (payload: Uint8Array, peer?: string) => void): void;
  close(): void;
}

If you have an RTCDataChannel of any flavour (DTLS, your own QUIC bridge, anything that satisfies the spec interface), wrapDataChannel makes it a Transport. If you have a WebSocket, write your own three-method wrapper -- it's ~30 lines.


Testing

npm test          # node:test, 18 cases pass
npm run test:gc   # same suite under --expose-gc

What's tested in Node:

  • Wire-protocol round-trips via the re-exports.
  • wrapDataChannel against a mock RTCDataChannel shape (binaryType / readyState / onmessage / send / close): construction validation, send-after-close graceful drop, close clears the handler, multiple delivery modes (ArrayBuffer, Uint8Array, ignored strings).
  • createHost / createJoiner throw the documented error when RTCPeerConnection is unavailable (which is always the case in Node).
  • End-to-end: two Sessions wired up via two mock-DC-backed wrapDataChannel transports through connectPeer. The mock dispatches asynchronously via queueMicrotask -- matching real RTCDataChannel semantics, which never deliver synchronously inside .send(). A simulated misprediction triggers a rollback; both sessions converge byte-for-byte.

The real RTCDataChannel transport gets exercised end-to-end by the Pong demo -- that's the visual smoke test that can only run in a browser.


Browser compatibility

| Target | Supported | |---|---| | Chrome / Edge 76+ | yes | | Firefox 78+ | yes | | Safari 15+ (iOS 15+) | yes | | Node.js | no (no RTCPeerConnection in stdlib) |

For Node-side testing of the wire protocol, use @zakkster/lite-rollback-local's packInput / unpackMessage -- same encoding, no DOM dependency.


License

MIT (c) Zahary Shinikchiev