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

lipi-protocol

v0.1.0

Published

Type-safe protocol library for stylus-to-display annotation. The wire format used by Lipi (lipi.fly.dev) and any compatible client or server.

Downloads

150

Readme

lipi-protocol

Type-safe wire-format definitions for stylus-to-display annotation.

npm install lipi-protocol

This is the open protocol used by Lipi — beam pen strokes from any tablet to any screen, in real time. Use these types to build a Lipi-compatible client or server in any TypeScript / JavaScript environment.

The protocol is open. The hosted service (lipi.fly.dev) is the convenience layer. If you want to roll your own annotation stack on a different transport, hosting, or UI — this package gives you the canonical message shapes to interoperate.

Why this exists

Strokes are a primitive. Like payments (Stripe), like ink (PostScript), like packets (TCP). The right way to ship a primitive is to standardize the wire format and let anyone build clients, servers, and integrations against it.

This package is the wire-format specification, in TypeScript. No runtime dependencies. Works in browsers, Node, Workers, Deno, Bun. ~3KB minified.

What's in it

  • Stroke eventsstroke_start, stroke_point, stroke_end, laser_*, undo, clear. Coordinates normalized [0..1] of the captured content. Pressure-aware. Apple-Pencil-compatible.
  • Signaling messagespresence, offer / answer / ice (WebRTC), renegotiate, report (telemetry), viewer_event (stroke broadcast).
  • Role + room modeldisplay, controller, viewer. Room-keyed multi-tenancy.
  • URL helpersbuildSignalingUrl, buildViewerUrl, parseRoomFromLocation, generateRoomKey.
  • Pressure envelopewidthForPressure(base, p) matches the canonical Lipi renderer.

Quick start

Build a viewer (OBS Browser Source, transparent overlay)

import {
  buildSignalingUrl,
  isSignalingMessage,
  isStrokeEvent,
  type StrokeEvent,
} from "lipi-protocol";

const url = buildSignalingUrl({
  host: "lipi.fly.dev",
  role: "viewer",
  room: "your-room-key",
});

const ws = new WebSocket(url);

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (!isSignalingMessage(msg)) return;
  if (msg.type === "viewer_event" && isStrokeEvent(msg.payload)) {
    renderStroke(msg.payload);  // ← your renderer
  }
};

Render with the canonical pressure envelope

import { widthForPressure, type StrokeEvent } from "lipi-protocol";

function renderStroke(s: { points: { x: number; y: number; p?: number }[]; width: number; color: string }) {
  for (let i = 1; i < s.points.length; i++) {
    const a = s.points[i - 1]!;
    const b = s.points[i]!;
    const avgP = ((a.p ?? 0.5) + (b.p ?? 0.5)) / 2;
    const w = widthForPressure(s.width, avgP);
    // ctx.lineWidth = w; ctx.moveTo(a.x * W, a.y * H); ctx.lineTo(b.x * W, b.y * H); ctx.stroke();
  }
}

Generate room keys server-side

import { generateRoomKey } from "lipi-protocol";

const key = generateRoomKey();              // → "key_a3f8...2c1b"
const pretty = generateRoomKey("class_");   // → "class_..."

Wire format at a glance

Connection URL

wss://lipi.fly.dev/?role=<display|controller|viewer>&room=<room-key>

Stroke point

Normalized to the captured content's intrinsic dimensions:

{ x: number, y: number, p?: number }   // all in [0..1]

A stroke at {x: 0.5, y: 0.5} lands at the center of the content. Period. No matter what resolution the controller or display is rendering at. Alignment is mathematical, not heuristic.

Stroke start

{
  type: "stroke_start",
  color: "#E63916",
  width: 4,
  mode: "draw" | "highlight" | "laser",
  point: { x, y, p }
}

Subsequent stroke_point events extend the stroke. stroke_end commits it. undo pops the last committed stroke. clear drops them all.

Signaling

WebRTC offer/answer/ICE flows through the WebSocket between display and controller. Viewer-role peers receive a copy of every stroke event wrapped in { type: "viewer_event", payload: <StrokeEvent> } — they don't participate in WebRTC.

See src/messages.ts and src/strokes.ts for the full type definitions with JSDoc.

Compatibility

  • TypeScript 5.0+
  • Node 18+ (uses globalThis.crypto)
  • All modern browsers
  • Workers / Edge runtimes (no Node-specific imports)

License

MIT — build whatever you want with this. The package has zero runtime dependencies and adds no transitive surface to your app.

Related

If you build something interesting on this protocol, drop a link in a GitHub issue — I'll add it to a "built with" section.